From 07d1280515c6e94610706750b28f579601849df0 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:57:20 -0400 Subject: [PATCH] GP-5570 copy symbols found in external debug files --- .../plugin/core/analysis/DWARFAnalyzer.java | 20 ++- .../bin/format/dwarf/DWARFImportOptions.java | 20 +++ .../ExternalDebugFileSymbolImporter.java | 158 ++++++++++++++++++ .../ExternalDebugFileSectionProvider.java | 36 +++- .../plugin/importer/ImporterUtilities.java | 16 ++ 5 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/ExternalDebugFileSymbolImporter.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index 3b7ff08aed..4445fe59da 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -4,9 +4,9 @@ * 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. @@ -19,8 +19,7 @@ import java.io.IOException; import ghidra.app.services.*; import ghidra.app.util.bin.format.dwarf.*; -import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider; -import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory; +import ghidra.app.util.bin.format.dwarf.sectionprovider.*; import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; @@ -34,7 +33,8 @@ public class DWARFAnalyzer extends AbstractAnalyzer { private static final String DWARF_LOADED_OPTION_NAME = "DWARF Loaded"; private static final String DWARF_ANALYZER_NAME = "DWARF"; private static final String DWARF_ANALYZER_DESCRIPTION = - "Automatically extracts DWARF info from ELF/MachO/PE files."; + "Automatically extracts DWARF info from ELF/MachO/PE files.\n" + + "Copies symbols from external debug files into the program."; /** @@ -92,6 +92,15 @@ public class DWARFAnalyzer extends AbstractAnalyzer { } try { + + if (importOptions.isCopyExternalDebugFileSymbols() && + dsp instanceof ExternalDebugFileSectionProvider extDSP) { + + ExternalDebugFileSymbolImporter extDFSI = new ExternalDebugFileSymbolImporter( + program, extDSP.getExternalProgram(), monitor); + extDFSI.importSymbols(log); + } + try (DWARFProgram prog = new DWARFProgram(program, importOptions, monitor, dsp)) { if (prog.getRegisterMappings() == null && importOptions.isImportFuncs()) { log.appendMsg("No DWARF to Ghidra register mappings found for this program's " + @@ -119,6 +128,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { return false; } + @Deprecated(forRemoval = true, since = "10.0") private static boolean oldCheckIfDWARFImported(Program prog) { // this was the old way of checking if the DWARF analyzer had already been run. Keep diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java index 0a0bbd0a46..d6b72c2f3d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java @@ -82,6 +82,12 @@ public class DWARFImportOptions { private static final String OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC = "Maximum length for a source map entry. Longer lengths will be replaced with 0"; + private static final String OPTION_COPY_EXTERNAL_DEBUG_FILE_SYMBOLS = + "Copy external debug file symbols"; + private static final String OPTION_COPY_EXTERNAL_DEBUG_FILE_SYMBOLS_DESC = + "Copies symbols (which will typically be mangled) from a found external debug file into " + + "the main program"; + //================================================================================================== // Old Option Names - Should stick around for multiple major versions after 10.2 //================================================================================================== @@ -119,6 +125,7 @@ public class DWARFImportOptions { private boolean ignoreParamStorage = false; private String defaultCC = ""; private long maxSourceMapEntryLength = 2000; + private boolean copyExternalDebugFileSymbols = true; /** * Create new instance @@ -438,6 +445,14 @@ public class DWARFImportOptions { maxSourceMapEntryLength = maxLength; } + public boolean isCopyExternalDebugFileSymbols() { + return copyExternalDebugFileSymbols; + } + + public void setCopyExternalDebugFileSymbols(boolean b) { + copyExternalDebugFileSymbols = b; + } + /** * See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)} * @@ -480,6 +495,9 @@ public class DWARFImportOptions { options.registerOption(OPTION_DEFAULT_CC, getDefaultCC(), null, OPTION_DEFAULT_CC_DESC); options.registerOption(OPTION_MAX_SOURCE_ENTRY_LENGTH, maxSourceMapEntryLength, null, OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC); + + options.registerOption(OPTION_COPY_EXTERNAL_DEBUG_FILE_SYMBOLS, + isCopyExternalDebugFileSymbols(), null, OPTION_COPY_EXTERNAL_DEBUG_FILE_SYMBOLS_DESC); } /** @@ -509,6 +527,8 @@ public class DWARFImportOptions { setDefaultCC(options.getString(OPTION_DEFAULT_CC, getDefaultCC())); setMaxSourceMapEntryLength( options.getLong(OPTION_MAX_SOURCE_ENTRY_LENGTH, getMaxSourceMapEntryLength())); + setCopyExternalDebugFileSymbols(options.getBoolean(OPTION_COPY_EXTERNAL_DEBUG_FILE_SYMBOLS, + isCopyExternalDebugFileSymbols())); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/ExternalDebugFileSymbolImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/ExternalDebugFileSymbolImporter.java new file mode 100644 index 0000000000..a2e0dc7be7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/ExternalDebugFileSymbolImporter.java @@ -0,0 +1,158 @@ +/* ### + * 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. + */ +package ghidra.app.util.bin.format.dwarf; + +import ghidra.app.util.importer.MessageLog; +import ghidra.program.database.function.OverlappingFunctionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities.ClearDataMode; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +/** + * Imports symbols from an external debug program (typically created via a reverse strip) into + * the program that contains the executable code that the symbols will be applied to. + */ +public class ExternalDebugFileSymbolImporter { + + private Program program; + private Program externalDebugProgram; + private SymbolTable symTable; + private FunctionManager funcMgr; + private SymbolTable extSymTable; + + private TaskMonitor monitor; + + private int funcSymbolsCopied; + private int dataSymbolsCopied; + private int symbolsSkipped; + private int totalSymbolCount; + + public ExternalDebugFileSymbolImporter(Program program, Program externalDebugProgram, + TaskMonitor monitor) { + this.program = program; + this.externalDebugProgram = externalDebugProgram; + this.monitor = monitor; + + this.symTable = program.getSymbolTable(); + this.funcMgr = program.getFunctionManager(); + + this.extSymTable = externalDebugProgram.getSymbolTable(); + } + + public void importSymbols(MessageLog log) throws CancelledException { + if (!isSameMemmap()) { + Msg.warn(this, + "Unable to copy external symbols from external debug file, memory map does not match"); + return; + } + + try { + monitor.setIndeterminate(false); + monitor.initialize(extSymTable.getNumSymbols(), "External debug file symbols"); + for (Symbol extSym : extSymTable.getPrimarySymbolIterator(true)) { + monitor.increment(); + totalSymbolCount++; + if (shouldCopyExtSymbol(extSym)) { + copyExtSymbol(extSym); + } + } + } + catch (InvalidInputException | CodeUnitInsertionException + | OverlappingFunctionException e) { + log.appendMsg("Error while copying external debug file symbols"); + log.appendException(e); + } + finally { + + Msg.info(this, "Copied %d/%d of %d func/data/total symbols from external debug file" + .formatted(funcSymbolsCopied, dataSymbolsCopied, totalSymbolCount)); + } + } + + private void copyExtSymbol(Symbol extSym) + throws InvalidInputException, OverlappingFunctionException, CodeUnitInsertionException { + SymbolType symType = extSym.getSymbolType(); + String name = extSym.getName(); + Address addr = extSym.getAddress(); + if (symType == SymbolType.FUNCTION && extSym.getObject() instanceof Function extFunc && + !extFunc.isThunk()) { + Function existingFunction = funcMgr.getFunctionAt(addr); + if (existingFunction == null) { + existingFunction = + funcMgr.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED); + } + else if (!name.equals(existingFunction.getName())) { + addLabelIfNeeded(name, addr); + } + funcSymbolsCopied++; + } + else if (symType == SymbolType.LABEL && extSym.getObject() instanceof Data extData) { + if (Undefined.isUndefined(extData.getDataType()) && + DataUtilities.isUndefinedRange(program, addr, addr.add(extData.getLength()))) { + DataType undefined = Undefined.getUndefinedDataType(extData.getLength()); + DataUtilities.createData(program, addr, undefined, -1, + ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); + } + addLabelIfNeeded(name, addr); + dataSymbolsCopied++; + } + else { + symbolsSkipped++; + } + } + + private void addLabelIfNeeded(String name, Address addr) throws InvalidInputException { + for (Symbol sym : symTable.getSymbolsAsIterator(addr)) { + if (sym.getName().equals(name)) { + return; + } + } + symTable.createLabel(addr, name, SourceType.IMPORTED); + } + + private boolean shouldCopyExtSymbol(Symbol extSym) { + return !extSym.getParentNamespace().isLibrary(); + } + + private boolean isCommonMemblock(MemoryBlock memBlk) { + return memBlk.isExecute(); + } + + private boolean isSameMemmap() { + for (MemoryBlock p1MemBlock : program.getMemory().getBlocks()) { + if (!isCommonMemblock(p1MemBlock)) { + continue; + } + MemoryBlock p2MemBlock = + externalDebugProgram.getMemory().getBlock(p1MemBlock.getStart()); + if (p2MemBlock == null || !p2MemBlock.getName().equals(p1MemBlock.getName()) || + p2MemBlock.getSize() != p1MemBlock.getSize()) { + return false; + } + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java index 6412034576..7d08760b79 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java @@ -4,9 +4,9 @@ * 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. @@ -19,12 +19,14 @@ import java.io.IOException; import java.net.MalformedURLException; import java.util.List; +import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.format.dwarf.external.*; import ghidra.app.util.importer.MessageLog; -import ghidra.app.util.opinion.ElfLoader; +import ghidra.app.util.opinion.*; import ghidra.formats.gfilesystem.*; import ghidra.framework.options.Options; +import ghidra.plugin.importer.ImporterUtilities; import ghidra.program.database.ProgramDB; import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.lang.Language; @@ -69,17 +71,27 @@ public class ExternalDebugFileSectionProvider extends BaseSectionProvider { fsService.getByteProvider(refdDebugFile.file.getFSRL(), false, monitor);) { Object consumer = new Object(); Language lang = program.getLanguage(); - CompilerSpec compSpec = - lang.getCompilerSpecByID(program.getCompilerSpec().getCompilerSpecID()); + LoadSpec origLoadSpec = ImporterUtilities.getLoadSpec(program); + if (origLoadSpec == null) { + return null; + } + + CompilerSpec compSpec = origLoadSpec.getLanguageCompilerSpec().getCompilerSpec(); + Program debugProgram = new ProgramDB("temp external debug info for " + program.getName(), lang, compSpec, consumer); + + Loader origLoader = origLoadSpec.getLoader(); + List