GP-5570 copy symbols found in external debug files

This commit is contained in:
dev747368
2025-04-17 14:57:20 -04:00
parent 5fb58661b9
commit 07d1280515
5 changed files with 236 additions and 14 deletions

View File

@@ -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

View File

@@ -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()));
}
}

View File

@@ -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;
}
}

View File

@@ -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<Option> defaultOptions = origLoader.getDefaultOptions(debugFileByteProvider,
origLoadSpec, debugProgram, false);
ElfLoader elfLoader = new ElfLoader();
elfLoader.load(debugFileByteProvider, null, List.of(), debugProgram, monitor,
elfLoader.load(debugFileByteProvider, null, defaultOptions, debugProgram, monitor,
new MessageLog());
ExternalDebugFileSectionProvider result =
new ExternalDebugFileSectionProvider(debugProgram,
debugFileByteProvider.getFSRL());
ExternalDebugFileSectionProvider result = new ExternalDebugFileSectionProvider(
debugProgram, debugFileByteProvider.getFSRL());
debugProgram.release(consumer);
return result;
}
@@ -106,6 +118,8 @@ public class ExternalDebugFileSectionProvider extends BaseSectionProvider {
@Override
public void close() {
// we close the parent class'es program instance here because we repurposed it from its
// normal use-case of referring to the main program
if (program != null) {
program.release(this);
}
@@ -114,6 +128,10 @@ public class ExternalDebugFileSectionProvider extends BaseSectionProvider {
program = null;
}
public Program getExternalProgram() {
return program;
}
@Override
public void updateProgramInfo(Program program) {
Options options = program.getOptions(Program.PROGRAM_INFO);

View File

@@ -565,4 +565,20 @@ public class ImporterUtilities {
.findFirst()
.orElse(null);
}
/**
* Get's the {@link LoadSpec} that was used to import the given {@link Program}
*
* @param program The {@link Program}
* @return The {@link LoadSpec} that was used to import the given {@link Program}, or null if
* it could not be determined
*/
public static LoadSpec getLoadSpec(Program program) {
ByteProvider provider;
if (program == null || (provider = getProvider(program)) == null) {
return null;
}
return getLoadSpec(provider, program);
}
}