/* ### * IP: GHIDRA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Adds DWARF source file info to the current program. // A source file that is relative after path normalization will have all leading "." // and "/../" entries stripped and then be placed under an artificial directory. // Note that you can run this script on a program that has already been analyzed by the // DWARF analyzer. //@category DWARF import java.io.IOException; import java.util.*; import ghidra.app.script.GhidraScript; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.dwarf.*; import ghidra.app.util.bin.format.dwarf.line.DWARFLine; import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr; import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileInfo; import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider; import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory; import ghidra.framework.store.LockException; import ghidra.program.database.sourcemap.SourceFile; import ghidra.program.database.sourcemap.SourceFileIdType; import ghidra.program.model.address.*; import ghidra.program.model.sourcemap.SourceFileManager; import ghidra.util.Msg; import ghidra.util.SourceFileUtils; import ghidra.util.exception.CancelledException; public class DWARFLineInfoSourceMapScript extends GhidraScript { public static final int ENTRY_MAX_LENGTH = 1000; private static final int MAX_ERROR_MSGS_TO_DISPLAY = 25; private static final int MAX_WARNING_MSGS_TO_DISPLAY = 25; private static final String COMPILATION_ROOT_DIRECTORY = DWARFImporter.DEFAULT_COMPILATION_DIR; private int numErrors; private int numWarnings; @Override protected void run() throws Exception { if (!currentProgram.hasExclusiveAccess()) { Msg.showError(this, null, "Exclusive Access Required", "Must have exclusive access to a program to add source map info"); return; } DWARFSectionProvider dsp = DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor); if (dsp == null) { printerr("Unable to find DWARF information"); return; } DWARFImportOptions importOptions = new DWARFImportOptions(); try (DWARFProgram dprog = new DWARFProgram(currentProgram, importOptions, monitor, dsp)) { dprog.init(monitor); addSourceLineInfo(dprog); } } private void addSourceLineInfo(DWARFProgram dprog) throws CancelledException, IOException, LockException, AddressOverflowException { BinaryReader reader = dprog.getDebugLineBR(); if (reader == null) { popup("Unable to get reader for debug line info"); return; } int entryCount = 0; List compUnits = dprog.getCompilationUnits(); SourceFileManager sourceManager = currentProgram.getSourceFileManager(); monitor.initialize(compUnits.size(), "DWARF: Reading Source Information"); // add all the source files and read the source map entries Map sourceFileInfoToSourceFile = new HashMap<>(); Set badSourceFileInfo = new HashSet<>(); List sourceInfo = new ArrayList<>(); for (DWARFCompilationUnit cu : compUnits) { DWARFLine dLine = cu.getLine(); monitor.increment(); for (SourceFileInfo sfi : dLine.getAllSourceFileInfos()) { if (sourceFileInfoToSourceFile.containsKey(sfi)) { continue; } if (badSourceFileInfo.contains(sfi)) { continue; } try { String path = SourceFileUtils.normalizeDwarfPath(sfi.filePath(), COMPILATION_ROOT_DIRECTORY); SourceFileIdType type = sfi.md5() == null ? SourceFileIdType.NONE : SourceFileIdType.MD5; SourceFile sFile = new SourceFile(path, type, sfi.md5()); sourceManager.addSourceFile(sFile); sourceFileInfoToSourceFile.put(sfi, sFile); } catch (IllegalArgumentException e) { if (numErrors++ < MAX_ERROR_MSGS_TO_DISPLAY) { printerr("Exception creating source file %s".formatted(e.getMessage())); } badSourceFileInfo.add(sfi); continue; } } sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader)); } monitor.setIndeterminate(true); monitor.setMessage("Sorting " + sourceInfo.size() + " entries"); sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address())); monitor.setIndeterminate(false); monitor.initialize(sourceInfo.size(), "DWARF: Applying Source Map Info"); AddressSet warnedAddresses = new AddressSet(); for (int i = 0; i < sourceInfo.size(); i++) { monitor.increment(1); SourceFileAddr sourceFileAddr = sourceInfo.get(i); if (sourceFileAddr.isEndSequence()) { continue; } if (sourceFileAddr.fileName() == null) { continue; } SourceFileInfo sfi = new SourceFileInfo(sourceFileAddr.fileName(), sourceFileAddr.md5()); if (badSourceFileInfo.contains(sfi)) { continue; } Address addr = dprog.getCodeAddress(sourceFileAddr.address()); if (warnedAddresses.contains(addr)) { continue; // only warn once per address } if (!currentProgram.getMemory().getExecuteSet().contains(addr)) { if (numWarnings++ < MAX_WARNING_MSGS_TO_DISPLAY) { printerr( "entry for non-executable address; skipping: file %s line %d address: %s %x" .formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address())); } warnedAddresses.add(addr); continue; } long length = getLength(i, sourceInfo); if (length < 0) { if (numWarnings++ < MAX_WARNING_MSGS_TO_DISPLAY) { println( "Error computing entry length for file %s line %d address %s %x; replacing" + " with length 0 entry".formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address())); } } if (length > ENTRY_MAX_LENGTH) { if (numWarnings++ < MAX_WARNING_MSGS_TO_DISPLAY) { println( ("entry for file %s line %d address: %s %x length %d too large, replacing " + "with length 0 entry").formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address(), length)); } } SourceFile source = sourceFileInfoToSourceFile.get(sfi); try { sourceManager.addSourceMapEntry(source, sourceFileAddr.lineNum(), addr, length); } catch (IllegalArgumentException e) { if (numErrors++ < MAX_ERROR_MSGS_TO_DISPLAY) { printerr(e.getMessage()); } continue; } entryCount++; } if (numWarnings >= MAX_WARNING_MSGS_TO_DISPLAY) { println("Additional warning messages suppressed"); } if (numErrors >= MAX_ERROR_MSGS_TO_DISPLAY) { println("Additional error messages suppressed"); } println("Added " + entryCount + " source map entries"); printf("There were %d errors and %d warnings\n", numErrors,numWarnings); } private long getLength(int i, List allSFA) { SourceFileAddr iAddr = allSFA.get(i); long iOffset = iAddr.address(); for (int j = i + 1; j < allSFA.size(); j++) { SourceFileAddr current = allSFA.get(j); long currentAddr = current.address(); if (current.isEndSequence()) { return currentAddr + 1 - iOffset; } if (currentAddr != iOffset) { return currentAddr - iOffset; } } return -1; } }