diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 4ceb05162e..627aaa2641 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -15,6 +15,7 @@ Module.manifest||GHIDRA||||END| data/ElfFunctionsThatDoNotReturn||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| +data/GolangFunctionsThatDoNotReturn||GHIDRA||||END| data/MachOFunctionsThatDoNotReturn||GHIDRA||||END| data/PEFunctionsThatDoNotReturn||GHIDRA||||END| data/base.file.extensions.icons.theme.properties||GHIDRA||||END| @@ -85,6 +86,7 @@ data/symbols/win64/mfc90u.exports||GHIDRA||||END| data/symbols/win64/msvcrt.hints||GHIDRA||||END| data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END| data/typeinfo/generic/generic_clib_64.gdt||GHIDRA||||END| +data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END| data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END| data/typeinfo/win32/msvcrt/clsids.txt||GHIDRA||reviewed||END| data/typeinfo/win32/msvcrt/guids.txt||GHIDRA||reviewed||END| diff --git a/Ghidra/Features/Base/data/ExtensionPoint.manifest b/Ghidra/Features/Base/data/ExtensionPoint.manifest index 273587cd9c..9fb4bec9b9 100644 --- a/Ghidra/Features/Base/data/ExtensionPoint.manifest +++ b/Ghidra/Features/Base/data/ExtensionPoint.manifest @@ -19,4 +19,5 @@ InstructionSkipper DataTypeReferenceFinder ChecksumAlgorithm OverviewColorService +DWARFFunctionFixup ElfInfoProducer diff --git a/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn b/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn new file mode 100644 index 0000000000..1c85fca0c5 --- /dev/null +++ b/Ghidra/Features/Base/data/GolangFunctionsThatDoNotReturn @@ -0,0 +1,66 @@ +# Golang function names which do not return +runtime.abort.abi0 +runtime.exit.abi0 +runtime.dieFromSignal +runtime.exitThread +runtime.fatal +runtime.fatalthrow +runtime.fatalpanic +runtime.gopanic +runtime.panicdivide +runtime.throw + +runtime.goPanicIndex +runtime.goPanicIndexU +runtime.goPanicSliceAlen +runtime.goPanicSliceAlenU +runtime.goPanicSliceAcap +runtime.goPanicSliceAcapU +runtime.goPanicSliceB +runtime.goPanicSliceBU +runtime.goPanicSlice3Alen +runtime.goPanicSlice3AlenU +runtime.goPanicSlice3Acap +runtime.goPanicSlice3AcapU +runtime.goPanicSlice3B +runtime.goPanicSlice3BU +runtime.goPanicSlice3C +runtime.goPanicSlice3CU +runtime.goPanicSliceConvert + +runtime.panicIndex +runtime.panicIndexU +runtime.panicSliceAlen +runtime.panicSliceAlenU +runtime.panicSliceAcap +runtime.panicSliceAcapU +runtime.panicSliceB +runtime.panicSliceBU +runtime.panicSlice3Alen +runtime.panicSlice3AlenU +runtime.panicSlice3Acap +runtime.panicSlice3AcapU +runtime.panicSlice3B +runtime.panicSlice3BU +runtime.panicSlice3C +runtime.panicSlice3CU +runtime.panicSliceConvert + +runtime.panicdottypeE +runtime.panicdottypeI +runtime.panicnildottype + +runtime.panicoverflow +runtime.panicfloat +runtime.panicmem +runtime.panicmemAddr +runtime.panicshift + +runtime.goexit0 +runtime.goexit0.abi0 +runtime.goexit1 +runtime.goexit.abi0 +runtime.Goexit + +runtime.sigpanic +runtime.sigpanic0.abi0 diff --git a/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml b/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml index d47b3c0f7b..5658ad2140 100644 --- a/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml +++ b/Ghidra/Features/Base/data/noReturnFunctionConstraints.xml @@ -1,5 +1,8 @@ + + GolangFunctionsThatDoNotReturn + ElfFunctionsThatDoNotReturn @@ -9,6 +12,9 @@ MachOFunctionsThatDoNotReturn + + GolangFunctionsThatDoNotReturn + PEFunctionsThatDoNotReturn diff --git a/Ghidra/Features/Base/data/typeinfo/golang/golang_1.18_anybit_any.gdt b/Ghidra/Features/Base/data/typeinfo/golang/golang_1.18_anybit_any.gdt new file mode 100644 index 0000000000..85f3ebe9ee Binary files /dev/null and b/Ghidra/Features/Base/data/typeinfo/golang/golang_1.18_anybit_any.gdt differ diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java index 59885a6062..1a2c9f56e7 100644 --- a/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/DWARF_ExtractorScript.java @@ -57,7 +57,6 @@ import ghidra.app.script.GhidraScript; import ghidra.app.util.bin.format.dwarf4.next.*; -import ghidra.program.model.data.BuiltInDataTypeManager; public class DWARF_ExtractorScript extends GhidraScript { @@ -70,8 +69,7 @@ public class DWARF_ExtractorScript extends GhidraScript { DWARFImportOptions importOptions = new DWARFImportOptions(); importOptions.setImportLimitDIECount(Integer.MAX_VALUE); try (DWARFProgram dwarfProg = new DWARFProgram(currentProgram, importOptions, monitor)) { - BuiltInDataTypeManager dtms = BuiltInDataTypeManager.getDataTypeManager(); - DWARFParser dp = new DWARFParser(dwarfProg, dtms, monitor); + DWARFParser dp = new DWARFParser(dwarfProg, monitor); DWARFImportSummary importSummary = dp.parse(); importSummary.logSummaryResults(); } diff --git a/Ghidra/Features/Base/ghidra_scripts/FixupGolangFuncParamStorageScript.java b/Ghidra/Features/Base/ghidra_scripts/FixupGolangFuncParamStorageScript.java new file mode 100644 index 0000000000..98768f34c7 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/FixupGolangFuncParamStorageScript.java @@ -0,0 +1,47 @@ +/* ### + * 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. + */ +// Assigns custom storage for params of a golang function to match golang's abi-internal +// register-based calling convention, or abi0 (all stack based) if abi-internal is not +// specified for the arch. +//@category Functions +//@menupath Tools.Fix Golang Function Param Storage +import java.util.List; + +import ghidra.app.script.GhidraScript; +import ghidra.app.util.bin.format.golang.GoFunctionFixup; +import ghidra.app.util.bin.format.golang.GoVer; +import ghidra.program.model.listing.Function; + +public class FixupGolangFuncParamStorageScript extends GhidraScript { + + @Override + protected void run() throws Exception { + Function func; + if (currentAddress == null || (func = getFunctionContaining(currentAddress)) == null) { + return; + } + GoVer goVersion = GoVer.fromProgramProperties(currentProgram); + if ( goVersion == GoVer.UNKNOWN ) { + List versions = List.of(GoVer.values()); + goVersion = + askChoice("Golang Version", "What is the golang version?", versions, GoVer.UNKNOWN); + } + println("Fixing param storage for function %s@%s".formatted(func.getName(), + func.getEntryPoint())); + GoFunctionFixup.fixupFunction(func, goVersion); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java index bbe8e2a995..d2e13ec137 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java @@ -82,6 +82,14 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { setDefaultEnablement(true); } + @Override + public boolean getDefaultEnablement(Program program) { + if ("golang".equals(program.getCompilerSpec().getCompilerSpecID().toString())) { + return false; + } + return super.getDefaultEnablement(program); + } + @Override public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) { dtmService = AutoAnalysisManager.getAnalysisManager(program).getDataTypeManagerService(); 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 d15e87818c..f0a215c001 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 @@ -26,7 +26,6 @@ import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProvid import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.data.BuiltInDataTypeManager; import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -180,13 +179,14 @@ public class DWARFAnalyzer extends AbstractAnalyzer { return false; } - try (DWARFSectionProvider dsp = - DWARFSectionProviderFactory.createSectionProviderFor(program, monitor)) { - if (dsp == null) { - Msg.info(this, "Unable to find DWARF information, skipping DWARF analysis"); - return false; - } + DWARFSectionProvider dsp = + DWARFSectionProviderFactory.createSectionProviderFor(program, monitor); // closed by DWARFProgram + if (dsp == null) { + Msg.info(this, "Unable to find DWARF information, skipping DWARF analysis"); + return false; + } + try { try (DWARFProgram prog = new DWARFProgram(program, importOptions, monitor, dsp)) { if (prog.getRegisterMappings() == null && importOptions.isImportFuncs()) { log.appendMsg( @@ -195,8 +195,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { "], function information may be incorrect / incomplete."); } - DWARFParser dp = - new DWARFParser(prog, BuiltInDataTypeManager.getDataTypeManager(), monitor); + DWARFParser dp = new DWARFParser(prog, monitor); DWARFImportSummary parseResults = dp.parse(); parseResults.logSummaryResults(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java new file mode 100644 index 0000000000..ea50023d5f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java @@ -0,0 +1,369 @@ +/* ### + * 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.plugin.core.analysis; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Set; + +import generic.jar.ResourceFile; +import ghidra.app.services.*; +import ghidra.app.util.MemoryBlockUtils; +import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ItemWithAddress; +import ghidra.app.util.bin.format.golang.*; +import ghidra.app.util.bin.format.golang.rtti.GoModuledata; +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.data.Structure; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; +import ghidra.util.task.UnknownProgressWrappingTaskMonitor; +import ghidra.xml.XmlParseException; +import utilities.util.FileUtilities; + +/** + * Analyzes Golang binaries for RTTI and function symbol information. + */ +public class GolangSymbolAnalyzer extends AbstractAnalyzer { + + private final static String NAME = "Golang Symbol"; + private final static String DESCRIPTION = """ + Analyze Golang binaries for RTTI and function symbols. + 'Apply Data Archives' and 'Shared Return Calls' analyzers should be disabled \ + for best results."""; + private final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME = + "ARTIFICIAL.runtime.zerobase"; + + private GolangAnalyzerOptions analyzerOptions = new GolangAnalyzerOptions(); + + public GolangSymbolAnalyzer() { + super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); + setPriority(AnalysisPriority.FORMAT_ANALYSIS.after().after()); + setDefaultEnablement(true); + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + monitor.setMessage("Golang symbol analyzer"); + + try (GoRttiContext programContext = GoRttiContext.getContextFor(program, log)) { + if (programContext == null) { + Msg.error(this, "Golang analyzer error: unable to get GoRttiContext"); + return false; + } + programContext.discoverGoTypes(monitor); + + GoModuledata firstModule = programContext.getFirstModule(); + programContext.labelStructure(firstModule, "firstmoduledata"); + + UnknownProgressWrappingTaskMonitor upwtm = + new UnknownProgressWrappingTaskMonitor(monitor, 100); + programContext.setMarkupTaskMonitor(upwtm); + upwtm.initialize(0); + upwtm.setMessage("Marking up Golang RTTI structures"); + programContext.markup(firstModule, false); + programContext.setMarkupTaskMonitor(null); + + markupMiscInfoStructs(program); + markupWellknownSymbols(programContext); + fixupNoReturnFuncs(program); + setupProgramContext(programContext); + programContext.recoverDataTypes(monitor); + + if (analyzerOptions.createBootstrapDatatypeArchive) { + createBootstrapGDT(programContext, program, monitor); + } + } + catch (IOException e) { + Msg.error(this, "Golang analysis failure", e); + } + + return true; + } + + @Override + public void registerOptions(Options options, Program program) { + options.registerOption(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME, + analyzerOptions.createBootstrapDatatypeArchive, null, + GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_DESC); + } + + @Override + public void optionsChanged(Options options, Program program) { + analyzerOptions.createBootstrapDatatypeArchive = + options.getBoolean(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME, + analyzerOptions.createBootstrapDatatypeArchive); + } + + private void markupWellknownSymbols(GoRttiContext programContext) throws IOException { + Program program = programContext.getProgram(); + + Symbol g0 = SymbolUtilities.getUniqueSymbol(program, "runtime.g0"); + Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class); + if (g0 != null && gStruct != null) { + programContext.markupAddressIfUndefined(g0.getAddress(), gStruct); + } + + Symbol m0 = SymbolUtilities.getUniqueSymbol(program, "runtime.m0"); + Structure mStruct = programContext.getGhidraDataType("runtime.m", Structure.class); + if (m0 != null && mStruct != null) { + programContext.markupAddressIfUndefined(m0.getAddress(), mStruct); + } + } + + private void markupMiscInfoStructs(Program program) { + // this also adds "golang" info to program properties + + ItemWithAddress wrappedBuildInfo = GoBuildInfo.findBuildInfo(program); + if (wrappedBuildInfo != null && program.getListing() + .isUndefined(wrappedBuildInfo.address(), wrappedBuildInfo.address())) { + // this will mostly be PE binaries that don't have Elf markup magic stuff + wrappedBuildInfo.item().markupProgram(program, wrappedBuildInfo.address()); + } + ItemWithAddress wrappedPeBuildId = PEGoBuildId.findBuildId(program); + if (wrappedPeBuildId != null && program.getListing() + .isUndefined(wrappedPeBuildId.address(), wrappedPeBuildId.address())) { + // HACK to handle golang hack: check if a function symbol was laid down at the location + // of the buildId string. If true, convert it to a plain label + Symbol[] buildIdSymbols = + program.getSymbolTable().getSymbols(wrappedPeBuildId.address()); + for (Symbol sym : buildIdSymbols) { + if (sym.getSymbolType() == SymbolType.FUNCTION) { + String symName = sym.getName(); + sym.delete(); + try { + program.getSymbolTable() + .createLabel(wrappedPeBuildId.address(), symName, + SourceType.IMPORTED); + } + catch (InvalidInputException e) { + // ignore + } + break; + } + } + wrappedPeBuildId.item().markupProgram(program, wrappedPeBuildId.address()); + } + } + + private void fixupNoReturnFuncs(Program program) { + Set noreturnFuncnames = new HashSet<>(); + + try { + for (ResourceFile file : NonReturningFunctionNames.findDataFiles(program)) { + FileUtilities.getLines(file) + .stream() + .map(String::trim) + .filter(s -> !s.isBlank() && !s.startsWith("#")) + .forEach(noreturnFuncnames::add); + } + } + catch (IOException | XmlParseException e) { + Msg.error(this, "Failed to read Golang noreturn func data file", e); + } + + int count = 0; + SymbolTable symbolTable = program.getSymbolTable(); + for (Symbol symbol : symbolTable.getPrimarySymbolIterator(true)) { + String name = symbol.getName(false); + if (symbol.isExternal() /* typically not an issue with golang */ + || !noreturnFuncnames.contains(name)) { + continue; + } + + Function functionAt = program.getFunctionManager().getFunctionAt(symbol.getAddress()); + if (functionAt == null) { + continue; + } + if (!functionAt.hasNoReturn()) { + + functionAt.setNoReturn(true); + + program.getBookmarkManager() + .setBookmark(symbol.getAddress(), BookmarkType.ANALYSIS, + "Non-Returning Function", "Non-Returning Golang Function Identified"); + count++; + } + } + Msg.info(this, "Marked %d golang funcs as NoReturn".formatted(count)); + } + + private Address createFakeContextMemory(Program program, long len) { + long offset_from_eom = 0x100_000; + Address max = program.getAddressFactory().getDefaultAddressSpace().getMaxAddress(); + Address mbStart = max.subtract(offset_from_eom + len - 1); + MemoryBlock newMB = + MemoryBlockUtils.createUninitializedBlock(program, false, "ARTIFICAL_GOLANG_CONTEXT", + mbStart, len, "Artifical memory block created to hold golang context data types", + null, true, true, false, null); + return newMB.getStart(); + } + + private void setupProgramContext(GoRttiContext programContext) throws IOException { + Program program = programContext.getProgram(); + GoRegisterInfo goRegInfo = GoRegisterInfoManager.getInstance() + .getRegisterInfoForLang(program.getLanguage(), + programContext.getGolangVersion()); + + MemoryBlock txtMemblock = program.getMemory().getBlock(".text"); + if (txtMemblock != null && goRegInfo.getZeroRegister() != null) { + try { + program.getProgramContext() + .setValue(goRegInfo.getZeroRegister(), txtMemblock.getStart(), + txtMemblock.getEnd(), BigInteger.ZERO); + } + catch (ContextChangeException e) { + Msg.error(this, "Unexpected Error", e); + } + } + + int alignment = programContext.getPtrSize(); + long sizeNeeded = 0; + + Symbol zerobase = SymbolUtilities.getUniqueSymbol(program, "runtime.zerobase"); + long zerobaseSymbol = sizeNeeded; + sizeNeeded += zerobase == null + ? NumericUtilities.getUnsignedAlignedValue(1 /* sizeof(byte) */, alignment) + : 0; + + long gStructOffset = sizeNeeded; + Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class); + sizeNeeded += gStruct != null + ? NumericUtilities.getUnsignedAlignedValue(gStruct.getLength(), alignment) + : 0; + + long mStructOffset = sizeNeeded; + Structure mStruct = programContext.getGhidraDataType("runtime.m", Structure.class); + sizeNeeded += mStruct != null + ? NumericUtilities.getUnsignedAlignedValue(mStruct.getLength(), alignment) + : 0; + + Address contextMemoryAddr = sizeNeeded > 0 + ? createFakeContextMemory(program, sizeNeeded) + : null; + + if (zerobase == null) { + programContext.labelAddress(contextMemoryAddr.add(zerobaseSymbol), + ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); + } + + if (gStruct != null) { + Address gAddr = contextMemoryAddr.add(gStructOffset); + programContext.markupAddressIfUndefined(gAddr, gStruct); + programContext.labelAddress(gAddr, "CURRENT_G"); + + Register currentGoroutineReg = goRegInfo.getCurrentGoroutineRegister(); + if (currentGoroutineReg != null && txtMemblock != null) { + // currentGoroutineReg is set in a platform's arch-golang.register.info in + // the element for arch's that have a dedicated processor + // register that points at G + try { + program.getProgramContext() + .setValue(currentGoroutineReg, txtMemblock.getStart(), + txtMemblock.getEnd(), gAddr.getOffsetAsBigInteger()); + } + catch (ContextChangeException e) { + Msg.error(this, "Unexpected Error", e); + } + } + } + if (mStruct != null) { + Address mAddr = contextMemoryAddr.add(mStructOffset); + programContext.markupAddressIfUndefined(mAddr, mStruct); + } + } + + private void createBootstrapGDT(GoRttiContext programContext, Program program, + TaskMonitor monitor) throws IOException { + GoVer goVer = programContext.getGolangVersion(); + String osName = GoRttiContext.getGolangOSString(program); + String gdtFilename = + GoRttiContext.getGDTFilename(goVer, programContext.getPtrSize(), osName); + gdtFilename = + gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis())); + File gdt = new File(System.getProperty("user.home"), gdtFilename); + programContext.exportTypesToGDT(gdt, monitor); + Msg.info(this, "Golang bootstrap GDT created: " + gdt); + } + + @Override + public void analysisEnded(Program program) { + } + + @Override + public boolean canAnalyze(Program program) { + return "golang".equals( + program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); + } + + @Override + public boolean getDefaultEnablement(Program program) { + return true; + } + + private static Address getArtificalZerobaseAddress(Program program) { + Symbol zerobaseSym = + SymbolUtilities.getUniqueSymbol(program, ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME); + return zerobaseSym != null ? zerobaseSym.getAddress() : null; + } + + /** + * Return the address of the golang zerobase symbol, or an artificial substitute. + *

+ * The zerobase symbol is used as the location of parameters that are zero-length. + * + * @param prog + * @return + */ + public static Address getZerobaseAddress(Program prog) { + Symbol zerobaseSym = SymbolUtilities.getUniqueSymbol(prog, "runtime.zerobase"); + Address zerobaseAddr = zerobaseSym != null + ? zerobaseSym.getAddress() + : getArtificalZerobaseAddress(prog); + if (zerobaseAddr == null) { + zerobaseAddr = prog.getImageBase().getAddressSpace().getMinAddress(); // ICKY HACK + Msg.warn(GoFunctionFixup.class, + "Unable to find Golang runtime.zerobase, using " + zerobaseAddr); + } + return zerobaseAddr; + } + + //-------------------------------------------------------------------------------------------- + private static class GolangAnalyzerOptions { + static final String CREATE_BOOTSTRAP_GDT_OPTIONNAME = "Create Bootstrap GDT"; + static final String CREATE_BOOTSTRAP_GDT_DESC = """ + Creates a Ghidra data type archive that contains just the necessary \ + data types to parse other golang binaries. \ + DWARF data is needed for this to succeed. \ + The new GDT file will be placed in the user's home directory and will \ + be called golang_MajorVer.MinorVer_XXbit_osname.NNNNNNNNN.gdt, where NNNNNN \ + is a timestamp."""; + boolean createBootstrapDatatypeArchive; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NonReturningFunctionNames.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NonReturningFunctionNames.java index 6749b61860..a77a3bad05 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NonReturningFunctionNames.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/NonReturningFunctionNames.java @@ -15,11 +15,12 @@ */ package ghidra.app.plugin.core.analysis; -import java.io.FileNotFoundException; -import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.io.FileNotFoundException; +import java.io.IOException; + import org.xml.sax.SAXException; import generic.constraint.DecisionSet; @@ -30,7 +31,7 @@ import ghidra.util.Msg; import ghidra.util.constraint.ProgramDecisionTree; import ghidra.xml.XmlParseException; -class NonReturningFunctionNames { +public class NonReturningFunctionNames { private static final String CONSTRAINED_FILENAME_PROPERTY = "functionNamesFile"; private static final String DATA_DIR = "data"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/SharedReturnAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/SharedReturnAnalyzer.java index e261d446e4..01e551da70 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/SharedReturnAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/SharedReturnAnalyzer.java @@ -20,8 +20,9 @@ import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.lang.*; -import ghidra.program.model.listing.*; +import ghidra.program.model.lang.GhidraLanguagePropertyKeys; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -86,6 +87,9 @@ public class SharedReturnAnalyzer extends AbstractAnalyzer { boolean sharedReturnEnabled = language.getPropertyAsBoolean( GhidraLanguagePropertyKeys.ENABLE_SHARED_RETURN_ANALYSIS, true); + if ("golang".equals(program.getCompilerSpec().getCompilerSpecID().toString())) { + sharedReturnEnabled = false; + } // If the language (in the .pspec file) overrides this setting, use that value boolean contiguousFunctionsEnabled = language.getPropertyAsBoolean( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java index ff58bd0e52..7fe38e2dfe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProgramStartingLocationOptions.java @@ -60,7 +60,7 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener { "a newly discovered starting symbol, provided the user hasn't manually moved."; private static final String DEFAULT_STARTING_SYMBOLS = - "main, WinMain, libc_start_main, WinMainStartup, start, entry"; + "main, WinMain, libc_start_main, WinMainStartup, start, entry, main.main"; public static enum StartLocationType { LOWEST_ADDRESS("Lowest Address"), diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java index 395dadb12f..19a226f915 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java @@ -98,9 +98,9 @@ public class BinaryReader { T get(InputStream is) throws IOException; } - private final ByteProvider provider; - private DataConverter converter; - private long currentIndex; + protected final ByteProvider provider; + protected DataConverter converter; + protected long currentIndex; /** * Constructs a reader using the given ByteProvider and endian-order. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java index a4794fe773..938eef22e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/MemoryByteProvider.java @@ -151,7 +151,7 @@ public class MemoryByteProvider implements ByteProvider { * @param maxAddress the highest address accessible by this provider (inclusive), or null * if there is no memory */ - private MemoryByteProvider(Memory memory, Address baseAddress, Address maxAddress) { + public MemoryByteProvider(Memory memory, Address baseAddress, Address maxAddress) { this.memory = memory; this.baseAddress = baseAddress; this.maxOffset = maxAddress != null diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java index dd7d79d606..ef0eacc14a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DIEAggregate.java @@ -17,9 +17,8 @@ package ghidra.app.util.bin.format.dwarf4; import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_formal_parameter; -import java.util.*; - import java.io.IOException; +import java.util.*; import org.apache.commons.lang3.ArrayUtils; @@ -301,6 +300,32 @@ public class DIEAggregate { return null; } + /** + * Return an attribute that is present in this {@link DIEAggregate}, or in any of its + * direct children (of a specific type) + * + * @param + * @param attribute the attribute to find + * @param childTag the type of children to search + * @param clazz type of the attribute to return + * @return attribute value, or null if not found + */ + public T findAttributeInChildren(int attribute, int childTag, + Class clazz) { + T attributeValue = getAttribute(attribute, clazz); + if (attributeValue != null) { + return attributeValue; + } + for (DebugInfoEntry childDIE : getChildren(childTag)) { + DIEAggregate childDIEA = getProgram().getAggregate(childDIE); + attributeValue = childDIEA.getAttribute(attribute, clazz); + if (attributeValue != null) { + return attributeValue; + } + } + return null; + } + /** * Finds a {@link DWARFAttributeValue attribute} with a matching {@link DWARFAttribute} type *

@@ -620,11 +645,13 @@ public class DIEAggregate { * Blob attributes are treated as a single location record for the current CU, using the * blob bytes as the DWARF expression of the location record. *

- * @param attribute - * @return + * @param attribute the attribute to evaluate + * @param range the address range the location covers (may be discarded if the attribute + * value is a location list with its own range values) + * @return list of locations, empty if missing, never null * @throws IOException */ - public List getAsLocation(int attribute) throws IOException { + public List getAsLocation(int attribute, DWARFRange range) throws IOException { AttrInfo attrInfo = findAttribute(attribute); if (attrInfo == null) { return List.of(); @@ -633,7 +660,7 @@ public class DIEAggregate { return readDebugLocList(dnum.getUnsignedValue()); } else if (attrInfo.attr instanceof DWARFBlobAttribute dblob) { - return _exprBytesAsLocation(dblob); + return _exprBytesAsLocation(dblob, range); } else { throw new UnsupportedOperationException( @@ -727,22 +754,8 @@ public class DIEAggregate { return results; } - private List _exprBytesAsLocation(DWARFBlobAttribute attr) { - List list = new ArrayList<>(1); - - Number highPc = getCompilationUnit().getCompileUnit().getHighPC(); - Number lowPc = getCompilationUnit().getCompileUnit().getLowPC(); - if (highPc == null) { - // a DW_AT_high_pc is not required - // in this case presumably we don't have to choose from a location list based on range - // Make a 1-byte range - highPc = lowPc; - } - // If there is no low either, assume we don't need a range, and make a (0,0) range - DWARFRange range = (lowPc == null) ? new DWARFRange(0, 0) - : new DWARFRange(lowPc.longValue(), highPc.longValue()); - list.add(new DWARFLocation(range, attr.getBytes())); - return list; + private List _exprBytesAsLocation(DWARFBlobAttribute attr, DWARFRange range) { + return List.of(new DWARFLocation(range, attr.getBytes())); } /** @@ -997,8 +1010,9 @@ public class DIEAggregate { } } if ( !params.isEmpty() ) { - Msg.warn(this, "Extra params in concrete DIE instance: " + params); - Msg.warn(this, this.toString()); + //Msg.warn(this, "Extra params in concrete DIE instance: " + params); + //Msg.warn(this, this.toString()); + newParams.addAll(params); } params = newParams; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLocation.java index 023c04bfce..f37b4e14ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLocation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFLocation.java @@ -15,6 +15,8 @@ */ package ghidra.app.util.bin.format.dwarf4; +import java.util.List; + public class DWARFLocation { private DWARFRange addressRange; private byte[] location; @@ -37,6 +39,39 @@ public class DWARFLocation { return this.location; } + /** + * Get the location that corresponds to the entry point of the function If + * there is only a single location, assume it applies to whole function + * + * @param locList + * @param funcAddr + * @return the byte array corresponding to the location expression + */ + public static DWARFLocation getTopLocation(List locList, long funcAddr) { + if (locList.size() == 1) { + return locList.get(0); + } + for (DWARFLocation loc : locList) { + if (loc.getRange().getFrom() == funcAddr) { + return loc; + } + } + return null; + } + + public static DWARFLocation getEntryLocation(List locList, long funcAddr) { + for (DWARFLocation loc : locList) { + if (loc.getRange().getFrom() == funcAddr) { + return loc; + } + } + return null; + } + + public static DWARFLocation getFirstLocation(List locList) { + return !locList.isEmpty() ? locList.get(0) : null; + } + /* * I know we frown on keeping large chunks of code around that have been commented * out, but... diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFRange.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFRange.java index df5b3b6a96..d764c6a8fd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFRange.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFRange.java @@ -19,6 +19,7 @@ package ghidra.app.util.bin.format.dwarf4; * Holds the start (inclusive) and end (exclusive) addresses of a range. */ public class DWARFRange implements Comparable { + public static final DWARFRange EMPTY = new DWARFRange(0, 1); private final long start; private final long end; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFUtil.java index 393716f2fc..8462f7936e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/DWARFUtil.java @@ -23,6 +23,8 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import generic.jar.ResourceFile; +import ghidra.app.cmd.comments.AppendCommentCmd; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeValue; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; @@ -30,9 +32,13 @@ import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.listing.Program; +import ghidra.program.database.data.DataTypeUtilities; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.Varnode; import ghidra.program.model.symbol.SymbolType; import ghidra.util.Conv; @@ -451,6 +457,38 @@ public class DWARFUtil { dtc.setComment(prev + description); } + public static void appendComment(Program program, Address address, int commentType, + String prefix, String comment, String sep) { + if (comment == null || comment.isBlank()) { + return; + } + CodeUnit cu = getCodeUnitForComment(program, address); + if (cu != null) { + String existingComment = cu.getComment(commentType); + if (existingComment != null && existingComment.contains(comment)) { + // don't add same comment twice + return; + } + } + AppendCommentCmd cmd = new AppendCommentCmd(address, commentType, + Objects.requireNonNullElse(prefix, "") + comment, sep); + cmd.applyTo(program); + } + + public static CodeUnit getCodeUnitForComment(Program program, Address address) { + Listing listing = program.getListing(); + CodeUnit cu = listing.getCodeUnitContaining(address); + if (cu == null) { + return null; + } + Address cuAddr = cu.getMinAddress(); + if (cu instanceof Data && !address.equals(cuAddr)) { + Data data = (Data) cu; + return data.getPrimitiveAt((int) address.subtract(cuAddr)); + } + return cu; + } + /** * Read an offset value who's size depends on the DWARF format: 32 vs 64. *

@@ -558,16 +596,41 @@ public class DWARFUtil { // A DW_AT_object_pointer property in the parent function is an explict way of // referencing the param that points to the object instance (ie. "this"). // + String paramName = paramDIEA.getName(); if (paramDIEA.getBool(DWARFAttribute.DW_AT_artificial, false) || - "this".equals(paramDIEA.getName())) { + Function.THIS_PARAM_NAME.equals(paramName)) { return true; } + DIEAggregate funcDIEA = paramDIEA.getParent(); DWARFAttributeValue dwATObjectPointer = - paramDIEA.getParent().getAttribute(DWARFAttribute.DW_AT_object_pointer); - return dwATObjectPointer != null && - dwATObjectPointer instanceof DWARFNumericAttribute dnum && - paramDIEA.hasOffset(dnum.getUnsignedValue()); + funcDIEA.getAttribute(DWARFAttribute.DW_AT_object_pointer); + if (dwATObjectPointer != null && dwATObjectPointer instanceof DWARFNumericAttribute dnum && + paramDIEA.hasOffset(dnum.getUnsignedValue())) { + return true; + } + + // If the variable is not named, check to see if the parent of the function + // is a struct/class, and the parameter points to it + DIEAggregate classDIEA = funcDIEA.getParent(); + if (paramName == null && classDIEA != null && classDIEA.isStructureType()) { + // Check to see if the parent data type equals the parameters' data type + return isPointerTo(classDIEA, paramDIEA.getTypeRef()); + } + + return false; + } + + public static boolean isPointerTo(DIEAggregate targetDIEA, DIEAggregate testDIEA) { + return testDIEA != null && testDIEA.getTag() == DWARFTag.DW_TAG_pointer_type && + testDIEA.getTypeRef() == targetDIEA; + } + + public static boolean isPointerDataType(DIEAggregate diea) { + while (diea.getTag() == DWARFTag.DW_TAG_typedef) { + diea = diea.getTypeRef(); + } + return diea.getTag() == DWARFTag.DW_TAG_pointer_type; } /** @@ -633,7 +696,7 @@ public class DWARFUtil { // it writes a raw 64bit long (BE). The upper 32 bits (already read as length) will // always be 0 since super-large binaries from that system weren't really possible. // The next 32 bits will be the remainder of the value. - if ( reader.isBigEndian() && program.getDefaultPointerSize() == 8) { + if (reader.isBigEndian() && program.getDefaultPointerSize() == 8) { length = reader.readNextUnsignedInt(); format = DWARFCompilationUnit.DWARF_64; } @@ -649,4 +712,151 @@ public class DWARFUtil { return new LengthResult(length, format); } + /** + * Returns a file that has been referenced in the specified {@link Language language's} + * ldefs description via a + *

<external_name tool="name" name="value"/>
+ * entry. + * + * @param lang {@link Language} to query + * @param name name of the option in the ldefs file + * @return file pointed to by the specified external_name tool entry + * @throws IOException + */ + public static ResourceFile getLanguageExternalFile(Language lang, String name) + throws IOException { + String filename = getLanguageExternalNameValue(lang, name); + return filename != null + ? new ResourceFile(getLanguageDefinitionDirectory(lang), filename) + : null; + } + + /** + * Returns the base directory of a language definition. + * + * @param lang {@link Language} to get base definition directory + * @return base directory for language definition files + * @throws IOException + */ + public static ResourceFile getLanguageDefinitionDirectory(Language lang) throws IOException { + LanguageDescription langDesc = lang.getLanguageDescription(); + if (!(langDesc instanceof SleighLanguageDescription)) { + throw new IOException("Not a Sleigh Language: " + lang.getLanguageID()); + } + SleighLanguageDescription sld = (SleighLanguageDescription) langDesc; + ResourceFile defsFile = sld.getDefsFile(); + ResourceFile parentFile = defsFile.getParentFile(); + return parentFile; + } + + /** + * Returns a value specified in a {@link Language} definition via a + *
<external_name tool="name" name="value"/>
+ * entry. + *

+ * @param lang {@link Language} to query + * @param name name of the value + * @return String value + * @throws IOException + */ + public static String getLanguageExternalNameValue(Language lang, String name) + throws IOException { + LanguageDescription langDesc = lang.getLanguageDescription(); + if (!(langDesc instanceof SleighLanguageDescription)) { + throw new IOException("Not a Sleigh Language: " + lang.getLanguageID()); + } + List values = langDesc.getExternalNames(name); + if (values == null || values.isEmpty()) { + return null; + } + if (values.size() > 1) { + throw new IOException( + String.format("Multiple external name values for %s found in language %s", name, + lang.getLanguageID())); + } + return values.get(0); + } + + public static void packCompositeIfPossible(Composite original, DataTypeManager dtm) { + if (original.isZeroLength() || original.getNumComponents() == 0) { + // don't try to pack empty structs, this would throw off conflicthandler logic. + // also don't pack sized structs with no fields because when packed down to 0 bytes they + // cause errors when used as a param type + return; + } + + Composite copy = (Composite) original.copy(dtm); + copy.setToDefaultPacking(); + if (copy.getLength() != original.getLength()) { + // so far, typically because trailing zero-len flex array caused toolchain to + // bump struct size to next alignment value in a way that doesn't mesh with ghidra's + // logic + return; // fail + } + + DataTypeComponent[] preComps = original.getDefinedComponents(); + DataTypeComponent[] postComps = copy.getDefinedComponents(); + if (preComps.length != postComps.length) { + return; // fail + } + for (int index = 0; index < preComps.length; index++) { + DataTypeComponent preDTC = preComps[index]; + DataTypeComponent postDTC = postComps[index]; + if (preDTC.getOffset() != postDTC.getOffset() || + preDTC.getLength() != postDTC.getLength() || + preDTC.isBitFieldComponent() != postDTC.isBitFieldComponent()) { + return; // fail + } + if (preDTC.isBitFieldComponent()) { + BitFieldDataType preBFDT = (BitFieldDataType) preDTC.getDataType(); + BitFieldDataType postBFDT = (BitFieldDataType) postDTC.getDataType(); + if (preBFDT.getBitOffset() != postBFDT.getBitOffset() || + preBFDT.getBitSize() != postBFDT.getBitSize()) { + return; // fail + } + } + } + + original.setToDefaultPacking(); + } + + public static List convertRegisterListToVarnodeStorage(List registers, + int dataTypeSize) { + List results = new ArrayList<>(); + for (Register reg : registers) { + int regSize = reg.getMinimumByteSize(); + int bytesUsed = Math.min(dataTypeSize, regSize); + Address addr = reg.getAddress(); + if (reg.isBigEndian() && bytesUsed < regSize) { + addr = addr.add(regSize - bytesUsed); + } + results.add(new Varnode(addr, bytesUsed)); + dataTypeSize -= bytesUsed; + } + return results; + } + + public static boolean isEmptyArray(DataType dt) { + return dt instanceof Array array && array.getNumElements() == 0; + } + + public static boolean isZeroByteDataType(DataType dt) { + if (VoidDataType.dataType.isEquivalent(dt)) { + return true; + } + if (!dt.isZeroLength() && dt instanceof Array) { + dt = DataTypeUtilities.getArrayBaseDataType((Array) dt); + } + return dt.isZeroLength(); + } + + public static boolean isVoid(DataType dt) { + return VoidDataType.dataType.isEquivalent(dt); + } + + public static boolean isStackVarnode(Varnode varnode) { + return varnode != null && + varnode.getAddress().getAddressSpace().getType() == AddressSpace.TYPE_STACK; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/encoding/DWARFAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/encoding/DWARFAttribute.java index df99a4f544..9048590952 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/encoding/DWARFAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/encoding/DWARFAttribute.java @@ -157,6 +157,16 @@ public final class DWARFAttribute { public static final int DW_AT_GNU_pubtypes = 0x2135; // end GNU DebugFission + // Golang + public static final int DW_AT_go_kind = 0x2900; + public static final int DW_AT_go_key = 0x2901; + public static final int DW_AT_go_elem = 0x2902; + public static final int DW_AT_go_embedded_field = 0x2903; + public static final int DW_AT_go_runtime_type = 0x2904; + public static final int DW_AT_go_package_name = 0x2905; + public static final int DW_AT_go_dict_index = 0x2906; + // end Golang + // Apple proprietary tags public static final int DW_AT_APPLE_ptrauth_key = 0x3e04; public static final int DW_AT_APPLE_ptrauth_address_discriminated = 0x3e05; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpressionEvaluator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpressionEvaluator.java index 7cfebc73b1..580d02d5f8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpressionEvaluator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/expression/DWARFExpressionEvaluator.java @@ -277,6 +277,8 @@ public class DWARFExpressionEvaluator { else { useUnknownRegister = true; if (offset == 0) { + // if offset is 0, we can represent the location as a ghidra register location + // also implies a deref by the user of this location info registerLoc = true; } } @@ -366,6 +368,7 @@ public class DWARFExpressionEvaluator { case DW_OP_call_frame_cfa: { push(registerMappings.getCallFrameCFA()); + lastStackRelative = true; break; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java new file mode 100644 index 0000000000..9839e054b4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/DWARFFunctionFixup.java @@ -0,0 +1,63 @@ +/* ### + * 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.dwarf4.funcfixup; + +import java.util.List; + +import ghidra.app.util.bin.format.dwarf4.DWARFException; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.program.model.listing.Function; +import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * Interface for add-in logic to fix/modify/tweak DWARF functions before they are written + * to the Ghidra program. + *

+ * Use {@code @ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_*)} to + * control the order of evaluation (higher numbers are run earlier). + *

+ * Fixups are found using {@link ClassSearcher}, and their class names must end + * in "DWARFFunctionFixup" (see ExtensionPoint.manifest). + */ +public interface DWARFFunctionFixup extends ExtensionPoint { + public static final int PRIORITY_NORMAL_EARLY = 4000; + public static final int PRIORITY_NORMAL = 3000; + public static final int PRIORITY_NORMAL_LATE = 2000; + public static final int PRIORITY_LAST = 1000; + + /** + * Called before a {@link DWARFFunction} is used to create a Ghidra Function. + *

+ * If processing of the function should terminate (and the function be skipped), throw + * a {@link DWARFException}. + * + * @param dfunc {@link DWARFFunction} info read from DWARF about the function + * @param gfunc the Ghidra {@link Function} that will receive the DWARF information + */ + void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException; + + /** + * Return a list of all current {@link DWARFFunctionFixup fixups} found in the classpath + * by ClassSearcher. + * + * @return list of all current fixups found in the classpath + */ + public static List findFixups() { + return ClassSearcher.getInstances(DWARFFunctionFixup.class); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java new file mode 100644 index 0000000000..6532a36d2d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/OutputParamCheckDWARFFunctionFixup.java @@ -0,0 +1,42 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; +import ghidra.program.model.listing.Function; +import ghidra.util.Msg; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Complains about function parameters that are marked as 'output' and don't have storage + * locations. + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE) +public class OutputParamCheckDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + // Complain about parameters that are marked as 'output' that haven't been handled by + // some other fixup, as we don't know what to do with them. + for (DWARFVariable dvar : dfunc.params) { + if (dvar.isOutputParameter && dvar.isMissingStorage()) { + Msg.warn(this, String.format("Unsupported output parameter for %s@%s: %s", + gfunc.getName(), gfunc.getEntryPoint(), dvar.name.getName())); + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java new file mode 100644 index 0000000000..485c4ddb87 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamNameDWARFFunctionFixup.java @@ -0,0 +1,52 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.next.*; +import ghidra.program.model.listing.Function; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Ensures that function parameter names are unique and valid + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_LAST) +public class ParamNameDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + + // Fix any dups among the parameters, to-be-added-local vars, and already present local vars + NameDeduper nameDeduper = new NameDeduper(); + nameDeduper.addReservedNames(dfunc.getAllParamNames()); + nameDeduper.addReservedNames(dfunc.getAllLocalVariableNames()); + nameDeduper.addUsedNames(dfunc.getNonParamSymbolNames(gfunc)); + + for (DWARFVariable param : dfunc.params) { + String newName = nameDeduper.getUniqueName(param.name.getName()); + if (newName != null) { + param.name = param.name.replaceName(newName, param.name.getOriginalName()); + } + } + + for (DWARFVariable localVar : dfunc.localVars) { + String newName = nameDeduper.getUniqueName(localVar.name.getName()); + if (newName != null) { + localVar.name = localVar.name.replaceName(newName, localVar.name.getOriginalName()); + } + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java new file mode 100644 index 0000000000..3c08c7c021 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ParamSpillDWARFFunctionFixup.java @@ -0,0 +1,54 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; +import ghidra.program.model.listing.Function; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Steal storage location from parameters that are defined in a function's local variable + * area, because the storage location isn't the parameter location during call, but its location + * after being spilled. + * + * Create a local variable at that storage location. + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE) +public class ParamSpillDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + for (DWARFVariable param : dfunc.params) { + if (!param.isStackStorage()) { + continue; + } + long paramStackOffset = param.getStackOffset(); + if (dfunc.isInLocalVarStorageArea(paramStackOffset) && + dfunc.getLocalVarByOffset(paramStackOffset) == null) { + + DWARFVariable paramSpill = DWARFVariable.fromDataType(dfunc, param.type); + String paramName = param.name.getName(); + paramSpill.name = + param.name.replaceName(paramName + "-local", paramName + "-local"); + paramSpill.setStackStorage(paramStackOffset); + dfunc.localVars.add(paramSpill); + param.clearStorage(); + } + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java new file mode 100644 index 0000000000..0818db1470 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/RustDWARFFunctionFixup.java @@ -0,0 +1,43 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.DIEAggregate; +import ghidra.app.util.bin.format.dwarf4.DWARFException; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.program.model.listing.Function; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Prevent functions in a Rust compile unit from incorrectly being locked down to an empty signature. + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY) +public class RustDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException { + DIEAggregate diea = dfunc.diea; + int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage(); + if (cuLang == DWARFSourceLanguage.DW_LANG_Rust && dfunc.params.isEmpty()) { + // if there were no defined parameters and the language is Rust, don't force an + // empty param signature. Rust language emit dwarf info without types (signatures) + // when used without -g. + throw new DWARFException("Rust empty param list" /* string doesnt matter */); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java new file mode 100644 index 0000000000..c6276d70c3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/SanityCheckDWARFFunctionFixup.java @@ -0,0 +1,45 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.DWARFException; +import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeValue; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.program.model.listing.Function; +import ghidra.util.Msg; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Check for errors and prevent probable bad function info from being locked in + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE) +public class SanityCheckDWARFFunctionFixup implements DWARFFunctionFixup, DWARFAttributeValue { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException { + // if there were no defined parameters and we had problems decoding local variables, + // don't force the method to have an empty param signature because there are other + // issues afoot. + if (dfunc.params.isEmpty() && dfunc.localVarErrors) { + Msg.error(this, + String.format( + "Inconsistent function signature information, leaving undefined: %s@%s", + gfunc.getName(), gfunc.getEntryPoint())); + throw new DWARFException("Failed sanity check"); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java new file mode 100644 index 0000000000..4e0a97e9f5 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/StorageVerificationDWARFFunctionFixup.java @@ -0,0 +1,55 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; +import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; +import ghidra.program.model.listing.Function; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Downgrades the function's signature commit mode to FORMAL-param-info-only if there are + * problems with param storage info. + *

+ * Does not check the function's return value storage as that typically won't have information + * because DWARF does not specify that. + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_LAST - 1) +public class StorageVerificationDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + boolean storageIsGood = true; + for (DWARFVariable param : dfunc.params) { + if (param.isMissingStorage() && !param.isZeroByte()) { + storageIsGood = false; + break; + } + + // downgrade to formal if the location info for the param starts somewhere inside the + // function instead of at the entry point + if (!param.isLocationValidOnEntry()) { + storageIsGood = false; + break; + } + } + if (!storageIsGood) { + dfunc.signatureCommitMode = CommitMode.FORMAL; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java new file mode 100644 index 0000000000..0d739babb7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/funcfixup/ThisCallingConventionDWARFFunctionFixup.java @@ -0,0 +1,53 @@ +/* ### + * 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.dwarf4.funcfixup; + +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction; +import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable; +import ghidra.program.model.data.GenericCallingConvention; +import ghidra.program.model.listing.Function; +import ghidra.util.Msg; +import ghidra.util.classfinder.ExtensionPointProperties; + +/** + * Update the function's calling convention (if unset) if there is a "this" parameter. + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL) +public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFixup { + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + if (dfunc.params.isEmpty() || dfunc.callingConvention != null) { + // if someone else set calling convention, don't override it + return; + } + + DWARFVariable firstParam = dfunc.params.get(0); + if (firstParam.isThis) { + if (!firstParam.name.isAnon() && + !Function.THIS_PARAM_NAME.equals(firstParam.name.getOriginalName())) { + Msg.error(this, + String.format("WARNING: Renaming %s to %s in function %s@%s", + firstParam.name.getName(), Function.THIS_PARAM_NAME, gfunc.getName(), + gfunc.getEntryPoint())); + } + firstParam.name = + firstParam.name.replaceName(Function.THIS_PARAM_NAME, Function.THIS_PARAM_NAME); + dfunc.callingConvention = GenericCallingConvention.thiscall; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java new file mode 100644 index 0000000000..05e76383e7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataInstanceHelper.java @@ -0,0 +1,199 @@ +/* ### + * 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.dwarf4.next; + +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.*; + +/** + * Logic to test if a Data instance is replaceable with a data type. + */ +public class DWARFDataInstanceHelper { + private Program program; + private Listing listing; + + public DWARFDataInstanceHelper(Program program) { + this.program = program; + this.listing = program.getListing(); + } + + private boolean isArrayDataTypeCompatibleWithExistingData(Array arrayDT, Data existingData) { + + DataType existingDataDT = existingData.getBaseDataType(); + if (existingDataDT.isEquivalent(arrayDT)) { + return true; + } + + DataType elementDT = arrayDT.getDataType(); + if (elementDT instanceof TypeDef typedef) { + elementDT = typedef.getBaseDataType(); + } + + DataType existingElementDT = existingDataDT instanceof Array existingArrayDT + ? existingArrayDT.getDataType() + : null; + if (elementDT instanceof CharDataType && existingDataDT instanceof StringDataType) { + // hack to allow a char array to overwrite a string in memory + existingElementDT = elementDT; + } + if (existingElementDT instanceof TypeDef typedef) { + existingElementDT = typedef.getBaseDataType(); + } + + if (existingDataDT instanceof Array || existingDataDT instanceof StringDataType) { + if (!existingElementDT.isEquivalent(elementDT)) { + return false; + } + + if (arrayDT.getLength() <= existingData.getLength()) { + // if proposed array is smaller than in-memory array: ok + return true; + } + + // if proposed array is longer than in-memory array, check if there is only + // undefined data following the in-memory array + return hasTrailingUndefined(existingData, arrayDT); + } + + // existing data wasn't an array, test each location the proposed array would overwrite + Address address = existingData.getAddress(); + for (int i = 0; i < arrayDT.getNumElements(); i++) { + Address elementAddress = address.add(arrayDT.getElementLength() * i); + Data data = listing.getDataAt(elementAddress); + if (data != null && !isDataTypeCompatibleWithExistingData(elementDT, data)) { + return false; + } + } + + return true; + } + + private boolean hasTrailingUndefined(Data data, DataType replacementDT) { + Address address = data.getAddress(); + return DataUtilities.isUndefinedRange(program, address.add(data.getLength()), + address.add(replacementDT.getLength() - 1)); + } + + private boolean isStructDataTypeCompatibleWithExistingData(Structure structDT, + Data existingData) { + DataType existingDataDT = existingData.getBaseDataType(); + if (existingDataDT instanceof Structure) { + return existingDataDT.isEquivalent(structDT); + } + + // existing data wasn't a structure, test each location the proposed structure would overwrite + Address address = existingData.getAddress(); + for (DataTypeComponent dtc : structDT.getDefinedComponents()) { + Address memberAddress = address.add(dtc.getOffset()); + Data data = listing.getDataAt(memberAddress); + if (data != null && !isDataTypeCompatibleWithExistingData(dtc.getDataType(), data)) { + return false; + } + } + return true; + } + + private boolean isPointerDataTypeCompatibleWithExistingData(Pointer pdt, Data existingData) { + DataType existingDT = existingData.getBaseDataType(); + + // allow 'upgrading' an integer type to a pointer + boolean isRightType = + (existingDT instanceof Pointer) || (existingDT instanceof AbstractIntegerDataType); + return isRightType && existingDT.getLength() == pdt.getLength(); + } + + private boolean isSimpleDataTypeCompatibleWithExistingData(DataType simpleDT, + Data existingData) { + // dataType will only be a base data type, not a typedef + + DataType existingDT = existingData.getBaseDataType(); + if (simpleDT instanceof CharDataType && existingDT instanceof StringDataType) { + // char overwriting a string + return true; + } + + if (!simpleDT.getClass().isInstance(existingDT)) { + return false; + } + int dataTypeLen = simpleDT.getLength(); + if (dataTypeLen > 0 && dataTypeLen != existingData.getLength()) { + return false; + } + return true; + } + + private boolean isEnumDataTypeCompatibleWithExistingData(Enum enumDT, Data existingData) { + // This is a very fuzzy check to see if the value located at address is compatible. + // Match if its an enum or integer with correct size. The details about enum + // members are ignored. + DataType existingDT = existingData.getBaseDataType(); + if (!(existingDT instanceof Enum || existingDT instanceof AbstractIntegerDataType)) { + return false; + } + if (existingDT instanceof BooleanDataType) { + return false; + } + if (existingDT.getLength() != enumDT.getLength()) { + return false; + } + return true; + } + + private boolean isDataTypeCompatibleWithExistingData(DataType dataType, Data existingData) { + if (existingData == null || !existingData.isDefined()) { + return true; + } + + if (dataType instanceof Array) { + return isArrayDataTypeCompatibleWithExistingData((Array) dataType, existingData); + } + if (dataType instanceof Pointer) { + return isPointerDataTypeCompatibleWithExistingData((Pointer) dataType, existingData); + } + if (dataType instanceof Structure) { + return isStructDataTypeCompatibleWithExistingData((Structure) dataType, existingData); + } + if (dataType instanceof TypeDef) { + return isDataTypeCompatibleWithExistingData(((TypeDef) dataType).getBaseDataType(), + existingData); + } + if (dataType instanceof Enum) { + return isEnumDataTypeCompatibleWithExistingData((Enum) dataType, existingData); + } + + if (dataType instanceof AbstractIntegerDataType || + dataType instanceof AbstractFloatDataType || dataType instanceof StringDataType || + dataType instanceof WideCharDataType || dataType instanceof WideChar16DataType || + dataType instanceof WideChar32DataType) { + return isSimpleDataTypeCompatibleWithExistingData(dataType, existingData); + } + + return false; + } + + public boolean isDataTypeCompatibleWithAddress(DataType dataType, Address address) { + if (DataUtilities.isUndefinedRange(program, address, + address.add(dataType.getLength() - 1))) { + return true; + } + + Data data = listing.getDataAt(address); + return data == null || isDataTypeCompatibleWithExistingData(dataType, data); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeConflictHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeConflictHandler.java index f6a4228f34..2429b8be40 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeConflictHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeConflictHandler.java @@ -55,9 +55,9 @@ import ghidra.util.SystemUtilities; * structures. * */ -class DWARFDataTypeConflictHandler extends DataTypeConflictHandler { +public class DWARFDataTypeConflictHandler extends DataTypeConflictHandler { - static final DWARFDataTypeConflictHandler INSTANCE = new DWARFDataTypeConflictHandler(); + public static final DWARFDataTypeConflictHandler INSTANCE = new DWARFDataTypeConflictHandler(); private DWARFDataTypeConflictHandler() { // do not create instances of this class diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java index 81b0d4bf1f..5cf87925a9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporter.java @@ -27,8 +27,9 @@ import org.apache.commons.lang3.StringUtils; import ghidra.app.util.DataTypeNamingUtil; import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; -import ghidra.app.util.bin.format.dwarf4.encoding.*; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFEndianity; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; +import ghidra.app.util.bin.format.golang.rtti.types.GoKind; import ghidra.program.database.DatabaseObject; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.data.*; @@ -87,12 +88,11 @@ public class DWARFDataTypeImporter { * @param dwarfDTM {@link DWARFDataTypeManager} helper * @param importOptions {@link DWARFImportOptions} control optional features during import */ - public DWARFDataTypeImporter(DWARFProgram prog, DWARFDataTypeManager dwarfDTM, - DWARFImportOptions importOptions) { + public DWARFDataTypeImporter(DWARFProgram prog, DWARFDataTypeManager dwarfDTM) { this.prog = prog; this.dataTypeManager = prog.getGhidraProgram().getDataTypeManager(); this.dwarfDTM = dwarfDTM; - this.importOptions = importOptions; + this.importOptions = prog.getImportOptions(); this.voidDDT = new DWARFDataType(dwarfDTM.getVoidType(), DWARFNameInfo.fromDataType(dwarfDTM.getVoidType()), -1); } @@ -109,7 +109,7 @@ public class DWARFDataTypeImporter { //Msg.warn(this, "Allowed recursive loop in datatype detected at " + id); break; case 3: - Msg.error(this, "Recursive loop in datatype detected at " + id); + Msg.error(this, "Recursive loop in datatype detected at " + Long.toHexString(id)); return false; } recursionTrackingOffsetToLoopCount.put(id, count); @@ -155,39 +155,39 @@ public class DWARFDataTypeImporter { // Fall back to creating a new Ghidra DataType from the info in the DIEA. switch (diea.getTag()) { - case DWARFTag.DW_TAG_pointer_type: - case DWARFTag.DW_TAG_reference_type: - case DWARFTag.DW_TAG_rvalue_reference_type: + case DW_TAG_pointer_type: + case DW_TAG_reference_type: + case DW_TAG_rvalue_reference_type: result = makeDataTypeForPointer(diea); break; - case DWARFTag.DW_TAG_ptr_to_member_type: + case DW_TAG_ptr_to_member_type: result = makeDataTypeForPtrToMemberType(diea); break; - case DWARFTag.DW_TAG_base_type: + case DW_TAG_base_type: result = makeDataTypeForBaseType(diea); break; - case DWARFTag.DW_TAG_typedef: + case DW_TAG_typedef: result = makeDataTypeForTypedef(diea); break; - case DWARFTag.DW_TAG_unspecified_type: + case DW_TAG_unspecified_type: result = makeDataTypeForUnspecifiedType(diea); break; - case DWARFTag.DW_TAG_const_type: - case DWARFTag.DW_TAG_volatile_type: - case DWARFTag.DW_TAG_restrict_type: - case DWARFTag.DW_TAG_shared_type: - case DWARFTag.DW_TAG_APPLE_ptrauth_type: + case DW_TAG_const_type: + case DW_TAG_volatile_type: + case DW_TAG_restrict_type: + case DW_TAG_shared_type: + case DW_TAG_APPLE_ptrauth_type: result = makeDataTypeForConst(diea); break; - case DWARFTag.DW_TAG_enumeration_type: + case DW_TAG_enumeration_type: result = makeDataTypeForEnum(diea); break; - case DWARFTag.DW_TAG_array_type: + case DW_TAG_array_type: result = makeDataTypeForArray(diea); break; - case DWARFTag.DW_TAG_structure_type: - case DWARFTag.DW_TAG_class_type: - case DWARFTag.DW_TAG_union_type: + case DW_TAG_structure_type: + case DW_TAG_class_type: + case DW_TAG_union_type: result = makeDataTypeForStruct(diea); // push partial datatype info into currentTypes mapping to handle @@ -197,10 +197,10 @@ public class DWARFDataTypeImporter { finishStruct(diea, result); break; - case DWARFTag.DW_TAG_subroutine_type: + case DW_TAG_subroutine_type: result = makeDataTypeForFunctionDefinition(diea, true); break; - case DWARFTag.DW_TAG_subprogram: + case DW_TAG_subprogram: result = makeDataTypeForFunctionDefinition(diea, false); break; default: @@ -287,12 +287,23 @@ public class DWARFDataTypeImporter { String paramName = paramDIEA.getName(); DWARFDataType paramDT = getDataType(paramDIEA.getTypeRef(), null); - if (paramDT == null || paramDT.dataType.getLength() <= 0) { + DataType dt = fixupDataTypeInconsistencies(paramDT); + + if (dt == null && DWARFUtil.isPointerDataType(paramDIEA.getTypeRef())) { + // Hack to handle Golang self-referencing func defs. + Msg.error(this, + "Error resolving parameter data type, probable recursive definition, replacing with void*: " + + dni.getName()); + Msg.debug(this, "Problem funcDef: " + diea.toString()); + Msg.debug(this, "Problem param: " + paramDIEA); + dt = dwarfDTM.getPtrTo(dwarfDTM.getVoidType()); + } + if (dt == null || dt.getLength() <= 0) { Msg.error(this, "Bad function parameter type for " + dni.asCategoryPath()); return null; } - ParameterDefinition pd = new ParameterDefinitionImpl(paramName, paramDT.dataType, null); + ParameterDefinition pd = new ParameterDefinitionImpl(paramName, dt, null); params.add(pd); foundThisParam |= DWARFUtil.isThisParam(paramDIEA); @@ -304,7 +315,7 @@ public class DWARFDataTypeImporter { funcDef.setNoReturn(diea.getBool(DW_AT_noreturn, false)); funcDef.setArguments(params.toArray(new ParameterDefinition[params.size()])); - if (!diea.getChildren(DWARFTag.DW_TAG_unspecified_parameters).isEmpty()) { + if (!diea.getChildren(DW_TAG_unspecified_parameters).isEmpty()) { funcDef.setVarArgs(true); } @@ -329,7 +340,17 @@ public class DWARFDataTypeImporter { updateMapping(origPD.getDataType(), newPD.getDataType()); } - return new DWARFDataType(funcDef, dni, diea.getOffset()); + DataType dtToAdd = funcDef; + if (diea.hasAttribute(DW_AT_byte_size)) { + // if the funcdef has a bytesize attribute, we should convert this data type to a ptr + long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1); + if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) { + ptrSize = -1;// use default pointer size + } + dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize); + } + + return new DWARFDataType(dtToAdd, dni, diea.getOffset()); } /** @@ -357,12 +378,38 @@ public class DWARFDataTypeImporter { "Warning: Base type bit size and bit offset not currently handled for data type %s, DIE %s" .formatted(dni.toString(), diea.getHexOffset())); } + boolean explictSize = false; + if (diea.hasAttribute(DW_AT_go_kind)) { + long goKindInt = diea.getLong(DW_AT_go_kind, 0); + GoKind kind = GoKind.parseByte((byte) goKindInt); + explictSize = isExplictSizedGolangType(kind); + } - DataType dt = - dwarfDTM.getBaseType(dni.getOriginalName(), dwarfSize, dwarfEncoding, isBigEndian); + DataType dt = dwarfDTM.getBaseType(dni.getOriginalName(), dwarfSize, dwarfEncoding, + isBigEndian, explictSize); return new DWARFDataType(dt, dni, diea.getOffset()); } + private boolean isExplictSizedGolangType(GoKind kind) { + switch (kind) { + case Int8: + case Int16: + case Int32: + case Int64: + case Uint8: + case Uint16: + case Uint32: + case Uint64: + case Float32: + case Float64: + case Complex64: + case Complex128: + return true; + default: + return false; + } + } + /** * Simple passthru, returns whatever type this "const" modifier applies to. *

@@ -396,7 +443,7 @@ public class DWARFDataTypeImporter { private DWARFDataType makeDataTypeForEnum(DIEAggregate diea) { DWARFNameInfo dni = prog.getName(diea); - int enumSize = (int) diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, -1); + int enumSize = (int) diea.getUnsignedLong(DW_AT_byte_size, -1); // in addition to byte_size, enums can have encoding (signed/unsigned) and a basetype, which // itself might have a signed/unsigned encoding. // Which attributes are present varies wildly between versions and vendors, so seems @@ -435,12 +482,12 @@ public class DWARFDataTypeImporter { private void populateStubEnum(Enum enumDT, DIEAggregate diea, boolean defaultSignedness) { // NOTE: gcc tends to emit values without an explicit signedness. The caller // can specify a default signedness, but this should probably always be unsigned. - for (DebugInfoEntry childEntry : diea.getChildren(DWARFTag.DW_TAG_enumerator)) { + for (DebugInfoEntry childEntry : diea.getChildren(DW_TAG_enumerator)) { DIEAggregate childDIEA = prog.getAggregate(childEntry); String valueName = childDIEA.getName(); DWARFNumericAttribute enumValAttr = childDIEA - .getAttribute(DWARFAttribute.DW_AT_const_value, DWARFNumericAttribute.class); + .getAttribute(DW_AT_const_value, DWARFNumericAttribute.class); if (enumValAttr != null) { long enumVal = enumValAttr.getValueWithSignednessHint(defaultSignedness); @@ -531,15 +578,15 @@ public class DWARFDataTypeImporter { DWARFNameInfo dni = prog.getName(diea); - long structSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, 0); + long structSize = diea.getUnsignedLong(DW_AT_byte_size, 0); long origStructSize = structSize; if (isStructTooBigForGhidra(structSize)) { Msg.error(this, "Large DWARF structure encountered, substituting empty struct for " + dni + ", size: " + Long.toString(structSize) + " at DIE " + diea.getHexOffset()); structSize = 0; } - boolean isUnion = diea.getTag() == DWARFTag.DW_TAG_union_type; - boolean isDecl = diea.getBool(DWARFAttribute.DW_AT_declaration, false); + boolean isUnion = diea.getTag() == DW_TAG_union_type; + boolean isDecl = diea.getBool(DW_AT_declaration, false); DataType struct = isUnion ? new UnionDataType(dni.getParentCP(), dni.getName(), dataTypeManager) @@ -617,19 +664,19 @@ public class DWARFDataTypeImporter { */ private void populateStubUnion(DWARFDataType ddt, DIEAggregate diea) throws IOException, DWARFExpressionException { - long unionSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, -1); + long unionSize = diea.getUnsignedLong(DW_AT_byte_size, -1); UnionDataType union = (UnionDataType) ddt.dataType; - for (DebugInfoEntry childEntry : diea.getChildren(DWARFTag.DW_TAG_member)) { + for (DebugInfoEntry childEntry : diea.getChildren(DW_TAG_member)) { DIEAggregate childDIEA = prog.getAggregate(childEntry); // skip static member vars as they do not have storage in the structure // C does not allow static member vars in unions - if (childDIEA.hasAttribute(DWARFAttribute.DW_AT_external)) { + if (childDIEA.hasAttribute(DW_AT_external)) { continue; } - int bitSize = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_size, -1); + int bitSize = childDIEA.parseInt(DW_AT_bit_size, -1); boolean isBitField = bitSize != -1; String memberName = childDIEA.getName(); @@ -644,13 +691,14 @@ public class DWARFDataTypeImporter { continue; } + DataType dt = fixupDataTypeInconsistencies(childDT); String memberComment = null; - if (childDT.dataType instanceof Dynamic || - childDT.dataType instanceof FactoryDataType) { - memberComment = "Unsupported dynamic size data type: " + childDT.dataType; - childDT.dataType = Undefined.getUndefinedDataType(1); + if (dt instanceof Dynamic || + dt instanceof FactoryDataType) { + memberComment = "Unsupported dynamic size data type: " + dt; + dt = Undefined.getUndefinedDataType(1); } - int dtLen = childDT.dataType.getLength(); + int dtLen = dt.getLength(); if (unionSize != -1 && !isBitField && dtLen > unionSize) { // if we can, ensure that the member being added to the union isn't larger // than what DWARF specifies. @@ -658,23 +706,23 @@ public class DWARFDataTypeImporter { if (dtLen > 1) { // replace problematic datatype with 1 byte undefined placeholder memberComment = - "Data type larger than union's declared size: " + childDT.dataType; - childDT.dataType = Undefined.getUndefinedDataType(1); + "Data type larger than union's declared size: " + dt; + dt = Undefined.getUndefinedDataType(1); } else { // can't do any fancy replacement, just add warning to union's description DWARFUtil.appendDescription(union, memberDesc("Missing member", - "data type larger than union", memberName, childDT, -1, bitSize, -1), "\n"); + "data type larger than union", memberName, dt, -1, bitSize, -1), "\n"); continue; } } if (isBitField) { - if (!BitFieldDataType.isValidBaseDataType(childDT.dataType)) { + if (!BitFieldDataType.isValidBaseDataType(dt)) { DWARFUtil.appendDescription(union, memberDesc("Missing member", - "Bad data type for bitfield: " + childDT.dataType.getName(), memberName, - childDT, -1, bitSize, -1), + "Bad data type for bitfield: " + dt.getName(), memberName, + dt, -1, bitSize, -1), "\n"); continue; } @@ -682,7 +730,7 @@ public class DWARFDataTypeImporter { // DWARF has attributes (DWARFAttribute.DW_AT_data_bit_offset, DWARFAttribute.DW_AT_bit_offset) // that specify the bit_offset of the field in the union. We don't use them. try { - union.addBitField(childDT.dataType, bitSize, memberName, memberComment); + union.addBitField(dt, bitSize, memberName, memberComment); } catch (InvalidDataTypeException e) { Msg.error(this, @@ -690,17 +738,17 @@ public class DWARFDataTypeImporter { union.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() + "], skipping: " + e.getMessage()); DWARFUtil.appendDescription(union, memberDesc("Missing member ", - "Failed to add bitfield", memberName, childDT, -1, bitSize, -1), "\n"); + "Failed to add bitfield", memberName, dt, -1, bitSize, -1), "\n"); } } else { // just a normal field try { DataTypeComponent dataTypeComponent = - union.add(childDT.dataType, memberName, memberComment); + union.add(dt, memberName, memberComment); // adding a member to a composite can cause a clone() of the datatype instance, so // update the instance mapping to keep track of the new instance. - updateMapping(childDT.dataType, dataTypeComponent.getDataType()); + updateMapping(dt, dataTypeComponent.getDataType()); } catch (IllegalArgumentException exc) { Msg.error(this, @@ -731,7 +779,7 @@ public class DWARFDataTypeImporter { ") is larger than DWARF value (" + unionSize + ")", "\n"); } if (importOptions.isTryPackStructs()) { - packCompositeIfPossible(ddt); + DWARFUtil.packCompositeIfPossible((Composite) ddt.dataType, dataTypeManager); } } @@ -747,7 +795,7 @@ public class DWARFDataTypeImporter { StructureDataType structure = (StructureDataType) ddt.dataType; - long structSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, 0); + long structSize = diea.getUnsignedLong(DW_AT_byte_size, 0); if (isStructTooBigForGhidra(structSize)) { return; } @@ -756,58 +804,14 @@ public class DWARFDataTypeImporter { // location can conflict with the first member field's offset. // This means that member fields will be successfully added and the field // that represents the base class will fail in these cases. - populateStubStruct_worker(ddt, structure, diea, DWARFTag.DW_TAG_member); - populateStubStruct_worker(ddt, structure, diea, DWARFTag.DW_TAG_inheritance); + populateStubStruct_worker(ddt, structure, diea, DW_TAG_member); + populateStubStruct_worker(ddt, structure, diea, DW_TAG_inheritance); removeUneededStructMemberShrinkage(structure); if (importOptions.isTryPackStructs()) { - packCompositeIfPossible(ddt); + DWARFUtil.packCompositeIfPossible((Composite) ddt.dataType, dataTypeManager); } } - private void packCompositeIfPossible(DWARFDataType ddt) { - Composite original = (Composite) ddt.dataType; - if (original.isZeroLength() || original.getNumComponents() == 0) { - // don't try to pack empty structs, this would throw off conflicthandler logic. - // also don't pack sized structs with no fields because when packed down to 0 bytes they - // cause errors when used as a param type - return; - } - - Composite copy = (Composite) original.copy(dataTypeManager); - copy.setToDefaultPacking(); - if (copy.getLength() != original.getLength()) { - // so far, typically because trailing zero-len flex array caused toolchain to - // bump struct size to next alignment value in a way that doesn't mesh with ghidra's - // logic - return; // fail - } - - DataTypeComponent[] preComps = original.getDefinedComponents(); - DataTypeComponent[] postComps = copy.getDefinedComponents(); - if (preComps.length != postComps.length) { - return; // fail - } - for (int index = 0; index < preComps.length; index++) { - DataTypeComponent preDTC = preComps[index]; - DataTypeComponent postDTC = postComps[index]; - if (preDTC.getOffset() != postDTC.getOffset() || - preDTC.getLength() != postDTC.getLength() || - preDTC.isBitFieldComponent() != postDTC.isBitFieldComponent()) { - return; // fail - } - if (preDTC.isBitFieldComponent()) { - BitFieldDataType preBFDT = (BitFieldDataType) preDTC.getDataType(); - BitFieldDataType postBFDT = (BitFieldDataType) postDTC.getDataType(); - if (preBFDT.getBitOffset() != postBFDT.getBitOffset() || - preBFDT.getBitSize() != postBFDT.getBitSize()) { - return; // fail - } - } - } - - original.setToDefaultPacking(); - } - /** * Restore structure fields to their regular size (if there is room) to ensure * future DataType equiv and comparisons are successful. @@ -867,11 +871,11 @@ public class DWARFDataTypeImporter { DIEAggregate childDIEA = prog.getAggregate(childEntry); // skip static member vars as they do not have storage in the structure - if (childDIEA.hasAttribute(DWARFAttribute.DW_AT_external)) { + if (childDIEA.hasAttribute(DW_AT_external)) { continue; } - int bitSize = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_size, -1); + int bitSize = childDIEA.parseInt(DW_AT_bit_size, -1); boolean isBitField = bitSize != -1; DWARFDataType childDT = getDataType(childDIEA.getTypeRef(), null); @@ -880,6 +884,7 @@ public class DWARFDataTypeImporter { "Failed to get data type for struct field: " + childDIEA.getHexOffset()); continue; } + DataType dt = fixupDataTypeInconsistencies(childDT); String memberName = childDIEA.getName(); @@ -888,8 +893,8 @@ public class DWARFDataTypeImporter { if (memberName == null) { // If the member is an inheritance type, then set the name // to be the name of the data type - if (childDIEA.getTag() == DWARFTag.DW_TAG_inheritance) { - memberName = "super_" + childDT.dataType.getName(); + if (childDIEA.getTag() == DW_TAG_inheritance) { + memberName = "super_" + dt.getName(); } else { memberName = "field_" + structure.getNumDefinedComponents(); @@ -897,17 +902,16 @@ public class DWARFDataTypeImporter { } boolean hasMemberOffset = - childDIEA.hasAttribute(DWARFAttribute.DW_AT_data_member_location); + childDIEA.hasAttribute(DW_AT_data_member_location); int memberOffset = 0; if (hasMemberOffset) { try { - memberOffset = childDIEA.parseDataMemberOffset( - DWARFAttribute.DW_AT_data_member_location, 0); + memberOffset = childDIEA.parseDataMemberOffset(DW_AT_data_member_location, 0); } catch (DWARFExpressionException e) { DWARFUtil.appendDescription(structure, memberDesc("Missing member", - "failed to parse location", memberName, childDT, -1, bitSize, -1), "\n"); + "failed to parse location", memberName, dt, -1, bitSize, -1), "\n"); continue; } } @@ -922,30 +926,29 @@ public class DWARFDataTypeImporter { //} if (isBitField) { - if (!BitFieldDataType.isValidBaseDataType(childDT.dataType)) { + if (!BitFieldDataType.isValidBaseDataType(dt)) { DWARFUtil.appendDescription(structure, - memberDesc("Missing member", - "Bad data type for bitfield: " + childDT.dataType.getName(), memberName, - childDT, -1, bitSize, -1), + memberDesc("Missing member", "Bad data type for bitfield: " + dt.getName(), + memberName, dt, -1, bitSize, -1), "\n"); continue; } int containerLen; if (hasMemberOffset) { - int byteSize = childDIEA.parseInt(DWARFAttribute.DW_AT_byte_size, -1); - containerLen = byteSize <= 0 ? childDT.dataType.getLength() : byteSize; + int byteSize = childDIEA.parseInt(DW_AT_byte_size, -1); + containerLen = byteSize <= 0 ? dt.getLength() : byteSize; } else { containerLen = structure.getLength(); } int containerBitLen = containerLen * 8; - int bitOffset = childDIEA.parseInt(DWARFAttribute.DW_AT_data_bit_offset, -1); + int bitOffset = childDIEA.parseInt(DW_AT_data_bit_offset, -1); int ghidraBitOffset; if (bitOffset == -1) { // try to fall back to previous dwarf version's bit_offset attribute that has slightly different info - bitOffset = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_offset, -1); + bitOffset = childDIEA.parseInt(DW_AT_bit_offset, -1); // convert DWARF bit offset value to Ghidra bit offset ghidraBitOffset = containerBitLen - bitOffset - bitSize; @@ -961,7 +964,7 @@ public class DWARFDataTypeImporter { if (bitOffset < 0 || ghidraBitOffset < 0 || ghidraBitOffset >= containerBitLen) { DWARFUtil.appendDescription(structure, memberDesc("Missing member", - "bad bitOffset", memberName, childDT, memberOffset, bitSize, bitOffset), + "bad bitOffset", memberName, dt, memberOffset, bitSize, bitOffset), "\n"); continue; } @@ -970,7 +973,7 @@ public class DWARFDataTypeImporter { // TODO: need safety checks here to make sure that using insertAt() doesn't // modify the struct structure.insertBitFieldAt(memberOffset, containerLen, ghidraBitOffset, - childDT.dataType, bitSize, memberName, null); + dt, bitSize, memberName, null); } catch (InvalidDataTypeException e) { Msg.error(this, @@ -978,23 +981,23 @@ public class DWARFDataTypeImporter { structure.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() + "], skipping: " + e.getMessage()); DWARFUtil.appendDescription(structure, - memberDesc("Missing member ", "Failed to add bitfield", memberName, childDT, + memberDesc("Missing member ", "Failed to add bitfield", memberName, dt, memberOffset, bitSize, bitOffset), "\n"); } } else { String memberComment = null; - boolean isDynamicSizedType = (childDT.dataType instanceof Dynamic || - childDT.dataType instanceof FactoryDataType); + boolean isDynamicSizedType = + (dt instanceof Dynamic || dt instanceof FactoryDataType); if (isDynamicSizedType) { - memberComment = "Unsupported dynamic size data type: " + childDT.dataType; - childDT.dataType = Undefined.getUndefinedDataType(1); + memberComment = "Unsupported dynamic size data type: " + dt; + dt = Undefined.getUndefinedDataType(1); } - int childLength = getUnpaddedDataTypeLength(childDT.dataType); + int childLength = getUnpaddedDataTypeLength(dt); if (memberOffset + childLength > structure.getLength()) { DWARFUtil.appendDescription(structure, memberDesc("Missing member", - "exceeds parent struct len", memberName, childDT, memberOffset, -1, -1), + "exceeds parent struct len", memberName, dt, memberOffset, -1, -1), "\n"); continue; @@ -1002,16 +1005,16 @@ public class DWARFDataTypeImporter { try { DataTypeComponent dtc; - if (DataTypeComponent.usesZeroLengthComponent(childDT.dataType)) { + if (DataTypeComponent.usesZeroLengthComponent(dt)) { if (!isUndefinedOrZeroLenAtOffset(structure, memberOffset)) { DWARFUtil.appendDescription(structure, memberDesc("Missing member", - "conflicting member at same offset", memberName, childDT, + "conflicting member at same offset", memberName, dt, memberOffset, -1, -1), "\n"); continue; } // use insertAt for zero len members to allow multiple at same offset dtc = - structure.insertAtOffset(memberOffset, childDT.dataType, 0, memberName, + structure.insertAtOffset(memberOffset, dt, 0, memberName, memberComment); } else { @@ -1023,18 +1026,18 @@ public class DWARFDataTypeImporter { DWARFUtil.appendDescription(structure, memberDesc("Missing member", "conflict with " + existingDTC.getFieldName(), - memberName, childDT, memberOffset, -1, -1), + memberName, dt, memberOffset, -1, -1), "\n"); } continue; } - dtc = structure.replace(ordinalToReplace, childDT.dataType, childLength, - memberName, memberComment); + dtc = structure.replace(ordinalToReplace, dt, childLength, memberName, + memberComment); } // struct.replaceAtOffset() and insertAtOffset() clones the childDT, which will mess up our // identity based mapping in currentImplDataTypeToDDT. // Update the mapping to prevent that. - updateMapping(childDT.dataType, dtc.getDataType()); + updateMapping(dt, dtc.getDataType()); } catch (IllegalArgumentException exc) { Msg.error(this, @@ -1042,7 +1045,7 @@ public class DWARFDataTypeImporter { structure.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() + "], skipping: " + exc.getMessage()); DWARFUtil.appendDescription(structure, memberDesc("Missing member ", "", - memberName, childDT, memberOffset, -1, -1), "\n"); + memberName, dt, memberOffset, -1, -1), "\n"); } } } @@ -1074,14 +1077,25 @@ public class DWARFDataTypeImporter { } private static String memberDesc(String prefix, String errorStr, String memberName, - DWARFDataType ddt, int memberOffset, int bitSize, int bitOffset) { + DataType dt, int memberOffset, int bitSize, int bitOffset) { return (!StringUtils.isBlank(prefix) ? prefix + " " : "") + memberName + " : " + - ddt.dataType.getName() + (bitSize != -1 ? ":" + bitSize : "") + " at offset " + + dt.getName() + (bitSize != -1 ? ":" + bitSize : "") + " at offset " + (memberOffset != -1 ? "0x" + Long.toHexString(memberOffset) : "unknown") + (bitOffset != -1 ? ":" + bitOffset : "") + (!StringUtils.isBlank(errorStr) ? " [" + errorStr + "]" : ""); } + private DataType fixupDataTypeInconsistencies(DWARFDataType ddt) { + if (ddt == null) { + return null; + } + DataType result = ddt.dataType; + if (result instanceof FunctionDefinition) { + result = dwarfDTM.getPtrTo(result); + } + return result; + } + /** * Creates a Ghidra {@link ArrayDataType}. *

@@ -1102,6 +1116,14 @@ public class DWARFDataTypeImporter { if (self != null) { return self; } + DataType elementDT = fixupDataTypeInconsistencies(elementType); + + long explictArraySize = diea.getUnsignedLong(DW_AT_byte_size, -1); + if (elementType.dataType.isZeroLength() || explictArraySize == 0) { + // don't bother checking range info, we are going to force a zero-element array + DataType zeroLenArray = new ArrayDataType(elementDT, 0, -1, dataTypeManager); + return new DWARFDataType(zeroLenArray, null, diea.getOffset()); + } // Build a list of the defined dimensions for this array type. // The first element in the DWARF dimension list would be where a wild-card (-1 length) @@ -1142,19 +1164,19 @@ public class DWARFDataTypeImporter { } else if (numElements > Integer.MAX_VALUE) { Msg.error(this, "Bad value [" + numElements + "] for array's size in DIE: " + - diea.getOffset() + ", forcing to 1"); + diea.getHexOffset() + ", forcing to 1"); numElements = 1; } dimensions.add((int) numElements); } - DataType dt = elementType.dataType; + DataType dt = elementDT; for (int i = dimensions.size() - 1; i >= 0; i--) { int numElements = dimensions.get(i); ArrayDataType subArray = new ArrayDataType(dt, numElements, -1, dataTypeManager); - if (dt == elementType.dataType) { + if (dt == elementDT) { updateMapping(dt, subArray.getDataType()); } dt = subArray; @@ -1191,8 +1213,7 @@ public class DWARFDataTypeImporter { throws IOException, DWARFExpressionException { DWARFDataType refdDT = getDataType(diea.getTypeRef(), voidDDT); - int byteSize = diea.parseInt(DWARFAttribute.DW_AT_byte_size, - diea.getCompilationUnit().getPointerSize()); + int byteSize = diea.parseInt(DW_AT_byte_size, diea.getCompilationUnit().getPointerSize()); // do a second query to see if there was a recursive loop in the call above back // to this datatype that resulted in this datatype being created. @@ -1225,8 +1246,7 @@ public class DWARFDataTypeImporter { return null; } - int byteSize = diea.parseInt(DWARFAttribute.DW_AT_byte_size, - diea.getCompilationUnit().getPointerSize()); + int byteSize = diea.parseInt(DW_AT_byte_size, diea.getCompilationUnit().getPointerSize()); DataType offsetType = dwarfDTM.getOffsetType(byteSize); // create a typedef to the offsetType and put containing type and var type info in the typedef name. @@ -1282,6 +1302,12 @@ public class DWARFDataTypeImporter { boolean typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict( typedefDNI.asDataTypePath().getPath(), refdDT.dataType.getPathName()); + if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT && + ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) { + // hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare + typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict( + typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName()); + } if (typedefWithSameName) { if (importOptions.isElideTypedefsWithSameName()) { @@ -1340,7 +1366,7 @@ public class DWARFDataTypeImporter { @Override public String toString() { return dataType.getName() + " | " + (dni != null ? dni.toString() : "na") + " | " + - hexOffsets(); + hexOffsets() + " | zerolen: " + dataType.isZeroLength(); } public String hexOffsets() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java index 39d3b540e3..b4d8540067 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeManager.java @@ -79,15 +79,13 @@ public class DWARFDataTypeManager { * @param prog {@link DWARFProgram} that holds the Ghidra {@link Program} being imported. * @param dataTypeManager {@link DataTypeManager} of the Ghidra Program. * @param builtInDTM {@link DataTypeManager} with built-in data types. - * @param importSummary {@link DWARFImportSummary} where summary information will be stored * during the import session. */ - public DWARFDataTypeManager(DWARFProgram prog, DataTypeManager dataTypeManager, - DataTypeManager builtInDTM, DWARFImportSummary importSummary) { + public DWARFDataTypeManager(DWARFProgram prog, DataTypeManager dataTypeManager) { this.prog = prog; this.dataTypeManager = dataTypeManager; - this.builtInDTM = builtInDTM; - this.importSummary = importSummary; + this.builtInDTM = BuiltInDataTypeManager.getDataTypeManager(); + this.importSummary = prog.getImportSummary(); this.importOptions = prog.getImportOptions(); initBaseDataTypes(); } @@ -122,8 +120,7 @@ public class DWARFDataTypeManager { // This does slow us down a little bit but this makes the GUI responsive to the user. Swing.runNow(Dummy.runnable()); - DWARFDataTypeImporter ddtImporter = - new DWARFDataTypeImporter(prog, this, prog.getImportOptions()); + DWARFDataTypeImporter ddtImporter = new DWARFDataTypeImporter(prog, this); // Convert the DWARF DIE record into a Ghidra DataType (probably impls) DWARFDataType pre = ddtImporter.getDataType(diea, null); @@ -255,6 +252,15 @@ public class DWARFDataTypeManager { return null; } + public DataType getDataTypeForVariable(DIEAggregate diea) { + DataType type = getDataType(diea, getVoidType()); + if (type instanceof FunctionDefinition) { + type = getPtrTo(type); + } + return type; + + } + /** * Returns a pointer to the specified data type. * @@ -265,6 +271,10 @@ public class DWARFDataTypeManager { return dataTypeManager.getPointer(dt); } + public DataType getPtrTo(DataType dt, int ptrSize) { + return dataTypeManager.getPointer(dt, ptrSize); + } + /** * Iterate all {@link DataType}s that match the CategoryPath / name given * in the {@link DataTypePath} parameter, including "conflict" datatypes @@ -372,10 +382,15 @@ public class DWARFDataTypeManager { * @param dwarfSize * @param dwarfEncoding * @param isBigEndian + * @param isExplictSize boolean flag, if true the returned data type will not be linked to + * the dataOrganization's compiler specified data types (eg. if type is something like int32_t, + * the returned type should never change size, even if the dataOrg changes). If false, + * the returned type will be linked to the dataOrg's compiler specified data types if possible, + * except for data types that have a name that include a bitsize in the name, such as "int64_t". * @return */ public DataType getBaseType(String name, int dwarfSize, int dwarfEncoding, - boolean isBigEndian) { + boolean isBigEndian, boolean isExplictSize) { DataType dt = null; String mangledName = null; @@ -398,11 +413,11 @@ public class DWARFDataTypeManager { // may be duplicated across different float types. Lookup by name is preferred. // May need to add name lookup capability to AbstractFloatDataType case DWARFEncoding.DW_ATE_float -> AbstractFloatDataType.getFloatDataType(dwarfSize, - getCorrectDTMForFixedLengthTypes(name, dwarfSize)); + getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize)); case DWARFEncoding.DW_ATE_signed -> AbstractIntegerDataType.getSignedDataType(dwarfSize, - getCorrectDTMForFixedLengthTypes(name, dwarfSize)); + getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize)); case DWARFEncoding.DW_ATE_unsigned -> AbstractIntegerDataType.getUnsignedDataType( - dwarfSize, getCorrectDTMForFixedLengthTypes(name, dwarfSize)); + dwarfSize, getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize)); case DWARFEncoding.DW_ATE_signed_char -> baseDataTypeChar; case DWARFEncoding.DW_ATE_unsigned_char -> baseDataTypeUchar; case DWARFEncoding.DW_ATE_UTF -> findMatchingDataTypeBySize(baseDataTypeChars, @@ -430,16 +445,18 @@ public class DWARFDataTypeManager { } - private DataTypeManager getCorrectDTMForFixedLengthTypes(String name, int dwarfSize) { + private DataTypeManager getCorrectDTMForFixedLengthTypes(String name, int dwarfSize, + boolean predeterminedHasExplictSize) { // If the requested name of the base type appears to have a bitsize string // embedded in it, this chunk of code will switch between using the normal DTM // to using a null DTM to force the Abstract(Integer|Float)DataType helper method to // create compiler independent data types that don't change size when the architecture is // changed. int typenameExplicitSize; - boolean usedFixedSizeType = importOptions.isSpecialCaseSizedBaseTypes() && - (typenameExplicitSize = getExplicitSizeFromTypeName(name)) != -1 && - typenameExplicitSize / 8 == dwarfSize; + boolean usedFixedSizeType = predeterminedHasExplictSize || + (importOptions.isSpecialCaseSizedBaseTypes() && + (typenameExplicitSize = getExplicitSizeFromTypeName(name)) != -1 && + typenameExplicitSize / 8 == dwarfSize); return usedFixedSizeType ? null : dataTypeManager; } @@ -706,13 +723,14 @@ public class DWARFDataTypeManager { */ private FunctionDefinitionDataType createFunctionDefinitionDataType(DIEAggregate diea, DWARFNameInfo dni) { - DataType returnDataType = getDataType(diea.getTypeRef(), baseDataTypeVoid); + DataType returnDataType = getDataTypeForVariable(diea.getTypeRef()); boolean foundThisParam = false; List params = new ArrayList<>(); for (DIEAggregate paramDIEA : diea.getFunctionParamList()) { String paramName = paramDIEA.getName(); - DataType paramDT = getDataType(paramDIEA.getTypeRef(), null); + DataType paramDT = getDataTypeForVariable(paramDIEA.getTypeRef()); + if (paramDT == null || paramDT.getLength() <= 0) { Msg.error(this, "Bad function parameter type for function " + dni.asCategoryPath() + @@ -747,7 +765,7 @@ public class DWARFDataTypeManager { return funcDef; } - + /** * Regex to match common fixed-size type names like "int64", "int64_t", etc, by triggering * off some known size designators in the string. @@ -777,4 +795,5 @@ public class DWARFDataTypeManager { return -1; } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java new file mode 100644 index 0000000000..311f5676d3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunction.java @@ -0,0 +1,361 @@ +/* ### + * 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.dwarf4.next; + +import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.*; +import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_unspecified_parameters; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import java.io.IOException; + +import ghidra.app.util.bin.format.dwarf4.*; +import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; +import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.PrototypeModel; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; + +/** + * Represents a function that was read from DWARF information. + */ +public class DWARFFunction { + public enum CommitMode { + SKIP, FORMAL, STORAGE, + } + + public DIEAggregate diea; + public DWARFNameInfo name; + public Namespace namespace; + public Address address; + public Address highAddress; + public long frameBase; // TODO: change this to preserve the func's frameBase expr instead of value + + public GenericCallingConvention callingConvention; + public PrototypeModel prototypeModel; + + public DWARFVariable retval; + public List params = new ArrayList<>(); + public boolean varArg; + public List localVars = new ArrayList<>(); + // We keep track of local var errors here because local variables aren't added + // to the local's list if they are problematic + public boolean localVarErrors; + public CommitMode signatureCommitMode = CommitMode.STORAGE; + + public boolean noReturn; + public DWARFSourceInfo sourceInfo; + public boolean isExternal; + + /** + * Create a function instance from the information found in the specified DIEA. + * + * @param diea DW_TAG_subprogram {@link DIEAggregate} + * @return new {@link DWARFFunction}, or null if invalid DWARF information + * @throws IOException if error accessing attribute values + * @throws DWARFExpressionException if error accessing attribute values + */ + public static DWARFFunction read(DIEAggregate diea) + throws IOException, DWARFExpressionException { + if (isBadSubprogramDef(diea)) { + return null; + } + + DWARFProgram prog = diea.getProgram(); + DWARFDataTypeManager dwarfDTM = prog.getDwarfDTM(); + + Address funcAddr = prog.getCodeAddress(diea.getLowPC(0)); + DWARFFunction dfunc = new DWARFFunction(diea, prog.getName(diea), funcAddr); + + dfunc.namespace = dfunc.name.getParentNamespace(prog.getGhidraProgram()); + dfunc.sourceInfo = DWARFSourceInfo.create(diea); + + dfunc.highAddress = + diea.hasAttribute(DW_AT_high_pc) ? prog.getCodeAddress(diea.getHighPC()) : null; + + // Check if the function is an external function + dfunc.isExternal = diea.getBool(DW_AT_external, false); + dfunc.noReturn = diea.getBool(DW_AT_noreturn, false); + + // Retrieve the frame base if it exists + DWARFLocation frameLoc = null; + if (diea.hasAttribute(DW_AT_frame_base)) { + List frameBase = diea.getAsLocation(DW_AT_frame_base, dfunc.getRange()); + // get the framebase register, find where the frame is finally setup. + frameLoc = DWARFLocation.getTopLocation(frameBase, dfunc.address.getOffset()); + if (frameLoc != null) { + dfunc.frameBase = (int) diea.evaluateLocation(frameLoc); + } + } + + dfunc.retval = + DWARFVariable.fromDataType(dfunc, dwarfDTM.getDataTypeForVariable(diea.getTypeRef())); + + int paramOrdinal = 0; + for (DIEAggregate paramDIEA : diea.getFunctionParamList()) { + DWARFVariable param = DWARFVariable.readParameter(paramDIEA, dfunc, paramOrdinal++); + dfunc.params.add(param); + } + dfunc.varArg = !diea.getChildren(DW_TAG_unspecified_parameters).isEmpty(); + + return dfunc; + } + + private DWARFFunction(DIEAggregate diea, DWARFNameInfo dni, Address address) { + this.diea = diea; + this.name = dni; + this.address = address; + } + + public DWARFProgram getProgram() { + return diea.getProgram(); + } + + public DWARFRange getRange() { + return new DWARFRange(address.getOffset(), + highAddress != null ? highAddress.getOffset() : address.getOffset() + 1); + } + + public String getCallingConventionName() { + return prototypeModel != null + ? prototypeModel.getName() + : callingConvention != null + ? callingConvention.getDeclarationName() + : null; + } + + /** + * Returns the DWARFVariable that starts at the specified stack offset. + * + * @param offset stack offset + * @return local variable that starts at offset, or null if not present + */ + public DWARFVariable getLocalVarByOffset(long offset) { + for (DWARFVariable localVar : localVars) { + if (localVar.isStackStorage() && localVar.getStackOffset() == offset) { + return localVar; + } + } + return null; + } + + /** + * Returns true if the specified stack offset is within the function's local variable + * storage area. + * + * @param offset stack offset to test + * @return true if stack offset is within this function's local variable area + */ + public boolean isInLocalVarStorageArea(long offset) { + boolean paramsHavePositiveOffset = diea.getProgram().stackGrowsNegative(); + return (paramsHavePositiveOffset && offset < 0) || + (!paramsHavePositiveOffset && offset >= 0); + } + + public boolean hasConflictWithParamStorage(DWARFVariable dvar) throws InvalidInputException { + if (dvar.lexicalOffset != 0) { + return false; + } + VariableStorage storage = dvar.getVariableStorage(); + for (DWARFVariable param : params) { + VariableStorage paramStorage = param.getVariableStorage(); + if (paramStorage.intersects(storage)) { + return true; + } + } + return false; + } + + public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar, Function gfunc) + throws InvalidInputException { + VariableStorage newVarStorage = dvar.getVariableStorage(); + for (Variable existingVar : gfunc.getAllVariables()) { + if (existingVar.getFirstUseOffset() == dvar.lexicalOffset && + existingVar.getVariableStorage().intersects(newVarStorage)) { + if ((existingVar instanceof LocalVariable) && + Undefined.isUndefined(existingVar.getDataType())) { + continue; + } + return true; + } + } + return false; + } + + public List getAllParamNames() { + return params.stream() + .filter(dvar -> !dvar.name.isAnon()) + .map(dvar -> dvar.name.getName()) + .collect(Collectors.toList()); + } + + public List getAllLocalVariableNames() { + return localVars.stream() + .filter(dvar -> !dvar.name.isAnon()) + .map(dvar -> dvar.name.getName()) + .collect(Collectors.toList()); + } + + public List getExistingLocalVariableNames(Function gfunc) { + return Arrays.stream(gfunc.getLocalVariables()) + .filter(var -> var.getName() != null && !Undefined.isUndefined(var.getDataType())) + .map(var -> var.getName()) + .collect(Collectors.toList()); + } + + public List getNonParamSymbolNames(Function gfunc) { + SymbolIterator symbols = gfunc.getProgram().getSymbolTable().getSymbols(gfunc); + return StreamSupport.stream(symbols.spliterator(), false) + .filter(symbol -> symbol.getSymbolType() != SymbolType.PARAMETER) + .map(Symbol::getName) + .collect(Collectors.toList()); + } + + /** + * Returns this function's parameters as a list of {@link Parameter} instances. + * + * @param includeStorageDetail boolean flag, if true storage information will be included, if + * false, VariableStorage.UNASSIGNED_STORAGE will be used + * @param program Ghidra program that contains the parameter + * @return list of Parameters + * @throws InvalidInputException + */ + public List getParameters(boolean includeStorageDetail) + throws InvalidInputException { + List result = new ArrayList<>(); + for (DWARFVariable dvar : params) { + result.add(dvar.asParameter(includeStorageDetail, getProgram().getGhidraProgram())); + } + return result; + } + + /** + * Returns a {@link FunctionDefinition} that reflects this function's information. + * + * @param prog {@link DWARFProgram} that contains this function + * @return {@link FunctionDefinition} that reflects this function's information + */ + public FunctionDefinition asFuncDef() { + List funcDefParams = new ArrayList<>(); + for (DWARFVariable param : params) { + funcDefParams.add(param.asParameterDef()); + } + + FunctionDefinitionDataType funcDef = + new FunctionDefinitionDataType(name.getParentCP(), name.getName(), + getProgram().getGhidraProgram().getDataTypeManager()); + funcDef.setReturnType(retval.type); + funcDef.setArguments(funcDefParams.toArray(ParameterDefinition[]::new)); + funcDef.setGenericCallingConvention( + Objects.requireNonNullElse(callingConvention, GenericCallingConvention.unknown)); + funcDef.setVarArgs(varArg); + + DWARFSourceInfo sourceInfo = null; + if (getProgram().getImportOptions().isOutputSourceLocationInfo() && + (sourceInfo = DWARFSourceInfo.create(diea)) != null) { + funcDef.setComment(sourceInfo.getDescriptionStr()); + } + + return funcDef; + } + + public void commitLocalVariable(DWARFVariable dvar, Function gfunc) { + + VariableStorage varStorage = null; + try { + varStorage = dvar.getVariableStorage(); + if (hasConflictWithParamStorage(dvar)) { + appendComment(gfunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, + "Local variable %s[%s] conflicts with parameter, skipped.".formatted( + dvar.getDeclInfoString(), varStorage), + "\n"); + return; + } + + if (hasConflictWithExistingLocalVariableStorage(dvar, gfunc)) { + appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, + "Local omitted variable %s[%s] scope starts here".formatted( + dvar.getDeclInfoString(), varStorage), + "; "); + return; + } + + NameDeduper nameDeduper = new NameDeduper(); + nameDeduper.addReservedNames(getAllLocalVariableNames()); + nameDeduper.addUsedNames(getAllParamNames()); + nameDeduper.addUsedNames(getExistingLocalVariableNames(gfunc)); + + Variable var = dvar.asLocalVariable(); + String origName = var.getName(); + String newName = nameDeduper.getUniqueName(origName); + if (newName != null) { + try { + var.setName(newName, null); + } + catch (DuplicateNameException | InvalidInputException e) { + // can't happen + } + var.setComment("Original name: " + origName); + } + + VariableUtilities.checkVariableConflict(gfunc, var, varStorage, true); + gfunc.addLocalVariable(var, SourceType.IMPORTED); + } + catch (InvalidInputException | DuplicateNameException e) { + appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, + "Local omitted variable %s[%s] scope starts here".formatted( + dvar.getDeclInfoString(), + varStorage != null ? varStorage.toString() : "UNKNOWN"), + "; "); + + } + } + + @Override + public String toString() { + return String.format( + "DWARFFunction [\n\tdni=%s,\n\taddress=%s,\n\tparams=%s,\n\tsourceInfo=%s,\n\tlocalVarErrors=%s,\n\tretval=%s\n]", + name, address, params, sourceInfo, localVarErrors, retval); + } + + private static boolean isBadSubprogramDef(DIEAggregate diea) { + if (diea.isDanglingDeclaration() || !diea.hasAttribute(DW_AT_low_pc)) { + return true; + } + + // fetch the low_pc attribute directly instead of calling diea.getLowPc() to avoid + // any fixups applied by lower level code + DWARFNumericAttribute attr = + diea.getAttribute(DW_AT_low_pc, DWARFNumericAttribute.class); + if (attr != null && attr.getUnsignedValue() == 0) { + return true; + } + + return false; + } + + private void appendComment(Address address, int commentType, String comment, String sep) { + DWARFUtil.appendComment(getProgram().getGhidraProgram(), address, commentType, "", comment, + sep); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java index 0f9f1a5ffb..e406e55217 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporter.java @@ -22,24 +22,18 @@ import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -import ghidra.app.cmd.comments.AppendCommentCmd; import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.bin.format.dwarf4.*; -import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; -import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute; -import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; -import ghidra.app.util.bin.format.dwarf4.expression.*; -import ghidra.program.database.data.DataTypeUtilities; +import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; +import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; 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.data.Enum; -import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function.FunctionUpdateType; -import ghidra.program.model.pcode.Varnode; import ghidra.program.model.symbol.*; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.Msg; @@ -62,31 +56,29 @@ public class DWARFFunctionImporter { private final Program currentProgram; private final DWARFDataTypeManager dwarfDTM; private final DWARFImportOptions importOptions; + private final DWARFImportSummary importSummary; private ProgramModule rootModule;// Program tree module for DWARF private Set processedOffsets = new HashSet<>(); - private Map functionsProcessed = new HashMap<>(); + private Set

functionsProcessed = new HashSet<>(); private Set
variablesProcesesed = new HashSet<>(); + private List functionFixups = DWARFFunctionFixup.findFixups(); private TaskMonitor monitor; - private DWARFImportSummary importSummary; - public static boolean hasDWARFProgModule(Program prog, String progModuleName) { ProgramModule dwarfModule = prog.getListing().getRootModule(progModuleName); return dwarfModule != null; } - public DWARFFunctionImporter(DWARFProgram prog, DWARFDataTypeManager dwarfDTM, - DWARFImportOptions importOptions, DWARFImportSummary importSummary, - TaskMonitor monitor) { + public DWARFFunctionImporter(DWARFProgram prog, TaskMonitor monitor) { this.prog = prog; this.monitor = monitor; this.currentProgram = prog.getGhidraProgram(); - this.dwarfDTM = dwarfDTM; - this.importOptions = importOptions; - this.importSummary = importSummary; + this.dwarfDTM = prog.getDwarfDTM(); + this.importOptions = prog.getImportOptions(); + this.importSummary = prog.getImportSummary(); } private boolean shouldProcess(DIEAggregate diea) { @@ -115,7 +107,10 @@ public class DWARFFunctionImporter { try { switch (diea.getTag()) { - case DW_TAG_subprogram: + case DW_TAG_gnu_call_site: // needs skip head + case DW_TAG_call_site: + diea = DIEAggregate.createSkipHead(diea); // fallthru to next switch case + case DW_TAG_subprogram: // normal try { processSubprogram(diea); } @@ -125,29 +120,16 @@ public class DWARFFunctionImporter { break; case DW_TAG_variable: // only process variable definitions that are static variables - // (ie. they are children of the compunit root, ie. depth == 1) - // local variables should be children of dw_tag_subprograms + // (ie. they are children of the compunit root, ie. depth == 1). + // Local variables should be children of dw_tag_subprograms // and will be handled in processFuncChildren() if (diea.getDepth() == 1) { - try { - processVariable(diea, null, null, -1); - } - catch (InvalidInputException e) { - Msg.error(this, "Failed to process var " + diea.getHexOffset(), e); - } + outputGlobal(DWARFVariable.readGlobalVariable(diea)); } break; case DW_TAG_label: processLabel(diea); break; - - case DW_TAG_gnu_call_site: - case DW_TAG_call_site: - DIEAggregate partDIEA = DIEAggregate.createSkipHead(diea); - if (partDIEA != null && !isBadSubprogramDef(partDIEA)) { - processSubprogram(partDIEA); - } - break; } } catch (OutOfMemoryError oom) { @@ -171,28 +153,13 @@ public class DWARFFunctionImporter { Collections.sort(sortedUnknownRegs); Msg.error(this, " unknown registers: " + - sortedUnknownRegs.stream().map(i -> Integer.toString(i)).collect( - Collectors.joining(", "))); + sortedUnknownRegs.stream() + .map(i -> Integer.toString(i)) + .collect( + Collectors.joining(", "))); } } - private boolean isBadSubprogramDef(DIEAggregate diea) { - if (diea.isDanglingDeclaration() || !diea.hasAttribute(DWARFAttribute.DW_AT_low_pc)) { - return true; - } - - long lowPC = diea.getLowPC(0); // adjusted by program base addr fixup - DWARFNumericAttribute attr = - diea.getAttribute(DWARFAttribute.DW_AT_low_pc, DWARFNumericAttribute.class); - if (attr != null && attr.getUnsignedValue() == 0 && lowPC != 0) { - // don't process this func if its raw lowpc is 0, with the exception of a binary (a .o) - // that starts at 0 and has a function at 0 - return true; - } - - return false; - } - private void markAllChildrenAsProcessed(DebugInfoEntry die) { for (DebugInfoEntry child : die.getChildren()) { processedOffsets.add(child.getOffset()); @@ -203,230 +170,177 @@ public class DWARFFunctionImporter { private void processSubprogram(DIEAggregate diea) throws IOException, InvalidInputException, DWARFExpressionException { - if (!shouldProcess(diea)) { + if (diea == null || !shouldProcess(diea)) { return; } - if (isBadSubprogramDef(diea)) { + // read the dwarf function info (name, addr, params) + DWARFFunction dfunc = DWARFFunction.read(diea); + if (dfunc == null) { markAllChildrenAsProcessed(diea.getHeadFragment()); return; } - DWARFFunction dfunc = new DWARFFunction(prog.getName(diea)); - dfunc.namespace = dfunc.dni.getParentNamespace(currentProgram); + FunctionDefinition origFuncDef = dfunc.asFuncDef(); // before any fixups - Number lowPC = diea.getLowPC(0); - dfunc.address = toAddr(lowPC); - dfunc.highAddress = - diea.hasAttribute(DWARFAttribute.DW_AT_high_pc) ? toAddr(diea.getHighPC()) : null; + if (functionsProcessed.contains(dfunc.address)) { + markAllChildrenAsProcessed(dfunc.diea.getHeadFragment()); - String previousFunctionProcessed = functionsProcessed.get(dfunc.address); - if (previousFunctionProcessed != null) { -// Msg.info(this, "Duplicate function defintion found for " + dni.getCategoryPath() + -// " at " + function.address + " in DIE " + diea.getHexOffset() + ", skipping"); - markAllChildrenAsProcessed(diea.getHeadFragment()); + Function currentFunction = currentProgram.getListing().getFunctionAt(dfunc.address); + if (currentFunction != null) { + decorateFunctionWithAlternateInfo(dfunc, currentFunction, origFuncDef); + } return; } - functionsProcessed.put(dfunc.address, - dfunc.dni.getNamespacePath() + " DIE: " + diea.getHexOffset()); + functionsProcessed.add(dfunc.address); - // Check if the function is an external function - dfunc.isExternal = diea.getBool(DWARFAttribute.DW_AT_external, false); + // only process the children (lexical blocks, local vars, etc) if we are going + // to emit a new ghidra function, otherwise if 2 dwarf function defs point to same + // location, we will get multiple side-effect output from processFuncChildren + processFuncChildren(diea, dfunc, 0); - // Retrieve the frame base if it exists - DWARFLocation frameLoc = null; - if (diea.hasAttribute(DWARFAttribute.DW_AT_frame_base)) { - List frameBase = diea.getAsLocation(DWARFAttribute.DW_AT_frame_base); - // get the framebase register, find where the frame is finally set - // up. - frameLoc = getTopLocation(frameBase, dfunc.address.getOffset()); - if (frameLoc != null) { - dfunc.frameBase = (int) diea.evaluateLocation(frameLoc); - } + Function gfunc = createFunction(dfunc, diea); // create empty func with no info + if (gfunc == null) { + return; } - // Get it's return type - // TODO: Sometimes the return type may actually be a pointer parameter - // passed into - // the given function - figure out how to determine this. For example, - // C++ can - // return object types defined in the function but may be implemented as - // the caller - // function passing a pointer to the callee function where the object is - // then operated on. - DIEAggregate typeRef = diea.getTypeRef(); - DataType formalReturnType = (typeRef != null) - ? dwarfDTM.getDataType(typeRef, DataType.DEFAULT) - : dwarfDTM.getVoidType(); - dfunc.retval = new DWARFVariable(); - dfunc.retval.type = formalReturnType; + if (gfunc.hasNoReturn() && !dfunc.noReturn) { + // preserve the noReturn flag if set by earlier analyzer + dfunc.noReturn = true; + } - boolean formalParamsOnly = false; - boolean skipFuncSignature = false; - List formalParams = new ArrayList<>(); - - for (DIEAggregate paramDIEA : diea.getFunctionParamList()) { - - DataType paramDT = dwarfDTM.getDataType(paramDIEA.getTypeRef(), null); - if (paramDT == null || DataTypeComponent.usesZeroLengthComponent(paramDT)) { - String paramName = paramDIEA.getString(DW_AT_name, "param" + formalParams.size()); - Msg.warn(this, "DWARF: zero-length function parameter " + paramName + - ":" + paramDT.getName() + ", omitting from definition of " + - dfunc.dni.getName() + "@" + dfunc.address); - // skip this parameter because its data type is a zero-width type that typically does - // not generate code. If this varies compiler-to-compiler, setting - // skipFuncSignature=true may be a better choice - continue; + // Run all the DWARFFunctionFixup instances + for (DWARFFunctionFixup fixup : functionFixups) { + try { + fixup.fixupDWARFFunction(dfunc, gfunc); } - - Parameter formalParam = createFormalParameter(paramDIEA); - if (formalParam == null) { - skipFuncSignature = true; + catch (DWARFException e) { + dfunc.signatureCommitMode = CommitMode.SKIP; break; } - formalParams.add(formalParam); - - if (!formalParamsOnly) { - DWARFVariable var = processVariable(paramDIEA, dfunc, null, -1); - if (var == null) { - // we had an error, can't rely on detailed param data, fallback to - // formal params - formalParamsOnly = true; - dfunc.params.clear(); - } - else { - dfunc.params.add(var); - } - } } - dfunc.varArg = !diea.getChildren(DW_TAG_unspecified_parameters).isEmpty(); - processFuncChildren(diea, dfunc); + decorateFunctionWithDWARFInfo(dfunc, gfunc, origFuncDef); - Function gfunc = createFunction(dfunc, diea); + if (dfunc.signatureCommitMode != CommitMode.SKIP) { + updateFunctionSignature(gfunc, dfunc); + } + else { + Msg.error(this, + String.format( + "Failed to get DWARF function signature information, leaving undefined: %s@%s", + gfunc.getName(), gfunc.getEntryPoint())); + Msg.debug(this, "DIE info: " + diea.toString()); + } - if (gfunc != null) { - if (diea.getBool(DW_AT_noreturn, false)) { - gfunc.setNoReturn(true); - } - if (formalParams.isEmpty() && dfunc.localVarErrors) { - // if there were no defined parameters and we had problems decoding local variables, - // don't force the method to have an empty param signature because there are other - // issues afoot. - skipFuncSignature = true; - } - else if (formalParams.isEmpty() && diea.getCompilationUnit() - .getCompileUnit() - .getLanguage() == DWARFSourceLanguage.DW_LANG_Rust) { - // if there were no defined parameters and the language is Rust, don't force an - // empty param signature. Rust language emit dwarf info without types (signatures) - // when used without -g. - skipFuncSignature = true; - } - - if (skipFuncSignature) { - Msg.error(this, - "Failed to get function signature information, leaving undefined: " + - gfunc.getName() + "@" + gfunc.getEntryPoint()); - Msg.debug(this, "DIE info: " + diea.toString()); - return; - } - - if (formalParamsOnly) { - updateFunctionSignatureWithFormalParams(gfunc, formalParams, - formalReturnType, dfunc.varArg, diea); + for (DWARFVariable localVar : dfunc.localVars) { + if (localVar.isRamStorage()) { + outputGlobal(localVar); // static variable scoped to the function } else { - updateFunctionSignatureWithDetailParams(gfunc, dfunc, diea); + dfunc.commitLocalVariable(localVar, gfunc); } } + } + + private void decorateFunctionWithAlternateInfo(DWARFFunction dfunc, Function gfunc, + FunctionDefinition funcDef) { + // Don't include the calling conv as it generates excessive false positives + // because we haven't run the dfunc through any fixups yet. + // Unnamed parameters still cause false positives because they render differently between + // funcdefs and actual functions + String newAlternatePrototype = funcDef.getPrototypeString(false); + + String currentPrototype = gfunc.getSignature(true).getPrototypeString(false); + if (!currentPrototype.equals(newAlternatePrototype)) { + appendPlateComment(dfunc.address, "DWARF alternate signature: ", newAlternatePrototype); + } } - - private void updateFunctionSignatureWithFormalParams(Function gfunc, List params, - DataType returnType, boolean varArgs, DIEAggregate diea) { - try { - String callingConventionName = null; - ReturnParameterImpl returnVar = new ReturnParameterImpl(returnType, currentProgram); - try { - if (!params.isEmpty() && Function.THIS_PARAM_NAME.equals(params.get(0).getName())) { - // this handles the common / simple case. More nuanced cases where the param - // didn't have the correct "this" name, but were marked with DW_AT_object_pointer - // or DW_AT_artifical won't be handled by this. - callingConventionName = GenericCallingConvention.thiscall.getDeclarationName(); - } - gfunc.setVarArgs(varArgs); - gfunc.updateFunction(callingConventionName, returnVar, params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); + private void decorateFunctionWithDWARFInfo(DWARFFunction dfunc, Function gfunc, + FunctionDefinition origFuncDef) { + if (dfunc.sourceInfo != null) { + // Move the function into the program tree of the file + moveIntoFragment(gfunc.getName(), dfunc.address, + dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1), + dfunc.sourceInfo.getFilename()); + + if (importOptions.isOutputSourceLocationInfo()) { + appendPlateComment(dfunc.address, "", dfunc.sourceInfo.getDescriptionStr()); } - catch (DuplicateNameException e) { - // try again after adjusting param names - setUniqueParameterNames(gfunc, params); - gfunc.updateFunction(callingConventionName, returnVar, params, + } + if (importOptions.isOutputDIEInfo()) { + appendPlateComment(dfunc.address, "DWARF DIE: ", dfunc.diea.getHexOffset()); + appendPlateComment(dfunc.address, "DWARF signature update mode: ", + dfunc.signatureCommitMode.toString()); + } + + if (dfunc.name.isNameModified()) { + appendPlateComment(dfunc.address, "DWARF original name: ", + dfunc.name.getOriginalName()); + } + + FunctionDefinition newFuncDef = dfunc.asFuncDef(); + String origFuncDefStr = origFuncDef.getPrototypeString(true); + if (!newFuncDef.getPrototypeString(true).equals(origFuncDefStr)) { + // if the prototype of the function was modified during the fixup phase, append + // the original version (according to dwarf) to the comment + appendPlateComment(dfunc.address, "DWARF original prototype: ", origFuncDefStr); + } + + + } + + private void updateFunctionSignature(Function gfunc, DWARFFunction dfunc) { + try { + boolean includeStorageDetail = dfunc.signatureCommitMode == CommitMode.STORAGE; + FunctionUpdateType functionUpdateType = includeStorageDetail + ? FunctionUpdateType.CUSTOM_STORAGE + : FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS; + + Parameter returnVar = dfunc.retval.asReturnParameter(includeStorageDetail); + List parameters = dfunc.getParameters(includeStorageDetail); + + if (includeStorageDetail && !dfunc.retval.isZeroByte() && + dfunc.retval.isMissingStorage()) { + // Update return value in a separate step as its storage isn't typically specified + // in dwarf info. + // This will allow automagical storage assignment for return value by ghidra. + gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, List.of(), FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); + returnVar = null; // don't update it in the second call to updateFunction() } + + gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, parameters, + functionUpdateType, true, SourceType.IMPORTED); + gfunc.setVarArgs(dfunc.varArg); + gfunc.setNoReturn(dfunc.noReturn); } catch (InvalidInputException | DuplicateNameException e) { Msg.error(this, - "Error updating function " + gfunc.getName() + " with formal params at " + - gfunc.getEntryPoint().toString() + ": " + e.getMessage()); - Msg.error(this, "DIE info: " + diea.toString()); + String.format("Error updating function %s@%s with params: %s", + gfunc.getName(), gfunc.getEntryPoint().toString(), e.getMessage())); + Msg.error(this, "DIE info: " + dfunc.diea.toString()); } } - private void updateFunctionSignatureWithDetailParams(Function gfunc, DWARFFunction dfunc, - DIEAggregate diea) { - try { - CompilerSpec compilerSpec = currentProgram.getCompilerSpec(); - PrototypeModel convention = null; - Variable returnVariable; - List params = new ArrayList<>(); - - returnVariable = buildReturnVariable(dfunc.retval); - for (int i = 0; i < dfunc.params.size(); ++i) { - Parameter curparam = buildParameter(gfunc, i, dfunc.params.get(i), diea); - params.add(curparam); - if (i == 0 && checkThisParameter(dfunc.params.get(0), diea)) { - convention = - compilerSpec.matchConvention(CompilerSpec.CALLING_CONVENTION_thiscall); - } - } - - for (int i = 0; i < dfunc.local.size(); ++i) { - commitLocal(gfunc, dfunc.local.get(i)); - } - - if (dfunc.retval != null || params.size() > 0) { - // Add the function signature definition into the data type manager -// TODO: createFunctionDefinition(dfunc, infopath); - - // NOTE: Storage is computed above for the purpose of identifying - // a best fit calling convention. The commitPrototype method currently - // always employs dynamic storage. - commitPrototype(gfunc, returnVariable, params, convention); - gfunc.setVarArgs(dfunc.varArg); - } - } - catch (InvalidInputException | DuplicateNameException iie) { - Msg.error(this, "Error updating function " + dfunc.dni.getName() + " at " + - dfunc.address.toString() + ": " + iie.getMessage()); - } - } - - private void processFuncChildren(DIEAggregate diea, DWARFFunction dfunc) + private void processFuncChildren(DIEAggregate diea, DWARFFunction dfunc, + long offsetFromFuncStart) throws InvalidInputException, IOException, DWARFExpressionException { + // offsetFromFuncStart will be -1 if the containing block didn't have location info for (DebugInfoEntry childEntry : diea.getHeadFragment().getChildren()) { DIEAggregate childDIEA = prog.getAggregate(childEntry); switch (childDIEA.getTag()) { case DW_TAG_variable: { - DWARFVariable var = - processVariable(childDIEA, dfunc, null, dfunc.address.getOffset()); - - if ((var != null) && var.isStackOffset) { - dfunc.local.add(var); + if (offsetFromFuncStart >= 0) { + DWARFVariable localVar = + DWARFVariable.readLocalVariable(childDIEA, dfunc, offsetFromFuncStart); + if (localVar != null) { + dfunc.localVars.add(localVar); + } } break; } @@ -443,258 +357,95 @@ public class DWARFFunctionImporter { case DW_TAG_gnu_call_site: case DW_TAG_call_site: DIEAggregate partDIEA = DIEAggregate.createSkipHead(diea); - if (partDIEA != null && !isBadSubprogramDef(partDIEA)) { - processSubprogram(partDIEA); - } + processSubprogram(partDIEA); break; } } } - private Parameter createFormalParameter(DIEAggregate diea) { - String name = diea.getString(DW_AT_name, null); - DataType dt = dwarfDTM.getDataType(diea.getTypeRef(), dwarfDTM.getVoidType()); - - try { - return new ParameterImpl(name, dt, currentProgram); - } - catch (InvalidInputException e) { - Msg.debug(this, "Failed to create parameter for " + diea.toString()); - } - return null; - } - - /** - * Creates a new {@link DWARFVariable} from the specified {@link DIEAggregate DIEA} and - * as a child of the specified function (if not null). - *

- * Used to process DW_TAG_variable as well as DW_TAG_formal_parameters. - * - * @param diea - the diea that specifies the variable - * @param dfunc - function that contains this variable, or null if static variable - * @param lexicalStart - not used by any caller - * @param firstUseAddr offset dfunc or -1 if formal parameter - * @return - * @throws IOException - * @throws InvalidInputException - */ - private DWARFVariable processVariable(DIEAggregate diea, DWARFFunction dfunc, - Address lexicalStart, long firstUseAddr) throws IOException, InvalidInputException { - - if (!shouldProcess(diea)) { - return null; + private void outputGlobal(DWARFVariable globalVar) { + if (globalVar == null) { + return; } - long funcAddr = (dfunc != null && dfunc.address != null) ? dfunc.address.getOffset() : -1; - - DWARFVariable dvar = new DWARFVariable(); - dvar.dni = prog.getName(diea); - dvar.lexicalOffset = dfunc != null && dfunc.address != null && lexicalStart != null - ? lexicalStart.subtract(dfunc.address) - : -1; - - // Unknown variable location - if (!diea.hasAttribute(DWARFAttribute.DW_AT_location)) { - return null; - } - - List locList = diea.getAsLocation(DWARFAttribute.DW_AT_location); - - // If we are trying to recover a local variable, only process the - // variable if it has a single location over the entire function - if ((firstUseAddr != -1) && locList.size() > 1) { - return null; - } - - DWARFLocation topLocation = getTopLocation(locList, funcAddr); - if (topLocation == null) { - if (dfunc != null) { - dfunc.localVarErrors = true; - } - return null; - } - - // Get the base type of this variable - dvar.type = dwarfDTM.getDataType(diea.getTypeRef(), dwarfDTM.getVoidType()); - - long frameBase = (dfunc != null) ? dfunc.frameBase : -1; - DWARFExpressionEvaluator exprEvaluator = - DWARFExpressionEvaluator.create(diea.getHeadFragment()); - exprEvaluator.setFrameBase(frameBase); - long res; - try { - DWARFExpression expr = exprEvaluator.readExpr(topLocation.getLocation()); - exprEvaluator.evaluate(expr); - res = exprEvaluator.pop(); - } - catch (DWARFExpressionException | UnsupportedOperationException - | IndexOutOfBoundsException ex) { - importSummary.exprReadError++; - if (dfunc != null) { - dfunc.localVarErrors = true; - } - - return null; - } - - if (exprEvaluator.isDwarfStackValue()) { - importSummary.varDWARFExpressionValue++; - if (dfunc != null) { - dfunc.localVarErrors = true; - } - return null; - } - else if (exprEvaluator.useUnknownRegister() && exprEvaluator.isRegisterLocation()) { - dvar.reg = exprEvaluator.getLastRegister(); - dvar.type = dwarfDTM.getPtrTo(dvar.type); - - // TODO: fix this later. Lie and use lexicalOffset-1 so the GUI correctly shows the first use - dvar.offset = dvar.lexicalOffset != -1 ? dvar.lexicalOffset - 1 : -1; - return dvar; - } - else if (exprEvaluator.useUnknownRegister()) { - importSummary.varDynamicRegisterError++; - if (dfunc != null) { - dfunc.localVarErrors = true; - } - return null; - } - else if (exprEvaluator.isStackRelative()) { - dvar.offset = res; - dvar.reg = null; - dvar.isStackOffset = true; - if (exprEvaluator.isDeref()) { - dvar.type = dwarfDTM.getPtrTo(dvar.type); - } - } - else if (exprEvaluator.isRegisterLocation()) { - // The DWARF expression evaluated to a simple register. If we have a mapping - // for it in the "processor.dwarf" register mapping file, try to create - // a variable, otherwise log the unknown register for later logging. - dvar.reg = exprEvaluator.getLastRegister(); - if (dvar.reg != null) { - dvar.offset = -1; - if (firstUseAddr != -1) { - dvar.offset = findFirstUse(currentProgram, dvar.reg, funcAddr, firstUseAddr); - } - if ((dvar.type != null) && - (dvar.type.getLength() > dvar.reg.getMinimumByteSize())) { - importSummary.varFitError++; - - String contextStr = (dfunc != null) - ? " for function " + dfunc.dni.getName() + "@" + dfunc.address - : ""; - if (diea.getTag() != DW_TAG_formal_parameter) { - Msg.warn(this, - "Variable " + dvar.dni.getName() + "[" + dvar.type.getName() + - ", size=" + dvar.type.getLength() + "]" + contextStr + - " can not fit into specified register " + dvar.reg.getName() + - ", size=" + dvar.reg.getMinimumByteSize() + - ", skipping. DWARF DIE: " + diea.getHexOffset()); - if (dfunc != null) { - dfunc.localVarErrors = true; - } - return null; - } - - dvar.type = dwarfDTM.getUndefined1Type(); - } - } - else { - // The DWARF register did not have a mapping to a Ghidra register, so - // log it to be displayed in an error summary at end of import phase. - importSummary.unknownRegistersEncountered.add(exprEvaluator.getRawLastRegister()); - if (dfunc != null) { - dfunc.localVarErrors = true; - } - return null; - } - } - else if (exprEvaluator.getLastRegister() == null) { - processStaticVar(res, dvar, diea); - return null;// Don't return the variable to be associated with the function - } - else { - Msg.error(this, - "LOCAL VAR: " + dvar.dni.getName() + " : " + - ghidra.app.util.bin.format.dwarf4.expression.DWARFExpression.exprToString( - topLocation.getLocation(), diea) + - ", DWARF DIE: " + diea.getHexOffset()); - return null; - } - return dvar; - } - - private void processStaticVar(long address, DWARFVariable dvar, DIEAggregate diea) - throws InvalidInputException { - dvar.dni = dvar.dni.replaceType(null /*nothing matches static global var*/); - if (address != 0) { - Address staticVariableAddress = toAddr(address + prog.getProgramBaseAddressFixup()); - if (isZeroByteDataType(dvar.type)) { - processZeroByteStaticVar(staticVariableAddress, dvar); - return; - } - - if (variablesProcesesed.contains(staticVariableAddress)) { - return; - } - - boolean external = diea.getBool(DW_AT_external, false); - - outputGlobal(staticVariableAddress, dvar.type, external, - DWARFSourceInfo.create(diea), dvar.dni); - } - else { - // If the expression evaluated to a static address of '0'. - // This case is probably caused by relocation fixups not being applied to the - // .debug_info section. - importSummary.relocationErrorVarDefs.add( - dvar.dni.getNamespacePath().asFormattedString() + " : " + - dvar.type.getPathName()); - } - } - - private void processZeroByteStaticVar(Address staticVariableAddress, DWARFVariable dvar) - throws InvalidInputException { - // because this is a zero-length data type (ie. array[0]), - // don't create a variable at the location since it will prevent other elements - // from occupying the same offset - Listing listing = currentProgram.getListing(); - String comment = - listing.getComment(CodeUnit.PRE_COMMENT, staticVariableAddress); - comment = (comment != null) ? comment + "\n" : ""; - comment += String.format("Zero length variable: %s: %s", dvar.dni.getOriginalName(), - dvar.type.getDisplayName()); - listing.setComment(staticVariableAddress, CodeUnit.PRE_COMMENT, comment); + Namespace namespace = globalVar.name.getParentNamespace(currentProgram); + String name = globalVar.name.getName(); + Address address = globalVar.getRamAddress(); + DataType dataType = globalVar.type; SymbolTable symbolTable = currentProgram.getSymbolTable(); - symbolTable.createLabel(staticVariableAddress, dvar.dni.getName(), - dvar.dni.getParentNamespace(currentProgram), - SourceType.IMPORTED); - } + Symbol labelSym = null; - private boolean isZeroByteDataType(DataType dt) { - if (!dt.isZeroLength() && dt instanceof Array) { - dt = DataTypeUtilities.getArrayBaseDataType((Array) dt); + if (globalVar.isZeroByte() || !variablesProcesesed.contains(address)) { + try { + labelSym = symbolTable.createLabel(address, name, namespace, SourceType.IMPORTED); + } + catch (InvalidInputException e) { + Msg.error(this, + String.format("Error creating label for global variable %s/%s at %s", + namespace, name, address)); + return; + } + } + + if (globalVar.isZeroByte()) { + // because this is a zero-length data type (ie. array[0]), + // don't create a variable at the location since it will prevent other elements + // from occupying the same offset + appendComment(address, CodeUnit.PRE_COMMENT, String.format( + "Zero length variable: %s: %s", name, dataType.getDisplayName()), "\n"); + + return; + } + + if (variablesProcesesed.contains(address)) { + return; + } + + labelSym.setPrimary(); + + if (globalVar.isExternal) { + setExternalEntryPoint(true, address); + } + + try { + if (dataType instanceof Dynamic || dataType instanceof FactoryDataType) { + appendComment(address, CodeUnit.EOL_COMMENT, + "Unsupported dynamic data type: " + dataType, "\n"); + dataType = Undefined.getUndefinedDataType(1); + } + DWARFDataInstanceHelper dih = new DWARFDataInstanceHelper(currentProgram); + if (!dih.isDataTypeCompatibleWithAddress(dataType, address)) { + appendComment(address, CodeUnit.EOL_COMMENT, String.format( + "Could not place DWARF static variable %s: %s @%s because existing data type conflicts.", + globalVar.name.getName(), dataType.getName(), address), "\n"); + } + else { + Data varData = DataUtilities.createData(currentProgram, address, dataType, -1, + ClearDataMode.CLEAR_ALL_CONFLICT_DATA); + if (varData != null && globalVar.sourceInfo != null) { + moveIntoFragment(name, varData.getMinAddress(), varData.getMaxAddress(), + globalVar.sourceInfo.getFilename()); + } + variablesProcesesed.add(address); + } + } + catch (CodeUnitInsertionException e) { + Msg.error(this, "Error creating data object at " + address, e); + } + importSummary.globalVarsAdded++; + + if (globalVar.sourceInfo != null) { + appendComment(address, CodeUnit.EOL_COMMENT, globalVar.sourceInfo.getDescriptionStr(), + "\n"); } - return dt.isZeroLength(); } - /** - * Process lexical block entries. - * - * @param entry - * DIE - * @param unit - * current compilation unit - * @param frameBase - * Location list of the current frame - * @param function - * parent function of the lexical block - * @throws IOException - * @throws InvalidInputException - * @throws DWARFExpressionException + /* + * Process lexical block entries inside of a function. + * + * This recursively processes any children of the lexical block diea via processFuncChildren(). */ private void processLexicalBlock(DIEAggregate diea, DWARFFunction dfunc) throws IOException, InvalidInputException, DWARFExpressionException { @@ -702,9 +453,6 @@ public class DWARFFunctionImporter { return; } - DWARFNameInfo dni = prog.getName(diea); - - String name = dni.getName(); Number lowPC = null; boolean disjoint = false; @@ -717,7 +465,7 @@ public class DWARFFunctionImporter { } // Otherwise process a range list else if (diea.hasAttribute(DW_AT_ranges)) { - List ranges = diea.parseDebugRange(DWARFAttribute.DW_AT_ranges); + List ranges = diea.parseDebugRange(DW_AT_ranges); // No range found if (ranges.isEmpty()) { @@ -727,18 +475,15 @@ public class DWARFFunctionImporter { lowPC = ranges.get(0).getFrom(); disjoint = ranges.size() > 1; } - else { - Msg.error(this, "LEXICAL BLOCK: No start and end ranges were found so the lexical " + - "block could not be processed."); - return; - } - Address blockStart = toAddr(lowPC); - if (name != null && importOptions.isOutputLexicalBlockComments()) { + Address blockStart = lowPC != null ? prog.getCodeAddress(lowPC) : null; + if (blockStart != null && importOptions.isOutputLexicalBlockComments()) { + DWARFNameInfo dni = prog.getName(diea); appendComment(blockStart, CodeUnit.PRE_COMMENT, - "Begin: " + name + (disjoint ? " - Disjoint" : ""), "\n"); + "Begin: " + dni.getName() + (disjoint ? " - Disjoint" : ""), "\n"); } - processFuncChildren(diea, dfunc); + processFuncChildren(diea, dfunc, + blockStart != null ? blockStart.subtract(dfunc.address) : -1); } private void processInlinedSubroutine(DIEAggregate diea, DWARFFunction dfunc) @@ -771,19 +516,18 @@ public class DWARFFunctionImporter { return; } + Address startAddr = prog.getCodeAddress(lowPC); + Address endAddr = prog.getCodeAddress(highPC); if (importOptions.isOutputInlineFuncComments()) { - addCommentsForInlineFunc(diea, toAddr(lowPC), toAddr(highPC)); + addCommentsForInlineFunc(diea, startAddr, endAddr); } - processFuncChildren(diea, dfunc); + processFuncChildren(diea, dfunc, startAddr.subtract(dfunc.address)); } - /** + /* * Constructs a function def signature for the function and adds it as a comment, either * EOL or PRE depending on how small the inline func is. - * @param diea - * @param blockStart - * @param blockEnd */ private void addCommentsForInlineFunc(DIEAggregate diea, Address blockStart, Address blockEnd) { FunctionDefinition funcDef = dwarfDTM.getFunctionSignature(diea); @@ -801,22 +545,13 @@ public class DWARFFunctionImporter { } } - /** - * Appends a comment at the specified address - * @param address the address to set the PRE comment - * @param commentType ie. CodeUnit.PRE_COMMENT - * @param comment the PRE comment - * @param sep the characters to use to separate existing comments - * @return true if the comment was successfully set - */ - private boolean appendComment(Address address, int commentType, String comment, String sep) { - AppendCommentCmd cmd = new AppendCommentCmd(address, commentType, comment, sep); - return cmd.applyTo(currentProgram); + private void appendComment(Address address, int commentType, String comment, String sep) { + DWARFUtil.appendComment(currentProgram, address, commentType, "", comment, sep); } - private final Address toAddr(Number offset) { - return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress( - offset.longValue(), true); + private void appendPlateComment(Address address, String prefix, String comment) { + DWARFUtil.appendComment(currentProgram, address, CodeUnit.PLATE_COMMENT, prefix, comment, + "\n"); } /** @@ -834,243 +569,6 @@ public class DWARFFunctionImporter { } } - private boolean isArrayDataTypeCompatibleWithExistingData(Array arrayDT, Address address) { - Listing listing = currentProgram.getListing(); - - // quick success - Data arrayData = listing.getDataAt(address); - if (arrayData != null && arrayData.getBaseDataType().isEquivalent(arrayDT)) { - return true; - } - - if (arrayData != null && arrayDT.getDataType() instanceof CharDataType && - arrayData.getBaseDataType() instanceof StringDataType) { - if (arrayData.getLength() >= arrayDT.getLength()) { - return true; - } - return DataUtilities.isUndefinedRange(currentProgram, - address.add(arrayData.getLength()), address.add(arrayDT.getLength() - 1)); - } - - // test each element - for (int i = 0; i < arrayDT.getNumElements(); i++) { - Address elementAddress = address.add(arrayDT.getElementLength() * i); - Data data = listing.getDataAt(elementAddress); - if (data != null && - !isDataTypeCompatibleWithExistingData(arrayDT.getDataType(), elementAddress)) { - return false; - } - } - - return true; - } - - private boolean isStructDataTypeCompatibleWithExistingData(Structure structDT, - Address address) { - for (DataTypeComponent dtc : structDT.getDefinedComponents()) { - Address memberAddress = address.add(dtc.getOffset()); - if (!isDataTypeCompatibleWithExistingData(dtc.getDataType(), memberAddress)) { - return false; - } - } - return true; - } - - private boolean isPointerDataTypeCompatibleWithExistingData(Pointer pdt, Address address) { - Listing listing = currentProgram.getListing(); - Data data = listing.getDataAt(address); - if (data == null) { - return true; - } - - DataType dataDT = data.getBaseDataType(); - return dataDT instanceof Pointer; - } - - private boolean isSimpleDataTypeCompatibleWithExistingData(DataType dataType, Address address) { - Listing listing = currentProgram.getListing(); - - Data data = listing.getDataAt(address); - if (data == null) { - return true; - } - - DataType dataDT = data.getBaseDataType(); - if (dataType instanceof CharDataType && dataDT instanceof StringDataType) { - return true; - } - - if (!dataType.getClass().isInstance(dataDT)) { - return false; - } - int dataTypeLen = dataType.getLength(); - if (dataTypeLen > 0 && dataTypeLen != data.getLength()) { - return false; - } - return true; - } - - private boolean isEnumDataTypeCompatibleWithExistingData(Enum enumDT, Address address) { - Listing listing = currentProgram.getListing(); - Data data = listing.getDataAt(address); - if (data == null) { - return true; - } - - DataType dataDT = data.getBaseDataType(); - if (!(dataDT instanceof Enum || dataDT instanceof AbstractIntegerDataType)) { - return false; - } - if (dataDT instanceof BooleanDataType) { - return false; - } - if (dataDT.getLength() != enumDT.getLength()) { - return false; - } - return true; - } - - private boolean isDataTypeCompatibleWithExistingData(DataType dataType, Address address) { - if (DataUtilities.isUndefinedRange(currentProgram, address, - address.add(dataType.getLength() - 1))) { - return true; - } - - if (dataType instanceof Array) { - return isArrayDataTypeCompatibleWithExistingData((Array) dataType, address); - } - if (dataType instanceof Pointer) { - return isPointerDataTypeCompatibleWithExistingData((Pointer) dataType, address); - } - if (dataType instanceof Structure) { - return isStructDataTypeCompatibleWithExistingData((Structure) dataType, address); - } - if (dataType instanceof TypeDef) { - return isDataTypeCompatibleWithExistingData(((TypeDef) dataType).getBaseDataType(), - address); - } - if (dataType instanceof Enum) { - return isEnumDataTypeCompatibleWithExistingData((Enum) dataType, address); - } - - if (dataType instanceof CharDataType || dataType instanceof StringDataType || - dataType instanceof IntegerDataType || dataType instanceof UnsignedIntegerDataType || - dataType instanceof BooleanDataType) { - return isSimpleDataTypeCompatibleWithExistingData(dataType, address); - } - - return false; - } - - private Data createVariable(Address address, DataType dataType, DWARFNameInfo dni) { - try { - String eolComment = null; - if (dataType instanceof Dynamic || dataType instanceof FactoryDataType) { - eolComment = "Unsupported dynamic data type: " + dataType; - dataType = Undefined.getUndefinedDataType(1); - } - if (!isDataTypeCompatibleWithExistingData(dataType, address)) { - appendComment(address, CodeUnit.EOL_COMMENT, - "Could not place DWARF static variable " + - dni.getNamespacePath().asFormattedString() + " : " + dataType + - " because existing data type conflicts.", - "\n"); - return null; - } - Data result = DataUtilities.createData(currentProgram, address, dataType, -1, - ClearDataMode.CLEAR_ALL_CONFLICT_DATA); - variablesProcesesed.add(address); - if (eolComment != null) { - appendComment(address, CodeUnit.EOL_COMMENT, eolComment, "\n"); - } - return result; - } - catch (CodeUnitInsertionException e) { - Msg.error(this, "Error creating data object at " + address, e); - } - return null; - } - - private void outputGlobal(Address address, DataType baseDataType, boolean external, - DWARFSourceInfo sourceInfo, DWARFNameInfo dni) { - - Namespace namespace = dni.getParentNamespace(currentProgram); - - SymbolTable symbolTable = currentProgram.getSymbolTable(); - try { - symbolTable.createLabel(address, dni.getName(), namespace, SourceType.IMPORTED); - SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(address, dni.getName(), namespace); - cmd.applyTo(currentProgram); - } - catch (InvalidInputException e) { - Msg.error(this, - "Error creating symbol " + namespace + "/" + dni.getName() + " at " + address); - return; - } - - setExternalEntryPoint(external, address); - - Data varData = createVariable(address, baseDataType, dni); - importSummary.globalVarsAdded++; - - if (sourceInfo != null) { - appendComment(address, CodeUnit.EOL_COMMENT, sourceInfo.getDescriptionStr(), "\n"); - - if (varData != null) { - moveIntoFragment(dni.getName(), varData.getMinAddress(), varData.getMaxAddress(), - sourceInfo.getFilename()); - } - } - } - - /** - * Get the location that corresponds to the entry point of the function If - * there is only a single location, assume it applies to whole function - * - * @param locList - * @param funcAddr - * @return the byte array corresponding to the location expression - */ - private static DWARFLocation getTopLocation(List locList, long funcAddr) { - if (locList.size() == 1) { - return locList.get(0); - } - for (DWARFLocation loc : locList) { - if (loc.getRange().getFrom() == funcAddr) { - return loc; - } - } - return null; - } - - private static int findFirstUse(Program currentProgram, Register register, long funcAddr, - long firstUseAddr) { - // look for the first write to this register within this range. - Address entry = currentProgram.getMinAddress().getNewAddress(firstUseAddr); - InstructionIterator instructions = currentProgram.getListing().getInstructions(entry, true); - while (instructions.hasNext()) { - Instruction instruction = instructions.next(); - - FlowType flowType = instruction.getFlowType(); - if (flowType.isTerminal()) { - return 0; - } - Object[] resultObjects = instruction.getResultObjects(); - for (int i = 0; i < resultObjects.length; i++) { - if (!(resultObjects[i] instanceof Register)) { - continue; - } - Register outReg = (Register) resultObjects[i]; - if (register.equals(outReg)) { - long offset = instruction.getMinAddress().getOffset() - funcAddr; - return (int) offset; - } - } - } - // return the offset from the function entry to the real first use - return 0; - } - /** * Move an address range into a fragment. * @param cu current compile unit @@ -1121,136 +619,16 @@ public class DWARFFunctionImporter { } } - /** - * For some DWARF debugger strategies, the storage location provided for a formal parameter is NOT the initial storage - * of the parameter and does not match the calling convention. If the storage location provided is in the local variable - * range for the function, this is an indication the storage does not represent the calling convention - * @param dfunc is the DWARF function data to test - * @return true if the storage locations represent the calling convention - */ -// private boolean evaluateParameterStorage(DWARFFunction dfunc) { -// if (!prog.getRegisterMappings().isUseFormalParameterStorage()) { -// return false; -// } -// for (int i = 0; i < dfunc.params.size(); ++i) { -// DWARFVariable var = dfunc.params.get(i); -// if (var.reg == null) { -// boolean paramsHavePositiveOffset = stackGrowsNegative; -// if (!var.isStackOffset || -// // double check for valid param offset -// (paramsHavePositiveOffset && var.offset < 0) || -// (!paramsHavePositiveOffset && var.offset >= 0)) { -// return false; -// } -// } -// if (var.type == null) { -// // this can happen when a parameter doesn't fit into the register that -// // the dwarf expression helper decoded as the parameter's location. -// return false; -// } -// } -// return true; -// } - - private Variable buildVariable(DWARFVariable dvar) throws InvalidInputException { - Varnode[] vnarray = buildVarnodes(dvar); - VariableStorage storage = new VariableStorage(currentProgram, vnarray); - int firstUseOffset = 0; - if ((dvar.reg != null) && (dvar.offset != -1)) { - firstUseOffset = (int) dvar.offset; - } - return new LocalVariableImpl(dvar.dni.getName(), firstUseOffset, dvar.type, storage, - currentProgram); - } - - private Variable buildReturnVariable(DWARFVariable dvar) throws InvalidInputException { - if (dvar == null) { - return new ReturnParameterImpl(DataType.VOID, currentProgram); - } - VariableStorage storage; - Varnode[] vnarray = buildVarnodes(dvar); - if (vnarray == null) { - storage = VariableStorage.UNASSIGNED_STORAGE; - } - else { - storage = new VariableStorage(currentProgram, vnarray); - } - return new ReturnParameterImpl(dvar.type, storage, currentProgram); - } - - private Parameter buildParameter(Function function, int i, DWARFVariable dvar, - DIEAggregate funcDIEA) throws InvalidInputException { - VariableStorage storage; - Varnode[] vnarray = buildVarnodes(dvar); - if (vnarray == null) { - storage = VariableStorage.UNASSIGNED_STORAGE; - } - else { - storage = new VariableStorage(currentProgram, vnarray); - } - - return new ParameterImpl(dvar.dni.getName(), dvar.type, storage, currentProgram); - } - - private Varnode[] buildVarnodes(DWARFVariable dvar) { - if (dvar.type == null) { - return null; - } - Varnode[] retarray = null; - int typesize = dvar.type.getLength(); - if (dvar.reg != null) { - retarray = new Varnode[1]; - if (prog.isBigEndian() && (dvar.reg.getMinimumByteSize() > typesize)) { - retarray[0] = new Varnode( - dvar.reg.getAddress().add(dvar.reg.getMinimumByteSize() - typesize), typesize); - } - else { - retarray[0] = new Varnode(dvar.reg.getAddress(), typesize); - } - } - else if (dvar.isStackOffset) { - retarray = new Varnode[1]; - retarray[0] = new Varnode( - currentProgram.getAddressFactory().getStackSpace().getAddress(dvar.offset), - typesize); - } - return retarray; - } - - private boolean checkThisParameter(DWARFVariable var, DIEAggregate diea) { - // If the variable is not named, check to see if the datatype is the same - // as the parent entry - if (Function.THIS_PARAM_NAME.equals(var.dni.getName())) { - return true; - } - - // Check for a parent class - DIEAggregate parentDIEA = diea.getParent(); - if (parentDIEA != null && parentDIEA.isStructureType()) { - DataType parentDT = dwarfDTM.getDataType(parentDIEA, null); - // Check to see if the parent data type equals the parameters' data type - if (parentDT != null && parentDT == var.type) { - if (!var.dni.isAnon()) { - Msg.error(this, "WARNING: Renaming " + var.dni.getName() + " to " + - Function.THIS_PARAM_NAME); - } - var.dni = var.dni.replaceName(Function.THIS_PARAM_NAME, Function.THIS_PARAM_NAME); - return true; - } - } - return false; - } - private Function createFunction(DWARFFunction dfunc, DIEAggregate diea) { try { // create a new symbol if one does not exist (symbol table will figure this out) SymbolTable symbolTable = currentProgram.getSymbolTable(); - symbolTable.createLabel(dfunc.address, dfunc.dni.getName(), dfunc.namespace, + symbolTable.createLabel(dfunc.address, dfunc.name.getName(), dfunc.namespace, SourceType.IMPORTED); // force new label to become primary (if already a function it will become function name) SetLabelPrimaryCmd cmd = - new SetLabelPrimaryCmd(dfunc.address, dfunc.dni.getName(), dfunc.namespace); + new SetLabelPrimaryCmd(dfunc.address, dfunc.name.getName(), dfunc.namespace); cmd.applyTo(currentProgram); setExternalEntryPoint(dfunc.isExternal, dfunc.address); @@ -1260,40 +638,20 @@ public class DWARFFunctionImporter { // TODO: If not contained within program memory should they be considered external? - if (!currentProgram.getMemory().getLoadedAndInitializedAddressSet().contains( - dfunc.address)) { + if (!currentProgram.getMemory() + .getLoadedAndInitializedAddressSet() + .contains(dfunc.address)) { Msg.warn(this, - "Unable to create function not contained within loaded memory (" + - dfunc.address + ") " + dfunc.namespace + "/" + dfunc.dni.getName()); + String.format( + "DWARF: unable to create function not contained within loaded memory: %s@%s", + dfunc.name, dfunc.address)); return null; } // create 1-byte function if one does not exist - primary label will become function names - function = currentProgram.getFunctionManager().createFunction(null, dfunc.address, - new AddressSet(dfunc.address), SourceType.IMPORTED); - } - - DWARFSourceInfo sourceInfo = DWARFSourceInfo.create(diea); - if (sourceInfo != null) { - // Move the function into the program tree of the file - moveIntoFragment(function.getName(), dfunc.address, - dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1), - sourceInfo.getFilename()); - - if (importOptions.isOutputSourceLocationInfo()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - sourceInfo.getDescriptionStr(), "\n"); - } - } - if (importOptions.isOutputDIEInfo()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - "DWARF DIE: " + diea.getHexOffset(), "\n"); - } - - DWARFNameInfo dni = prog.getName(diea); - if (dni.isNameModified()) { - appendComment(dfunc.address, CodeUnit.PLATE_COMMENT, - "Original name: " + dni.getOriginalName(), "\n"); + function = currentProgram.getFunctionManager() + .createFunction(null, dfunc.address, new AddressSet(dfunc.address), + SourceType.IMPORTED); } return function; @@ -1303,180 +661,11 @@ public class DWARFFunctionImporter { } catch (InvalidInputException e) { Msg.error(this, "Failed to create function " + dfunc.namespace + "/" + - dfunc.dni.getName() + ": " + e.getMessage()); + dfunc.name.getName() + ": " + e.getMessage()); } return null; } - /** - * Changes the names of the parameters in the array to unique names that won't conflict with - * any other names in the function's namespace when the parameters are used to replace - * the existing parameters in the function. Appends an integer number to - * the base name if necessary to create a unique name in the function's namespace. - * @param function the function - * @param parameters the parameters that need names that won't conflict. These should be - * Impl objects and not DB objects since their names will be changed within this method. - * @throws InvalidInputException invalid parameter name - * @throws DuplicateNameException (should not occur on non-DB parameter) - */ - private void setUniqueParameterNames(Function function, List parameters) - throws DuplicateNameException, InvalidInputException { - SymbolTable symbolTable = currentProgram.getSymbolTable(); - // Create a set containing all the unique parameter names determined so far so they can - // be avoided as additional parameter names are determined. - Set namesSoFar = new HashSet<>(); - for (int ordinal = 0; ordinal < parameters.size(); ordinal++) { - Parameter parameter = parameters.get(ordinal); - String baseName = parameter.getName(); - if (ordinal == 0 && Function.THIS_PARAM_NAME.equals(baseName)) { - continue; - } - String uniqueName = - getUniqueReplacementParameterName(symbolTable, function, baseName, namesSoFar); - namesSoFar.add(uniqueName); - parameter.setName(uniqueName, SourceType.IMPORTED); - } - } - - /** - * Get a unique parameter name for a parameter when all parameter names are being replaced. - * If the specified name is a default parameter name then the original default name passed - * in is returned. - * @param symbolTable the symbol table containing symbols for the indicated namespace - * @param namespace the namespace containing symbol names to check. - * @param baseName the base name to append with an integer number if necessary - * to create a unique name. - * @param namesNotToBeUsed set of names that should not be used when determining a unique name. - * @return a unique parameter name - */ - private static String getUniqueReplacementParameterName(SymbolTable symbolTable, - Function function, String name, Set namesNotToBeUsed) { - if (name == null || SymbolUtilities.isDefaultParameterName(name)) { - return name; - } - return getUniqueNameIgnoringCurrentParameters(symbolTable, function, name, - namesNotToBeUsed); - } - - /** - * Gets a unique name in the indicated namespace by appending an integer number if necessary - * and ignoring any conflicts with existing parameters. - * @param symbolTable the symbol table containing symbols for the indicated namespace - * @param namespace the namespace containing symbol names to check. - * @param baseName the base name to append with an integer number if necessary to create a - * unique name. - * @param namesNotToBeUsed set of names that should not be used when determining a unique name. - * @return an unused unique name within the namespace ignoring current parameter names and - * that doesn't conflict with any in the set of names not to be used. - */ - private static String getUniqueNameIgnoringCurrentParameters(SymbolTable symbolTable, - Namespace namespace, String baseName, Set namesNotToBeUsed) { - String name = baseName; - if (name != null) { - // establish unique name - int cnt = 0; - List symbols = symbolTable.getSymbols(name, namespace); - while (!symbols.isEmpty()) { - if (namesNotToBeUsed.contains(name)) { - continue; - } - if (areAllParamaters(symbols)) { - return name; - } - name = baseName + "_" + (++cnt); - symbols = symbolTable.getSymbols(name, namespace); - } - } - return name; - } - - private static boolean areAllParamaters(List symbols) { - for (Symbol symbol : symbols) { - if (symbol.getSymbolType() != SymbolType.PARAMETER) { - return false; - } - } - return true; - } - - private void commitPrototype(Function function, Variable returnVariable, - List params, PrototypeModel protoModel) - throws InvalidInputException, DuplicateNameException { - - CompilerSpec compilerSpec = currentProgram.getCompilerSpec(); - - if (protoModel == null) { - Parameter[] paramarray = params.toArray(Parameter[]::new); - protoModel = compilerSpec.findBestCallingConvention(paramarray); - } - - try { - function.updateFunction(protoModel.getName(), returnVariable, params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); - } - catch (DuplicateNameException e) { - setUniqueParameterNames(function, params); - function.updateFunction(protoModel.getName(), returnVariable, params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED); - } - -// TODO: Determination of storage is unreliable and frequently forces incorrect storage to be used -// if (useCustomStorageIfNeeded && -// !VariableUtilities.storageMatches(params, function.getParameters())) { -// // try again if dynamic storage assignment does not match what DWARF specified -// // force into custom storage mode -// function.updateFunction(protoModel.getName(), null, params, -// FunctionUpdateType.CUSTOM_STORAGE, true, SourceType.IMPORTED); -// } - } - - private void commitLocal(Function func, DWARFVariable dvar) throws InvalidInputException { - // Attempt to add the variable - Variable var = buildVariable(dvar); - - // check for an existing local variable with conflict storage. - boolean hasConflict = false; - for (Variable existingVar : func.getAllVariables()) { - if (existingVar.getFirstUseOffset() == var.getFirstUseOffset() && - existingVar.getVariableStorage().intersects(var.getVariableStorage())) { - if ((existingVar instanceof LocalVariable) && - Undefined.isUndefined(existingVar.getDataType())) { - // ignore locals with undefined type - they will be removed below - continue; - } - hasConflict = true; - break; - } - } - if (hasConflict) { - appendComment(func.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT, - "Scope for omitted local variable " + var.toString() + " starts here", "; "); - return; - } - - try { - VariableUtilities.checkVariableConflict(func, null, var.getVariableStorage(), true); - func.addLocalVariable(var, SourceType.IMPORTED); - } - catch (DuplicateNameException e) { - int count = 1; - // Add the variable with an unused name - String baseName = var.getName(); - while (!monitor.isCancelled()) { - try { - var.setName(baseName + "_" + Integer.toString(count), SourceType.IMPORTED); - func.addLocalVariable(var, SourceType.IMPORTED); - } - catch (DuplicateNameException e1) { - count++; - continue; - } - break; - } - } - - } - private void processLabel(DIEAggregate diea) { if (!shouldProcess(diea)) { return; @@ -1484,7 +673,7 @@ public class DWARFFunctionImporter { String name = prog.getEntryName(diea); if (name != null && diea.hasAttribute(DW_AT_low_pc)) { - Address address = toAddr(diea.getLowPC(0)); + Address address = prog.getCodeAddress(diea.getLowPC(0)); if (address.getOffset() != 0) { try { SymbolTable symbolTable = currentProgram.getSymbolTable(); @@ -1504,36 +693,4 @@ public class DWARFFunctionImporter { } } - /** - * Holds values necessary to create a new variable / parameter. - */ - static class DWARFVariable { - public DWARFNameInfo dni; - public DataType type; - public long offset;// Offset on stack or firstuseoffset if this is a register - public boolean isStackOffset;// true if offset represents stack offset - public long lexicalOffset; - public Register reg; - } - - /** - * Holds values necessary to create a new function - */ - static class DWARFFunction { - public Address address; - public Address highAddress; - public DWARFNameInfo dni; - public Namespace namespace; - public DWARFVariable retval; - public boolean isExternal; - public long frameBase; - public List params = new ArrayList<>(); - public List local = new ArrayList<>(); - public boolean varArg; - public boolean localVarErrors; // set to true if problem w/local var decoding - - public DWARFFunction(DWARFNameInfo dni) { - this.dni = dni; - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java index 35686c66a6..454a46b964 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFParser.java @@ -15,10 +15,11 @@ */ package ghidra.app.util.bin.format.dwarf4.next; -import java.io.IOException; import java.util.Collections; import java.util.List; +import java.io.IOException; + import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; import ghidra.app.util.bin.format.dwarf4.DWARFException; import ghidra.program.model.data.*; @@ -37,19 +38,11 @@ public class DWARFParser { private DWARFProgram prog; private DWARFDataTypeManager dwarfDTM; private TaskMonitor monitor; - private DWARFImportOptions importOptions; - private DWARFImportSummary importSummary = new DWARFImportSummary(); - public DWARFParser(DWARFProgram prog, DataTypeManager builtInDTM, TaskMonitor monitor) { + public DWARFParser(DWARFProgram prog, TaskMonitor monitor) { this.prog = prog; this.monitor = monitor; - this.importOptions = prog.getImportOptions(); - this.dwarfDTM = new DWARFDataTypeManager(prog, prog.getGhidraProgram().getDataTypeManager(), - builtInDTM, importSummary); - } - - public DWARFImportOptions getImportOptions() { - return importOptions; + this.dwarfDTM = prog.getDwarfDTM(); } /** @@ -193,8 +186,10 @@ public class DWARFParser { monitor.setIndeterminate(false); monitor.setShowProgressValue(true); - long start_ts = System.currentTimeMillis(); + DWARFImportOptions importOptions = prog.getImportOptions(); + DWARFImportSummary importSummary = prog.getImportSummary(); + long start_ts = System.currentTimeMillis(); if (importOptions.isImportDataTypes()) { dwarfDTM.importAllDataTypes(monitor); prog.getGhidraProgram().flushEvents(); @@ -203,8 +198,7 @@ public class DWARFParser { if (importOptions.isImportFuncs()) { long funcstart_ts = System.currentTimeMillis(); - DWARFFunctionImporter dfi = - new DWARFFunctionImporter(prog, dwarfDTM, importOptions, importSummary, monitor); + DWARFFunctionImporter dfi = new DWARFFunctionImporter(prog, monitor); dfi.importFunctions(); importSummary.funcsElapsedMS = System.currentTimeMillis() - funcstart_ts; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java index 3d8f0879fa..339b04f680 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFProgram.java @@ -31,6 +31,8 @@ import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException; import ghidra.app.util.bin.format.dwarf4.external.ExternalDebugInfo; import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.*; import ghidra.app.util.opinion.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.CategoryPath; import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Program; @@ -47,9 +49,13 @@ import ghidra.util.task.TaskMonitor; */ public class DWARFProgram implements Closeable { public static final String DWARF_ROOT_NAME = "DWARF"; + public static final CategoryPath DWARF_ROOT_CATPATH = CategoryPath.ROOT.extend(DWARF_ROOT_NAME); + public static final CategoryPath UNCAT_CATPATH = DWARF_ROOT_CATPATH.extend("_UNCATEGORIZED_"); + public static final int DEFAULT_NAME_LENGTH_CUTOFF = SymbolUtilities.MAX_SYMBOL_NAME_LENGTH; public static final int MAX_NAME_LENGTH_CUTOFF = SymbolUtilities.MAX_SYMBOL_NAME_LENGTH; public static final int MIN_NAME_LENGTH_CUTOFF = 20; + private static final int NAME_HASH_REPLACEMENT_SIZE = 8 + 2 + 2; private static final String ELLIPSES_STR = "..."; @@ -61,7 +67,7 @@ public class DWARFProgram implements Closeable { * program sections, or their compressed "z" versions. *

* If the program is a MachO binary (ie. Mac), it must have a ".dSYM" directory co-located next to the - * original binary file on the native filesystem. (ie. outside of Ghidra). See the DSymSectionProvider + * original binary file on the native filesystem. (lie. outside of Ghidra). See the DSymSectionProvider * for more info. *

* @param program {@link Program} to test @@ -114,10 +120,9 @@ public class DWARFProgram implements Closeable { private final Program program; private DWARFImportOptions importOptions; - private DWARFNameInfo rootDNI = - DWARFNameInfo.createRoot(new CategoryPath(CategoryPath.ROOT, DWARF_ROOT_NAME)); - private DWARFNameInfo unCatDataTypeRoot = DWARFNameInfo.createRoot( - new CategoryPath(rootDNI.getOrganizationalCategoryPath(), "_UNCATEGORIZED_")); + private DWARFImportSummary importSummary; + private DWARFNameInfo rootDNI = DWARFNameInfo.createRoot(DWARF_ROOT_CATPATH); + private DWARFNameInfo unCatDataTypeRoot = DWARFNameInfo.createRoot(UNCAT_CATPATH); private DWARFSectionProvider sectionProvider; private StringTable debugStrings; @@ -173,6 +178,12 @@ public class DWARFProgram implements Closeable { */ private ListValuedMap typeReferers = new ArrayListValuedHashMap<>(); + private final DWARFDataTypeManager dwarfDTM; + + private final boolean stackGrowsNegative; + + private final Map opaqueProps = new HashMap<>(); + /** * Main constructor for DWARFProgram. *

@@ -213,13 +224,15 @@ public class DWARFProgram implements Closeable { this.program = program; this.sectionProvider = sectionProvider; this.importOptions = importOptions; + this.importSummary = new DWARFImportSummary(); this.nameLengthCutoffSize = Math.max(MIN_NAME_LENGTH_CUTOFF, Math.min(importOptions.getNameLengthCutoff(), MAX_NAME_LENGTH_CUTOFF)); + this.dwarfDTM = new DWARFDataTypeManager(this, program.getDataTypeManager()); + this.stackGrowsNegative = program.getCompilerSpec().stackGrowsNegative(); monitor.setMessage("Reading DWARF debug string table"); this.debugStrings = StringTable.readStringTable( sectionProvider.getSectionAsByteProvider(DWARFSectionNames.DEBUG_STR, monitor)); -// Msg.info(this, "Read DWARF debug string table, " + debugStrings.getByteCount() + " bytes."); this.attributeFactory = new DWARFAttributeFactory(this); @@ -249,7 +262,7 @@ public class DWARFProgram implements Closeable { @Override public void close() throws IOException { - sectionProvider = null; + sectionProvider.close(); compUnits.clear(); debugAbbrBR = null; debugInfoBR = null; @@ -265,10 +278,18 @@ public class DWARFProgram implements Closeable { return importOptions; } + public DWARFImportSummary getImportSummary() { + return importSummary; + } + public Program getGhidraProgram() { return program; } + public DWARFDataTypeManager getDwarfDTM() { + return dwarfDTM; + } + public boolean isBigEndian() { return program.getLanguage().isBigEndian(); } @@ -443,6 +464,7 @@ public class DWARFProgram implements Closeable { String origName = isAnon ? null : name; String workingName = ensureSafeNameLength(name); + workingName = fixupSpecialMeaningCharacters(workingName); DWARFNameInfo result = parentDNI.createChild(origName, workingName, DWARFUtil.getSymbolTypeFromDIE(diea)); @@ -548,6 +570,16 @@ public class DWARFProgram implements Closeable { return strs; } + private String fixupSpecialMeaningCharacters(String s) { + // golang specific hacks: + // "\u00B7" -> "." + // "\u2215" -> "/" + if (s.contains("\u00B7") || s.contains("\u2215")) { + s = s.replaceAll("\u00B7", ".").replaceAll("\u2215", "/"); + } + return s; + } + public DWARFNameInfo getName(DIEAggregate diea) { DWARFNameInfo dni = lookupDNIByOffset(diea.getOffset()); if (dni == null) { @@ -717,6 +749,10 @@ public class DWARFProgram implements Closeable { return debugStrings; } + public AddressSpace getStackSpace() { + return program.getAddressFactory().getStackSpace(); + } + public DWARFAttributeFactory getAttributeFactory() { return attributeFactory; } @@ -1011,4 +1047,29 @@ public class DWARFProgram implements Closeable { public long getProgramBaseAddressFixup() { return programBaseAddressFixup; } + + public Address getCodeAddress(Number offset) { + return program.getAddressFactory() + .getDefaultAddressSpace() + .getAddress(offset.longValue(), true); + } + + public Address getDataAddress(Number offset) { + return program.getAddressFactory() + .getDefaultAddressSpace() + .getAddress(offset.longValue(), true); + } + + public boolean stackGrowsNegative() { + return stackGrowsNegative; + } + + public T getOpaqueProperty(Object key, T defaultValue, Class valueClass) { + Object obj = opaqueProps.get(key); + return obj != null && valueClass.isInstance(obj) ? valueClass.cast(obj) : defaultValue; + } + + public void setOpaqueProperty(Object key, Object value) { + opaqueProps.put(key, value); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsManager.java index d9f2e4f7db..385e2add5e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsManager.java @@ -15,16 +15,18 @@ */ package ghidra.app.util.bin.format.dwarf4.next; -import java.io.IOException; -import java.io.InputStream; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.io.IOException; +import java.io.InputStream; + import org.jdom.*; import org.jdom.input.SAXBuilder; import generic.jar.ResourceFile; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; import ghidra.program.model.lang.*; import ghidra.util.Msg; import ghidra.util.xml.XmlUtilities; @@ -38,20 +40,6 @@ public class DWARFRegisterMappingsManager { private static Map cache = new HashMap<>(); - /** - * Returns true if the specified {@link LanguageDescription} has DWARF - * register mappings. - * - * @param langDesc The {@link LanguageDescription} to test - * @return true if the language has a DWARF register mapping specified - * @throws IOException if there was an error in the language LDEF file. - */ - public static boolean hasDWARFRegisterMapping(LanguageDescription langDesc) throws IOException { - return (langDesc instanceof SleighLanguageDescription) && - getDWARFRegisterMappingFileNameFromLangDesc( - (SleighLanguageDescription) langDesc) != null; - } - /** * Returns true if the specified {@link Language} has DWARF register * mappings. @@ -61,7 +49,7 @@ public class DWARFRegisterMappingsManager { * @throws IOException if there was an error in the language LDEF file. */ public static boolean hasDWARFRegisterMapping(Language lang) throws IOException { - return hasDWARFRegisterMapping(lang.getLanguageDescription()); + return DWARFUtil.getLanguageExternalFile(lang, DWARF_REGISTER_MAPPING_NAME) != null; } /** @@ -84,51 +72,6 @@ public class DWARFRegisterMappingsManager { return result; } - /* - * Returns the DWARF register mapping file specified in the lang's definition, or - * null if it does not exist. - */ - private static String getDWARFRegisterMappingFileNameFromLangDesc( - SleighLanguageDescription langDesc) throws IOException { - List dwarfSpecFilename = langDesc.getExternalNames(DWARF_REGISTER_MAPPING_NAME); - if (dwarfSpecFilename == null) { - return null; - } - if (dwarfSpecFilename.size() > 1) { - throw new IOException("Multiple DWARF register mappings found for language " + - langDesc.getLanguageID() + ": " + dwarfSpecFilename.toString()); - } - return dwarfSpecFilename.get(0); - } - - /** - * Returns {@link ResourceFile} that should contain the specified language's - * DWARF register mapping, never null. - * - * @param lang {@link Language} to find the mapping file for. - * @return {@link ResourceFile} of where the mapping file should be, never - * null. - * @throws IOException if not a Sleigh language or no mapping specified or - * multiple mappings specified. - */ - public static ResourceFile getDWARFRegisterMappingFileFor(Language lang) throws IOException { - LanguageDescription langDesc = lang.getLanguageDescription(); - if (!(langDesc instanceof SleighLanguageDescription)) { - throw new IOException("Not a Sleigh Language: " + lang.getLanguageID()); - } - SleighLanguageDescription sld = (SleighLanguageDescription) langDesc; - ResourceFile defsFile = sld.getDefsFile(); - ResourceFile parentFile = defsFile.getParentFile(); - String dwarfSpecFilename = getDWARFRegisterMappingFileNameFromLangDesc(sld); - if (dwarfSpecFilename == null) { - throw new IOException("No DWARF register mapping information found for language " + - lang.getLanguageID().getIdAsString()); - } - ResourceFile dwarfFile = new ResourceFile(parentFile, dwarfSpecFilename); - - return dwarfFile; - } - /** * Finds the DWARF register mapping information file specified in the * specified language's LDEF file and returns a new @@ -149,7 +92,12 @@ public class DWARFRegisterMappingsManager { * in the register mapping data. */ public static DWARFRegisterMappings readMappingForLang(Language lang) throws IOException { - ResourceFile dwarfFile = getDWARFRegisterMappingFileFor(lang); + ResourceFile dwarfFile = + DWARFUtil.getLanguageExternalFile(lang, DWARF_REGISTER_MAPPING_NAME); + if (dwarfFile == null) { + throw new IOException("Missing DWARF register mapping file for language " + + lang.getLanguageID().getIdAsString()); + } if (!dwarfFile.exists()) { throw new IOException("Missing DWARF register mapping file " + dwarfFile + " for language " + lang.getLanguageID().getIdAsString()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFSourceInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFSourceInfo.java index f90806876b..593fea9a90 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFSourceInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFSourceInfo.java @@ -15,9 +15,12 @@ */ package ghidra.app.util.bin.format.dwarf4.next; +import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.DW_AT_decl_line; +import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_formal_parameter; + import ghidra.app.util.bin.format.dwarf4.DIEAggregate; import ghidra.app.util.bin.format.dwarf4.DebugInfoEntry; -import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute; +import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute; /** * Small class to hold the filename and line number info values from @@ -34,12 +37,23 @@ public class DWARFSourceInfo { * @return new {@link DWARFSourceInfo} with filename:linenum info, or null if no info present in DIEA. */ public static DWARFSourceInfo create(DIEAggregate diea) { - String file = diea.getSourceFile(); - int lineNum = (int) diea.getUnsignedLong(DWARFAttribute.DW_AT_decl_line, -1); + DIEAggregate currentDIEA = diea; + String file = null; + while (currentDIEA != null && (file = currentDIEA.getSourceFile()) == null) { + currentDIEA = currentDIEA.getParent(); + } + if (file == null) { + return null; + } + DWARFNumericAttribute declLineAttr = diea.findAttributeInChildren(DW_AT_decl_line, + DW_TAG_formal_parameter, DWARFNumericAttribute.class); // TODO: what other children might have a line number attribute? + if (declLineAttr == null) { + return null; + } - return (file != null && lineNum != -1) - ? new DWARFSourceInfo(file, lineNum) - : null; + int lineNum = (int) declLineAttr.getUnsignedValue(); + + return new DWARFSourceInfo(file, lineNum); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java new file mode 100644 index 0000000000..c3f6d14fe0 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/DWARFVariable.java @@ -0,0 +1,552 @@ +/* ### + * 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.dwarf4.next; + +import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.*; + +import java.io.IOException; +import java.util.*; + +import ghidra.app.util.bin.format.dwarf4.*; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag; +import ghidra.app.util.bin.format.dwarf4.expression.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.SymbolType; +import ghidra.util.Msg; +import ghidra.util.exception.InvalidInputException; + +/** + * Represents a function parameter, local variable, or global variable. + */ +public class DWARFVariable { + /** + * Creates an unnamed, storage-less {@link DWARFVariable} from a DataType. + * + * @param dt {@link DataType} of the variable + * @return new {@link DWARFVariable}, never null + */ + public static DWARFVariable fromDataType(DWARFFunction dfunc, DataType dt) { + return new DWARFVariable(dfunc.getProgram(), dfunc, dt); + } + + /** + * Reads a parameter. + * + * @param diea {@link DIEAggregate} DW_TAG_formal_parameter + * @param dfunc {@link DWARFFunction} that this parameter is attached to + * @param paramOrdinal + * @return new parameter, never null, possibly without storage info + * @throws IOException if error + */ + public static DWARFVariable readParameter(DIEAggregate diea, DWARFFunction dfunc, + int paramOrdinal) { + + DWARFVariable dvar = new DWARFVariable(dfunc, diea); + dvar.isOutputParameter = diea.getBool(DW_AT_variable_parameter, false); + dvar.isThis = paramOrdinal == 0 && DWARFUtil.isThisParam(diea); + dvar.readParamStorage(diea); + + return dvar; + } + + /** + * Reads a local variable. + * + * @param diea {@link DIEAggregate} DW_TAG_variable + * @param dfunc {@link DWARFFunction} that this local var belongs to + * @return new DWARFVariable that represents a local var, or null if + * error reading storage info + */ + public static DWARFVariable readLocalVariable(DIEAggregate diea, DWARFFunction dfunc, + long offsetFromFuncStart) { + // local variable without location information is useless, so return null in those cases + DWARFVariable dvar = new DWARFVariable(dfunc, diea); + dvar.lexicalOffset = offsetFromFuncStart; + + return dvar.readLocalVariableStorage(diea) ? dvar : null; + } + + /** + * Reads a static/global variable. + * + * @param diea {@link DIEAggregate} DW_TAG_variable + * @return new {@link DWARFVariable} that represents the global variable, or + * null if error reading storage info + */ + public static DWARFVariable readGlobalVariable(DIEAggregate diea) { + DWARFVariable dvar = new DWARFVariable(null, diea); + SymbolType globalVarSymbolType = null; // TODO: need better symbol type, nothing matches static global var + dvar.name = dvar.name.replaceType(globalVarSymbolType); + + return dvar.readGlobalStorage(diea) ? dvar : null; + } + + private final DWARFProgram program; + private final DWARFFunction dfunc; + public DWARFNameInfo name; + public DataType type; + public long lexicalOffset; // offset inside function where variable storage becomes valid + public boolean isOutputParameter; // changes to parameter value escape back to the calling location + public boolean isExternal; + public boolean isThis; + public DWARFSourceInfo sourceInfo; + private List storage = new ArrayList<>(); + private Varnode stackStorage; // any stack storage is forced to be last in the storage list + private String comment; + + private DWARFVariable(DWARFProgram program, DWARFFunction dfunc, DataType type) { + this.program = program; + this.dfunc = dfunc; + this.type = type; + } + + private DWARFVariable(DWARFFunction dfunc, DIEAggregate diea) { + this.program = diea.getProgram(); + this.dfunc = dfunc; + this.name = program.getName(diea); + this.type = program.getDwarfDTM().getDataTypeForVariable(diea.getTypeRef()); + this.isExternal = diea.getBool(DW_AT_external, false); + this.sourceInfo = DWARFSourceInfo.create(diea); + } + + /** + * Assign storage for this variable in a ram data location. + * + * @param offset address offset + * @param prog {@link DWARFProgram} + */ + public void setRamStorage(long offset) { + clearStorage(); + addRamStorage(offset); + } + + public void addRamStorage(long offset) { + storage.add(new Varnode(program.getDataAddress(offset), type.getLength())); + } + + /** + * Assign storage for this variable at a stack offset. + * + * @param offset stack offset + * @param prog {@link DWARFProgram} + */ + public void setStackStorage(long offset) { + clearStorage(); + addStackStorage(offset, type.getLength()); + } + + public void addStackStorage(long offset, int length) { + if (stackStorage == null) { + stackStorage = new Varnode(program.getStackSpace().getAddress(offset), length); + } + else { + if (stackStorage.getOffset() + stackStorage.getSize() > offset) { + throw new IllegalArgumentException("Overlaps previous stack allocation"); + } + stackStorage = new Varnode(program.getStackSpace().getAddress(stackStorage.getOffset()), + (int) (offset - stackStorage.getOffset() + length)); + } + } + + /** + * Assign storage for this variable via a list of registers. + * + * @param registers registers that contain the data + * @param prog {@link DWARFProgram} + */ + public void setRegisterStorage(List registers) { + clearStorage(); + addRegisterStorage(registers); + } + + public void addRegisterStorage(List registers) { + List varnodes = + DWARFUtil.convertRegisterListToVarnodeStorage(registers, type.getLength()); + storage.addAll(varnodes); + } + + /** + * @return true if this variable is stored on the stack + */ + public boolean isStackStorage() { + return storage.isEmpty() && stackStorage != null; + } + + /** + * If this is a stack variable, return its stack offset. + * + * @return its stack offset + */ + public long getStackOffset() { + if (!isStackStorage()) { + throw new IllegalArgumentException(); + } + return stackStorage.getOffset(); + } + + public String getToolTip() { + return """ + Built In Data Types
+   %s + """.formatted("DEFAULT_DATA_ORG_DESCRIPTION"); + } + + /** + * @return true if this variable's storage is in ram + */ + public boolean isRamStorage() { + return storage.size() == 1 && storage.get(0).isAddress(); + } + + /** + * If this is a static/global variable, stored at a ram address, return it's + * ram address. + * + * @return address of where this variable is stored, null if not ram address + */ + public Address getRamAddress() { + return isRamStorage() ? storage.get(0).getAddress() : null; + } + + public boolean isMissingStorage() { + return storage.isEmpty() && stackStorage == null; + } + + public boolean isZeroByte() { + return DWARFUtil.isZeroByteDataType(type); + } + + public boolean isVoidType() { + return DWARFUtil.isVoid(type); + } + + public boolean isEmptyArray() { + return DWARFUtil.isEmptyArray(type); + } + + public boolean isLocationValidOnEntry() { + return lexicalOffset == 0; + } + + public void clearStorage() { + storage.clear(); + stackStorage = null; + } + + private boolean readParamStorage(DIEAggregate diea) { + try { + if (DataTypeComponent.usesZeroLengthComponent(type)) { + Msg.warn(this, "DWARF: zero-length function parameter %s:%s in %s@%s".formatted( + name.getName(), type.getName(), dfunc.name.getName(), dfunc.address)); + return false; + } + DWARFLocation topLocation = DWARFLocation.getTopLocation( + diea.getAsLocation(DW_AT_location, dfunc.getRange()), dfunc.address.getOffset()); + if (topLocation == null) { + return false; + } + return readStorage(diea, topLocation); + } + catch (IOException e) { + diea.getProgram().getImportSummary().exprReadError++; + return false; + } + } + + private boolean readLocalVariableStorage(DIEAggregate diea) { + try { + DWARFLocation location = DWARFLocation + .getFirstLocation(diea.getAsLocation(DW_AT_location, dfunc.getRange())); + if (location == null) { + return false; + } + if (lexicalOffset == 0) { + // Don't override the lexical block's start offset (if set) + // with the address from the dwarf range. This gives slightly better results with + // test binaries in the decompiler, but might be wrong for other toolchains. + // If it causes problems, always use the address from the location's range. + lexicalOffset = location.getRange().getFrom() - dfunc.address.getOffset(); + } + + return readStorage(diea, location); + } + catch (IOException e) { + diea.getProgram().getImportSummary().exprReadError++; + return false; + } + } + + private boolean readGlobalStorage(DIEAggregate diea) { + DWARFProgram prog = diea.getProgram(); + + try { + DWARFLocation location = DWARFLocation + .getFirstLocation(diea.getAsLocation(DW_AT_location, DWARFRange.EMPTY)); + if (location == null) { + return false; + } + + DWARFExpressionEvaluator exprEvaluator = + DWARFExpressionEvaluator.create(diea.getHeadFragment()); + + DWARFExpression expr = exprEvaluator.readExpr(location.getLocation()); + + exprEvaluator.evaluate(expr); + if (exprEvaluator.getRawLastRegister() != -1) { + Msg.warn(this, "DWARF: bad location for global variable %s: %s" + .formatted(getDeclInfoString(), expr.toString())); + return false; + } + + long res = exprEvaluator.pop(); + if (res == 0) { + // If the expression evaluated to a static address of '0'. + // This case is probably caused by relocation fixups not being applied to the + // .debug_info section. + prog.getImportSummary().relocationErrorVarDefs.add("%s:%s".formatted( + name.getNamespacePath().asFormattedString(), type.getPathName())); + return false; + } + + setRamStorage(res + prog.getProgramBaseAddressFixup()); + return true; + } + catch (DWARFExpressionException | UnsupportedOperationException + | IndexOutOfBoundsException | IOException ex) { + prog.getImportSummary().exprReadError++; + return false; + } + } + + private boolean readStorage(DIEAggregate diea, DWARFLocation location) { + + if (location == null) { + return false; + } + + lexicalOffset = location.getRange().getFrom() - dfunc.address.getOffset(); + + DWARFProgram prog = diea.getProgram(); + DWARFImportSummary importSummary = prog.getImportSummary(); + + try { + DWARFExpressionEvaluator exprEvaluator = + DWARFExpressionEvaluator.create(diea.getHeadFragment()); + exprEvaluator.setFrameBase(dfunc.frameBase); + + DWARFExpression expr = exprEvaluator.readExpr(location.getLocation()); + + exprEvaluator.evaluate(expr); + long res = exprEvaluator.pop(); + + // check expression eval result. Use early return for errors, leaving storage unset. + // Success return is at bottom of if/else chain of checks. + + if (exprEvaluator.isDwarfStackValue()) { + // result is a value (not a location) left on the expr stack, which is not supported + importSummary.varDWARFExpressionValue++; + return false; + } + + if (exprEvaluator.useUnknownRegister()) { + // This is a deref of a register (excluding the stack pointer) + // If the offset of the deref was 0, we can cheese it into a ghidra register location + // by changing the datatype to a pointer-to-original-datatype, otherwise + // its not usable in ghidra + + if (!exprEvaluator.isRegisterLocation()) { + importSummary.varDynamicRegisterError++; + return false; + } + + type = prog.getDwarfDTM().getPtrTo(type); + setRegisterStorage(List.of(exprEvaluator.getLastRegister())); + } + else if (exprEvaluator.isStackRelative()) { + if (exprEvaluator.isDeref()) { + type = prog.getDwarfDTM().getPtrTo(type); + } + setStackStorage(res); + } + else if (exprEvaluator.isRegisterLocation()) { + // The DWARF expression evaluated to a simple register. If we have a mapping + // for it in the "processor.dwarf" register mapping file, try to create + // a variable, otherwise log the unknown register for later logging. + Register reg = exprEvaluator.getLastRegister(); + if (reg == null) { + // The DWARF register did not have a mapping to a Ghidra register, so + // log it to be displayed in an error summary at end of import phase. + importSummary.unknownRegistersEncountered + .add(exprEvaluator.getRawLastRegister()); + return false; + } + if ((type.getLength() > reg.getMinimumByteSize())) { + importSummary.varFitError++; + + Msg.warn(this, + "%s %s [%s, size=%d] for function %s@%s can not fit into specified register %s, size=%d, skipping. DWARF DIE: %s" + .formatted(getVarTypeName(diea), name.getName(), type.getName(), + type.getLength(), dfunc.name.getName(), dfunc.address, + reg.getName(), reg.getMinimumByteSize(), diea.getHexOffset())); + return false; + } + setRegisterStorage(List.of(reg)); + } + else if (exprEvaluator.getRawLastRegister() == -1 && res != 0) { + // static global variable location + setRamStorage(res); + } + else { + Msg.error(this, + "%s location error for function %s@%s, %s: %s, DWARF DIE: %s, unsupported location information." + .formatted(getVarTypeName(diea), dfunc.name.getName(), dfunc.address, + name.getName(), + DWARFExpression.exprToString(location.getLocation(), diea), + diea.getHexOffset())); + return false; + } + + return true; + } + catch (DWARFExpressionException | UnsupportedOperationException + | IndexOutOfBoundsException ex) { + importSummary.exprReadError++; + return false; + } + } + + private String getVarTypeName(DIEAggregate diea) { + return diea.getTag() == DWARFTag.DW_TAG_formal_parameter ? "Parameter" : "Variable"; + } + + public int getStorageSize() { + return getVarnodes().stream().mapToInt(Varnode::getSize).sum(); + } + + private Varnode[] getVarnodesAsArray() { + Varnode[] result = new Varnode[storage.size() + (stackStorage != null ? 1 : 0)]; + storage.toArray(result); + if (stackStorage != null) { + result[storage.size()] = stackStorage; + } + return result; + } + + public List getVarnodes() { + List tmp = new ArrayList<>(storage); + if (stackStorage != null) { + tmp.add(stackStorage); + } + return tmp; + } + + public void setVarnodes(List newStorage) { + clearStorage(); + this.storage = newStorage; + Varnode lastNode = !storage.isEmpty() ? storage.get(storage.size() - 1) : null; + if (lastNode != null && DWARFUtil.isStackVarnode(lastNode)) { + stackStorage = lastNode; + storage.remove(storage.size() - 1); + } + } + + public VariableStorage getVariableStorage() throws InvalidInputException { + Varnode[] varnodes = getVarnodesAsArray(); + return varnodes.length != 0 + ? new VariableStorage(program.getGhidraProgram(), varnodes) + : VariableStorage.UNASSIGNED_STORAGE; + } + + public Variable asLocalVariable() throws InvalidInputException { + int firstUseOffset = !isStackStorage() ? (int) lexicalOffset : 0; + + LocalVariableImpl result = new LocalVariableImpl(name.getName(), firstUseOffset, type, + getVariableStorage(), program.getGhidraProgram()); + result.setComment(comment); + + return result; + } + + public Parameter asParameter(boolean includeStorageDetail, Program program) + throws InvalidInputException { + VariableStorage paramStorage = !isMissingStorage() && includeStorageDetail + ? getVariableStorage() + : VariableStorage.UNASSIGNED_STORAGE; + + // try to allow ghidra to autoname param instead of using our autogenerated version + String newName = name.isAnon() ? null : name.getName(); + + ParameterImpl result = + new ParameterImpl(newName, Parameter.UNASSIGNED_ORDINAL, type, paramStorage, true, + program, SourceType.IMPORTED); + result.setComment(getParamComment()); + + return result; + } + + private String getParamComment() { + if (!isOutputParameter) { + return comment; + } + return Objects.requireNonNullElse(comment, "") + "(Output Parameter)"; + + } + + public ParameterDefinition asParameterDef() { + return new ParameterDefinitionImpl(name.getOriginalName(), type, getParamComment()); + } + + public Parameter asReturnParameter(boolean includeStorageDetail) + throws InvalidInputException { + VariableStorage storage = isVoidType() + ? VariableStorage.VOID_STORAGE + : !isMissingStorage() && includeStorageDetail + ? getVariableStorage() + : VariableStorage.UNASSIGNED_STORAGE; + return new ReturnParameterImpl(type, storage, true, program.getGhidraProgram()); + } + + public void appendComment(String prefix, String comment, String sep) { + if (comment == null || comment.isEmpty()) { + comment = ""; + } + else { + comment += sep; + } + this.comment += prefix + comment; + } + + public String getDeclInfoString() { + return "%s:%s".formatted(name.getName(), type.getDisplayName()); + } + + @Override + public String toString() { + try { + return "DWARFVariable [\n\t\tdni=%s,\n\t\ttype=%s,\n\t\tstorage=%s,\n\t\tisOutputParameter=%s\n\t]" + .formatted(name, type, getVariableStorage().toString(), isOutputParameter); + } + catch (InvalidInputException e) { + return ""; + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/NameDeduper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/NameDeduper.java new file mode 100644 index 0000000000..11e8e022db --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf4/next/NameDeduper.java @@ -0,0 +1,93 @@ +/* ### + * 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.dwarf4.next; + +import java.util.*; + +/** + * Helper for allocating unique string names. + *

+ * "Reserved names" are names that will be used by later calls to the de-duper. + *

+ * "Used names" are names that are already allocated and are in use. + *

+ * Reserved names only prevent re-use of a name when a name is being generated because of a + * collision with a "used name". + */ +public class NameDeduper { + private final Set usedNames = new HashSet<>(); + private final Set reservedNames = new HashSet<>(); + + /** + * Create a new name de-duper. + * + */ + public NameDeduper() { + // empty + } + + /** + * Add names to the the de-duper that have already been used. + * + * @param alreadyUsedNames + */ + public void addUsedNames(Collection alreadyUsedNames) { + usedNames.addAll(alreadyUsedNames); + } + + /** + * Add names to the de-duper that will be used in a future call. These names do not block + * calls to confirm that a name is unique, but instead prevent the name from being used + * when an auto-generated name is created. + * + * @param additionalReservedNames + */ + public void addReservedNames(Collection additionalReservedNames) { + reservedNames.addAll(additionalReservedNames); + } + + /** + * Returns true if the specified name hasn't been allocated yet. + * + * @param name + * @return + */ + public boolean isUniqueName(String name) { + return name == null || !usedNames.contains(name); + } + + /** + * Confirms that the specified name is unique, or returns a generated name that is unique. + * + * @param name name to test + * @return {@code null} if specified name is already unique (and marks the specified name as + * used), or returns a new, unique generated name + */ + public String getUniqueName(String name) { + if (name == null || usedNames.add(name)) { + return null; + } + + String original = name; + int tryNum = 0; + while (usedNames.contains(name) || reservedNames.contains(name)) { + name = String.format("%s_%d", original, ++tryNum); + } + usedNames.add(name); + return name; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java index 2054f72f88..824d426b09 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java @@ -21,6 +21,7 @@ import static ghidra.app.util.bin.StructConverter.BYTE; import java.util.*; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import ghidra.app.util.bin.BinaryReader; @@ -29,6 +30,7 @@ import ghidra.app.util.bin.format.elf.info.ElfInfoItem; import ghidra.framework.options.Options; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities.ClearDataMode; import ghidra.program.model.lang.Endian; import ghidra.program.model.listing.Program; import ghidra.program.model.util.CodeUnitInsertionException; @@ -57,7 +59,6 @@ public class GoBuildInfo implements ElfInfoItem { private static final int FLAG_ENDIAN = (1 << 0); private static final int FLAG_INLINE_STRING = (1 << 1); - /** * Reads a GoBuildInfo ".go.buildinfo" section from the specified Program, if present. * @@ -65,9 +66,20 @@ public class GoBuildInfo implements ElfInfoItem { * @return new {@link GoBuildInfo} section, if present, null if missing or error */ public static GoBuildInfo fromProgram(Program program) { + ItemWithAddress wrappedItem = findBuildInfo(program); + return wrappedItem != null ? wrappedItem.item() : null; + } + + public static ItemWithAddress findBuildInfo(Program program) { + // try as if binary is ELF ItemWithAddress wrappedItem = ElfInfoItem.readItemFromSection(program, SECTION_NAME, GoBuildInfo::read); - return wrappedItem != null ? wrappedItem.item() : null; + if (wrappedItem == null) { + // if not present, try common PE location for buildinfo, using "ElfInfoItem" logic + // even though this might be a PE binary, cause it doesn't matter + wrappedItem = ElfInfoItem.readItemFromSection(program, ".data", GoBuildInfo::read); + } + return wrappedItem; } /** @@ -95,6 +107,26 @@ public class GoBuildInfo implements ElfInfoItem { return readStringInfo(reader, inlineStr, program, pointerSize); } + /** + * Probes the specified InputStream and returns true if it starts with a go buildinfo magic + * signature. + * + * @param is InputStream + * @return true if starts with buildinfo magic signature + */ + public static boolean isPresent(InputStream is) { + try { + byte[] buffer = new byte[GO_BUILDINF_MAGIC.length]; + int bytesRead = is.read(buffer); + return bytesRead == GO_BUILDINF_MAGIC.length && + Arrays.equals(buffer, GO_BUILDINF_MAGIC); + } + catch (IOException e) { + // fall thru + } + return false; + } + private final int pointerSize; private final Endian endian; private final String version; // golang compiler version @@ -154,7 +186,8 @@ public class GoBuildInfo implements ElfInfoItem { try { StructureDataType struct = toStructure(program.getDataTypeManager()); if (struct != null) { - program.getListing().createData(address, struct); + DataUtilities.createData(program, address, struct, -1, false, + ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA); } } catch (CodeUnitInsertionException e) { @@ -163,7 +196,7 @@ public class GoBuildInfo implements ElfInfoItem { } public void decorateProgramInfo(Options props) { - props.setString("Golang go version", getVersion()); + GoVer.setProgramPropertiesWithOriginalVersionString(props, getVersion()); props.setString("Golang app path", getPath()); if (getModuleInfo() != null) { getModuleInfo() @@ -185,7 +218,7 @@ public class GoBuildInfo implements ElfInfoItem { StructureDataType toStructure(DataTypeManager dtm) { StructureDataType result = - new StructureDataType(GolangElfInfoProducer.GO_CATEGORYPATH, "GoBuildInfo", 0, dtm); + new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildInfo", 0, dtm); result.add(new ArrayDataType(ASCII, 14, -1, dtm), "magic", "\\xff Go buildinf:"); result.add(BYTE, "ptrSize", null); result.add(BYTE, "flags", null); @@ -211,9 +244,9 @@ public class GoBuildInfo implements ElfInfoItem { reader.setPointerIndex(32 /* static start of inline strings */); versionString = reader.readNext(GoBuildInfo::varlenString); - byte[] modelStringBytes = reader.readNext(GoBuildInfo::varlenBytes); + byte[] moduleStringBytes = reader.readNext(GoBuildInfo::varlenBytes); - moduleString = extractModuleString(modelStringBytes); + moduleString = extractModuleString(moduleStringBytes); } else { reader.setPointerIndex(16 /* static start of 2 string pointers */); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java new file mode 100644 index 0000000000..4a9f383587 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoConstants.java @@ -0,0 +1,22 @@ +/* ### + * 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.golang; + +import ghidra.program.model.data.CategoryPath; + +public class GoConstants { + public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang"); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java new file mode 100644 index 0000000000..22a1c925ce --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionFixup.java @@ -0,0 +1,321 @@ +/* ### + * 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.golang; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; + +/** + * Utility class to fix Golang function parameter storage + */ +public class GoFunctionFixup { + + /** + * Assigns custom storage for a function's parameters, using the function's current + * parameter list (formal info only) as starting information. + * + * @param func + * @throws DuplicateNameException + * @throws InvalidInputException + */ + public static void fixupFunction(Function func) + throws DuplicateNameException, InvalidInputException { + Program program = func.getProgram(); + GoVer goVersion = GoVer.fromProgramProperties(program); + fixupFunction(func, goVersion); + } + + public static void fixupFunction(Function func, GoVer goVersion) + throws DuplicateNameException, InvalidInputException { + Program program = func.getProgram(); + GoParamStorageAllocator storageAllocator = new GoParamStorageAllocator(program, goVersion); + + if (isGolangAbi0Func(func)) { + // Some (typically lower level) functions in the binary will be marked with a + // symbol that ends in the string "abi0". + // Throw away all registers and force stack allocation for everything + storageAllocator.setAbi0Mode(); + } + + fixupFunction(func, storageAllocator); + } + + private static void fixupFunction(Function func, GoParamStorageAllocator storageAllocator) + throws DuplicateNameException, InvalidInputException { + List spillVars = new ArrayList<>(); + Program program = func.getProgram(); + + // for each parameter in the function's param list, calculate custom storage for it + List newParams = new ArrayList<>(); + for (Parameter oldParam : func.getParameters()) { + DataType dt = oldParam.getFormalDataType(); + ParameterImpl newParam = null; + List regStorage = storageAllocator.getRegistersFor(dt); + if (regStorage != null && !regStorage.isEmpty()) { + newParam = updateParamWithCustomRegisterStorage(oldParam, regStorage); + spillVars.add(newParam); + if (dt instanceof Structure && + newParam.getVariableStorage().size() != dt.getLength()) { + Msg.warn(GoFunctionFixup.class, + "Known storage allocation problem: func %s@%s param %s register allocation for structs missing inter-field padding." + .formatted(func.getName(), func.getEntryPoint(), + newParam.toString())); + } + } + else { + newParam = updateParamWithStackStorage(oldParam, storageAllocator); + } + newParams.add(newParam); + } + + // prepare for calculating return result custom storage + storageAllocator.alignStack(); + storageAllocator.resetRegAllocation(); + + DataType returnDT = func.getReturnType(); + List returnResultAliasVars = new ArrayList<>(); + ReturnParameterImpl returnParam = returnDT != null + ? updateReturn(func, storageAllocator, returnResultAliasVars) + : null; + + storageAllocator.alignStack(); + + if (returnParam == null && newParams.isEmpty()) { + // its better to do nothing than lock the signature down + return; + } + + // Update the function in Ghidra + func.updateFunction(null, returnParam, newParams, FunctionUpdateType.CUSTOM_STORAGE, true, + SourceType.USER_DEFINED); + + // Remove any old local vars that are in the callers stack instead of in the local stack area + for (Variable localVar : func.getLocalVariables()) { + if (localVar.isStackVariable() && + !isInLocalVarStorageArea(func, localVar.getStackOffset())) { + func.removeVariable(localVar); + } + } + + // For any parameters that were passed as registers, the golang caller pre-allocates + // space on the stack for the parameter value to be used when the register is overwritten. + // Ghidra decompilation results are improved if those storage locations are covered + // by variables that we create artificially. + for (ParameterImpl param : spillVars) { + DataType paramDT = param.getFormalDataType(); + long stackOffset = storageAllocator.getStackAllocation(paramDT); + Varnode stackVarnode = + new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset), + paramDT.getLength()); + VariableStorage varStorage = new VariableStorage(program, List.of(stackVarnode)); + LocalVariableImpl localVar = + new LocalVariableImpl(param.getName() + "-spill", 0, paramDT, varStorage, program); + + // TODO: needs more thought + func.addLocalVariable(localVar, SourceType.USER_DEFINED); + } + + for (LocalVariable returnResultAliasVar : returnResultAliasVars) { + func.addLocalVariable(returnResultAliasVar, SourceType.USER_DEFINED); + } + } + + /** + * Returns a Ghidra data type that represents a zero-length array, to be used as a replacement + * for a zero-length array parameter. + * + * @param dt + * @return + */ + public static DataType makeEmptyArrayDataType(DataType dt) { + StructureDataType struct = new StructureDataType(dt.getCategoryPath(), + "empty_" + dt.getName(), 0, dt.getDataTypeManager()); + struct.setToDefaultPacking(); + return struct; + } + + private static ParameterImpl updateParamWithCustomRegisterStorage(Parameter oldParam, + List regStorage) throws InvalidInputException { + Program program = oldParam.getProgram(); + DataType dt = oldParam.getDataType(); + List varnodes = + DWARFUtil.convertRegisterListToVarnodeStorage(regStorage, dt.getLength()); + VariableStorage varStorage = + new VariableStorage(program, varnodes.toArray(Varnode[]::new)); + ParameterImpl newParam = + new ParameterImpl(oldParam.getName(), Parameter.UNASSIGNED_ORDINAL, dt, + varStorage, true, program, SourceType.USER_DEFINED); + return newParam; + } + + private static ParameterImpl updateParamWithStackStorage(Parameter oldParam, + GoParamStorageAllocator storageAllocator) throws InvalidInputException { + DataType dt = oldParam.getDataType(); + Program program = oldParam.getProgram(); + if (!DWARFUtil.isZeroByteDataType(dt)) { + long stackOffset = storageAllocator.getStackAllocation(dt); + return new ParameterImpl(oldParam.getName(), dt, (int) stackOffset, program); + } + else { + if (DWARFUtil.isEmptyArray(dt)) { + dt = makeEmptyArrayDataType(dt); + } + Address zerobaseAddress = GolangSymbolAnalyzer.getZerobaseAddress(program); + return new ParameterImpl(oldParam.getName(), dt, zerobaseAddress, program, + SourceType.USER_DEFINED); + } + + } + + private static ReturnParameterImpl updateReturn(Function func, + GoParamStorageAllocator storageAllocator, List returnResultAliasVars) + throws InvalidInputException { + + Program program = func.getProgram(); + DataTypeManager dtm = program.getDataTypeManager(); + DataType returnDT = func.getReturnType(); + List varnodes = new ArrayList<>(); + + if (returnDT == null || Undefined.isUndefined(returnDT) || DWARFUtil.isVoid(returnDT)) { + return null; + } + +// status refactoring return result storage calc to use new GoFunctionMultiReturn +// class to embed ordinal order in data type so that original data type and calc info +// can be recreated. + + GoFunctionMultiReturn multiReturn; + if ((multiReturn = + GoFunctionMultiReturn.fromStructure(returnDT, dtm, storageAllocator)) != null) { + // allocate storage for individual elements of the struct because they were + // originally separate return values. + // Also turn off endianness fixups in the registers that are fetched + // because we will do it manually + returnDT = multiReturn.getStruct(); + + for (DataTypeComponent dtc : multiReturn.getNormalStorageComponents()) { + allocateReturnStorage(program, dtc.getFieldName() + "-return-result-alias", + dtc.getDataType(), storageAllocator, varnodes, returnResultAliasVars, + false); + } + for (DataTypeComponent dtc : multiReturn.getStackStorageComponents()) { + allocateReturnStorage(program, dtc.getFieldName() + "-return-result-alias", + dtc.getDataType(), storageAllocator, varnodes, returnResultAliasVars, + false); + } + if (!program.getMemory().isBigEndian()) { + reverseNonStackStorageLocations(varnodes); + } + } + else if (DWARFUtil.isZeroByteDataType(returnDT)) { + if (DWARFUtil.isEmptyArray(returnDT)) { + returnDT = makeEmptyArrayDataType(returnDT); + } + varnodes.add(new Varnode(GolangSymbolAnalyzer.getZerobaseAddress(program), 1)); + } + else { + allocateReturnStorage(program, "return-value-alias-variable", returnDT, + storageAllocator, varnodes, returnResultAliasVars, true); + } + + if (varnodes.isEmpty()) { + return null; + } + VariableStorage varStorage = + new VariableStorage(program, varnodes.toArray(Varnode[]::new)); + return new ReturnParameterImpl(returnDT, varStorage, true, program); + } + + private static void allocateReturnStorage(Program program, String name_unused, DataType dt, + GoParamStorageAllocator storageAllocator, List varnodes, + List returnResultAliasVars, boolean allowEndianFixups) + throws InvalidInputException { + List regStorage = storageAllocator.getRegistersFor(dt, allowEndianFixups); + if (regStorage != null && !regStorage.isEmpty()) { + varnodes.addAll( + DWARFUtil.convertRegisterListToVarnodeStorage(regStorage, dt.getLength())); + } + else { + if (!DWARFUtil.isZeroByteDataType(dt)) { + long stackOffset = storageAllocator.getStackAllocation(dt); + varnodes.add( + new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset), + dt.getLength())); + + // when the return value is on the stack, the decompiler's output is improved + // when the function has something at the stack location + LocalVariableImpl returnAliasLocalVar = new LocalVariableImpl(name_unused, dt, + (int) stackOffset, program, SourceType.USER_DEFINED); + returnResultAliasVars.add(returnAliasLocalVar); + } + } + } + + public static boolean isGolangAbi0Func(Function func) { + Address funcAddr = func.getEntryPoint(); + for (Symbol symbol : func.getProgram().getSymbolTable().getSymbolsAsIterator(funcAddr)) { + if (symbol.getSymbolType() == SymbolType.LABEL) { + String labelName = symbol.getName(); + if (labelName.endsWith("abi0")) { + return true; + } + } + } + return false; + } + + public static boolean isInLocalVarStorageArea(Function func, long stackOffset) { + Program program = func.getProgram(); + boolean paramsHavePositiveOffset = program.getCompilerSpec().stackGrowsNegative(); + return (paramsHavePositiveOffset && stackOffset < 0) || + (!paramsHavePositiveOffset && stackOffset >= 0); + } + + /** + * Invert the order of the any register storage locations to match the decompiler's logic + * for assigning storage to structs that varies on endianness. + *

+ * Only valid for storage scheme that has all register storages listed first / contiguous. + * + * @param varnodes + */ + public static void reverseNonStackStorageLocations(List varnodes) { + int regStorageCount; + for (regStorageCount = 0; regStorageCount < varnodes.size(); regStorageCount++) { + if (DWARFUtil.isStackVarnode(varnodes.get(regStorageCount))) { + break; + } + } + List regStorageList = new ArrayList<>(varnodes.subList(0, regStorageCount)); + for (int i = 0; i < regStorageList.size(); i++) { + varnodes.set(i, regStorageList.get(regStorageList.size() - 1 - i)); + } + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java new file mode 100644 index 0000000000..62cbf71ddd --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoFunctionMultiReturn.java @@ -0,0 +1,207 @@ +/* ### + * 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.golang; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ghidra.app.util.bin.format.dwarf4.next.*; +import ghidra.program.model.data.*; +import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult; +import ghidra.program.model.lang.Register; + +/** + * Handles creating a Ghidra structure to represent multiple return values returned from a golang + * function. + *

+ * Assigning custom storage for the return value is complicated by: + *

    + *
  • golang storage allocations depend on the formal ordering of the return values + *
  • stack storage must be last in a list of varnodes + *
  • the decompiler maps a structure's contents to the list of varnodes in an endian-dependent + * manner. + *
+ * To meet these complications, the structure's layout is modified to put all items that were + * marked as being stack parameters to either the front or back of the structure. + *

+ * To allow this artificial structure to adjusted by the user and reused at some later time + * to re-calculate the correct storage, the items in the structure are tagged with the original + * ordinal of that item as a text comment of each structure field, so that the correct ordering + * of items can be re-created when needed. + *

+ * If the structure layout is modified to conform to an arch's requirements, the structure's + * name will be modified to include that arch's description at the end (eg. "_x86_64") + */ +public class GoFunctionMultiReturn { + public static final String MULTIVALUE_RETURNTYPE_SUFFIX = "_multivalue_return_type"; + private static final String ORDINAL_PREFIX = "ordinal: "; + + // match a substring that is "ordinal: NN", marking the number portion as group 1 + private static final Pattern ORDINAL_REGEX = + Pattern.compile(".*" + ORDINAL_PREFIX + "([\\d]+)[^\\d]*"); + + public static boolean isMultiReturnDataType(DataType dt) { + return dt instanceof Structure && dt.getName().endsWith(MULTIVALUE_RETURNTYPE_SUFFIX); + } + + public static GoFunctionMultiReturn fromStructure(DataType dt, DataTypeManager dtm, + GoParamStorageAllocator storageAllocator) { + return isMultiReturnDataType(dt) + ? new GoFunctionMultiReturn((Structure) dt, dtm, storageAllocator) + : null; + } + + private Structure struct; + private List normalStorageComponents = new ArrayList<>(); + private List stackStorageComponents = new ArrayList<>(); + + public GoFunctionMultiReturn(List returnParams, DWARFFunction dfunc, + DataTypeManager dtm, GoParamStorageAllocator storageAllocator) { + + Structure newStruct = mkStruct(dfunc.name.getParentCP(), dfunc.name.getName(), dtm); + int ordinalNum = 0; + for (DWARFVariable dvar : returnParams) { + newStruct.add(dvar.type, dvar.name.getName(), ORDINAL_PREFIX + ordinalNum); + ordinalNum++; + } + + regenerateMultireturnStruct(newStruct, dtm, storageAllocator); + } + + public GoFunctionMultiReturn(CategoryPath categoryPath, String funcName, List types, + DataTypeManager dtm, GoParamStorageAllocator storageAllocator) { + + Structure newStruct = mkStruct(categoryPath, funcName, dtm); + int ordinalNum = 0; + for (DataType dt : types) { + newStruct.add(dt, "~r%d".formatted(ordinalNum), ORDINAL_PREFIX + ordinalNum); + ordinalNum++; + } + + regenerateMultireturnStruct(newStruct, dtm, storageAllocator); + } + + private static Structure mkStruct(CategoryPath cp, String baseName, DataTypeManager dtm) { + String structName = baseName + MULTIVALUE_RETURNTYPE_SUFFIX; + Structure newStruct = new StructureDataType(cp, structName, 0, dtm); + newStruct.setPackingEnabled(true); + newStruct.setExplicitPackingValue(1); + newStruct.setDescription("Artificial data type to hold a function's return values"); + return newStruct; + } + + public GoFunctionMultiReturn(Structure struct, DataTypeManager dtm, + GoParamStorageAllocator storageAllocator) { + regenerateMultireturnStruct(struct, dtm, storageAllocator); + } + + public Structure getStruct() { + return struct; + } + + public List getNormalStorageComponents() { + return normalStorageComponents; + } + + public List getStackStorageComponents() { + return stackStorageComponents; + } + + private record StackComponentInfo(DataTypeComponent dtc, int ordinal, String comment) {} + + private void regenerateMultireturnStruct(Structure struct, DataTypeManager dtm, + GoParamStorageAllocator storageAllocator) { + if (storageAllocator == null) { + this.struct = struct; + for (DataTypeComponent dtc : getComponentsInOriginalOrder(struct)) { + stackStorageComponents.add(dtc); + } + return; + } + + Structure adjustedStruct = + new StructureDataType( + struct.getCategoryPath(), getBasename(struct.getName()) + + MULTIVALUE_RETURNTYPE_SUFFIX + "_" + storageAllocator.getArchDescription(), + 0, dtm); + adjustedStruct.setPackingEnabled(true); + adjustedStruct.setExplicitPackingValue(1); + + storageAllocator = storageAllocator.clone(); + List stackResults = new ArrayList<>(); + int compNum = 0; + for (DataTypeComponent dtc : getComponentsInOriginalOrder(struct)) { + List regs = storageAllocator.getRegistersFor(dtc.getDataType()); + if (regs == null || regs.isEmpty()) { + long stackOffset = storageAllocator.getStackAllocation(dtc.getDataType()); + String comment = "stack[%d] %s%d".formatted(stackOffset, ORDINAL_PREFIX, compNum); + stackResults.add(new StackComponentInfo(dtc, compNum, comment)); + } + else { + String comment = "%s %s%d".formatted(regs, ORDINAL_PREFIX, compNum); + DataTypeComponent newDTC = + adjustedStruct.add(dtc.getDataType(), dtc.getFieldName(), comment); + normalStorageComponents.add(newDTC); + } + compNum++; + } + + // add the stack items to the struct last or first, depending on endianness + for (int i = 0; i < stackResults.size(); i++) { + StackComponentInfo sci = stackResults.get(i); + DataTypeComponent dtc = sci.dtc; + DataTypeComponent newDTC; + if (storageAllocator.isBigEndian()) { + newDTC = adjustedStruct.add(dtc.getDataType(), dtc.getFieldName(), sci.comment); + } + else { + newDTC = + adjustedStruct.insert(i, dtc.getDataType(), -1, dtc.getFieldName(), + sci.comment); + } + stackStorageComponents.add(newDTC); + } + + boolean isEquiv = DWARFDataTypeConflictHandler.INSTANCE.resolveConflict(adjustedStruct, + struct) == ConflictResult.USE_EXISTING; + this.struct = isEquiv ? struct : adjustedStruct; + } + + private static String getBasename(String structName) { + int i = structName.indexOf(MULTIVALUE_RETURNTYPE_SUFFIX); + return i > 0 ? structName.substring(0, i) : structName; + } + + private static int getOrdinalNumber(DataTypeComponent dtc) { + String comment = Objects.requireNonNullElse(dtc.getComment(), ""); + Matcher m = ORDINAL_REGEX.matcher(comment); + try { + return m.matches() ? Integer.parseInt(m.group(1)) : -1; + } + catch (NumberFormatException nfe) { + return -1; + } + } + + private static List getComponentsInOriginalOrder(Structure struct) { + List dtcs = new ArrayList<>(List.of(struct.getDefinedComponents())); + Collections.sort(dtcs, + (dtc1, dtc2) -> Integer.compare(getOrdinalNumber(dtc1), getOrdinalNumber(dtc2))); + return dtcs; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java new file mode 100644 index 0000000000..f1407d1105 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoParamStorageAllocator.java @@ -0,0 +1,271 @@ +/* ### + * 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.golang; + +import java.util.*; + +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.util.NumericUtilities; + +/** + * Logic and helper for allocating storage for a function's parameters and return value. + *

+ * Not threadsafe. + */ +public class GoParamStorageAllocator { + private static final int INTREG = 0; + private static final int FLOATREG = 1; + + private List> regs; + private int[] nextReg = new int[2]; + private GoRegisterInfo callspecInfo; + private long stackOffset; + private boolean isBigEndian; + private PrototypeModel abiInternalCallingConvention; + private PrototypeModel abi0CallingConvention; + private String archDescription; + + /** + * Creates a new golang function call storage allocator for the specified Ghidra Language. + *

+ * See {@link GoRegisterInfoManager#getRegisterInfoForLang(Language, GoVer)} + * + * @param program {@link Program} + * @param goVersion version of go used to create the program + */ + public GoParamStorageAllocator(Program program, GoVer goVersion) { + Language lang = program.getLanguage(); + + this.callspecInfo = + GoRegisterInfoManager.getInstance().getRegisterInfoForLang(lang, goVersion); + this.stackOffset = callspecInfo.getStackInitialOffset(); + this.regs = List.of(callspecInfo.getIntRegisters(), callspecInfo.getFloatRegisters()); + this.isBigEndian = lang.isBigEndian(); + this.abiInternalCallingConvention = + program.getFunctionManager().getCallingConvention("abi-internal"); + this.abi0CallingConvention = program.getFunctionManager().getCallingConvention("abi0"); + this.archDescription = + "%s_%d".formatted(lang.getLanguageDescription().getProcessor().toString(), + lang.getLanguageDescription().getSize()); + } + + private GoParamStorageAllocator(List> regs, int[] nextReg, + GoRegisterInfo callspecInfo, long stackOffset, boolean isBigEndian, + PrototypeModel abiInternalCallingConvention, PrototypeModel abi0CallingConvention, + String archDescription) { + this.regs = List.of(regs.get(INTREG), regs.get(FLOATREG)); + this.nextReg = new int[] { nextReg[INTREG], nextReg[FLOATREG] }; + this.callspecInfo = callspecInfo; + this.stackOffset = stackOffset; + this.isBigEndian = isBigEndian; + this.abiInternalCallingConvention = abiInternalCallingConvention; + this.abi0CallingConvention = abi0CallingConvention; + this.archDescription = archDescription; + } + + @Override + public GoParamStorageAllocator clone() { + return new GoParamStorageAllocator(regs, nextReg, callspecInfo, stackOffset, isBigEndian, + abiInternalCallingConvention, abi0CallingConvention, archDescription); + } + + public PrototypeModel getAbi0CallingConvention() { + return abi0CallingConvention; + } + + public PrototypeModel getAbiInternalCallingConvention() { + return abiInternalCallingConvention; + } + + public String getArchDescription() { + return archDescription; + } + + public boolean isBigEndian() { + return isBigEndian; + } + + public void resetRegAllocation() { + nextReg[INTREG] = 0; + nextReg[FLOATREG] = 0; + } + + private boolean allocateReg(int count, int regType, DataType dt, List result) { + int newNextReg = nextReg[regType] + count; + if (newNextReg > regs.get(regType).size()) { + return false; + } + + int remainingSize = dt.getLength(); + for (int regNum = nextReg[regType]; regNum < newNextReg; regNum++) { + Register reg = getBestFitRegister(regs.get(regType).get(regNum), remainingSize); + remainingSize -= reg.getMinimumByteSize(); + result.add(reg); + } + nextReg[regType] = newNextReg; + return true; + } + + private Register getBestFitRegister(Register reg, int size) { + while (reg.getMinimumByteSize() > size && reg.hasChildren()) { + reg = reg.getChildRegisters().get(0); + } + return reg; + } + + private int[] saveRegAllocation() { + return new int[] { nextReg[INTREG], nextReg[FLOATREG] }; + } + + private void restoreRegAllocation(int[] savedNextReg) { + nextReg[INTREG] = savedNextReg[INTREG]; + nextReg[FLOATREG] = savedNextReg[FLOATREG]; + } + + public void setAbi0Mode() { + regs = List.of(List.of(), List.of()); + } + + public boolean isAbi0Mode() { + return regs.get(INTREG).isEmpty() && regs.get(FLOATREG).isEmpty(); + } + + /** + * Returns a list of {@link Register registers} that will successfully store the specified + * data type, as well as marking those registers as used and unavailable. + * + * @param dt {@link DataType} to allocate register space for + * @return list of {@link Register registers}, possibly empty if the data type was zero-length, + * possibly null if the data type is not compatible with register storage + */ + public List getRegistersFor(DataType dt) { + return getRegistersFor(dt, true); + } + + /** + * Returns a list of {@link Register registers} that will successfully store the specified + * data type, as well as marking those registers as used and unavailable. + * + * @param dt {@link DataType} to allocate register space for + * @param allowEndianFixups boolean flag, if true the result (if it contains more than a single + * location) will automatically be adjusted in little endian programs to match how storage + * varnodes are laid-out, if false the result will not be adjusted + * @return list of {@link Register registers}, possibly empty if the data type was zero-length, + * possibly null if the data type is not compatible with register storage + */ + public List getRegistersFor(DataType dt, boolean allowEndianFixups) { + int[] saveRegAllocation = saveRegAllocation(); + List result = new ArrayList<>(); + + if (!countRegistersFor(dt, result)) { + restoreRegAllocation(saveRegAllocation); + return null; + } + + if (allowEndianFixups && !isBigEndian && result.size() > 1) { + Collections.reverse(result); + } + + return new ArrayList<>(result); + } + + /** + * Returns the stack offset that should be used to store the data type on the stack, as well + * as marking that stack area as used and unavailable. + * + * @param dt {@link DataType} to allocate stack space for + * @return offset in stack where the data item will be located + */ + public long getStackAllocation(DataType dt) { + if (dt.isZeroLength()) { + return stackOffset; + } + alignStackFor(dt); + long result = stackOffset; + stackOffset += dt.getLength(); + return result; + } + + public long getStackOffset() { + return stackOffset; + } + + public void setStackOffset(long newStackOffset) { + this.stackOffset = newStackOffset; + } + + public void alignStackFor(DataType dt) { + int alignmentSize = callspecInfo.getAlignmentForType(dt); + stackOffset = NumericUtilities.getUnsignedAlignedValue(stackOffset, alignmentSize); + } + + public void alignStack() { + stackOffset = + NumericUtilities.getUnsignedAlignedValue(stackOffset, callspecInfo.getMaxAlign()); + } + + private boolean countRegistersFor(DataType dt, List result) { + if (DWARFUtil.isZeroByteDataType(dt)) { + return false; + } + + if (dt instanceof TypeDef typedefDT) { + dt = typedefDT.getBaseDataType(); + } + if (dt instanceof Pointer) { + return allocateReg(1, INTREG, dt, result); + } + if (GoRegisterInfo.isIntType(dt)) { + int size = dt.getLength(); + int intRegSize = callspecInfo.getIntRegisterSize(); + if (size <= intRegSize * 2) { + return allocateReg( + (int) (NumericUtilities.getUnsignedAlignedValue(size, intRegSize) / intRegSize), + INTREG, dt, result); + } + } + if (dt instanceof AbstractFloatDataType) { + return allocateReg(1, FLOATREG, dt, result); + } + if (dt instanceof Array array) { + int numElements = array.getNumElements(); + if (numElements == 0) { + return true; + } + if (numElements == 1 && countRegistersFor(array.getDataType(), result)) { + return true; + } + return false; + } + if (dt instanceof Structure struct) { + DataTypeComponent prevDTC = null; + for (DataTypeComponent dtc : struct.getDefinedComponents()) { + int padding = prevDTC != null ? dtc.getOffset() - prevDTC.getOffset() : 0; + if (padding != 0) { + + } + if (!countRegistersFor(dtc.getDataType(), result)) { + return false; + } + } + return true; + } + return false; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java new file mode 100644 index 0000000000..34a928e93e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java @@ -0,0 +1,107 @@ +/* ### + * 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.golang; + +import java.util.List; + +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.program.model.lang.Register; + +/** + * Immutable information about registers, alignment sizes, etc needed to allocate storage + * for parameters during a function call. + *

+ */ +public class GoRegisterInfo { + + private List intRegisters; + private List floatRegisters; + private int stackInitialOffset; + private int maxAlign; // 4 or 8 + private Register currentGoroutineRegister; // always points to g + private Register zeroRegister; // always contains a zero value + + GoRegisterInfo(List intRegisters, List floatRegisters, + int stackInitialOffset, int maxAlign, Register currentGoroutineRegister, + Register zeroRegister) { + this.intRegisters = intRegisters; + this.floatRegisters = floatRegisters; + this.stackInitialOffset = stackInitialOffset; + this.maxAlign = maxAlign; + this.currentGoroutineRegister = currentGoroutineRegister; + this.zeroRegister = zeroRegister; + } + + public int getIntRegisterSize() { + return maxAlign; // TODO: HACK: ????? + } + + public int getMaxAlign() { + return maxAlign; + } + + public Register getCurrentGoroutineRegister() { + return currentGoroutineRegister; + } + + public Register getZeroRegister() { + return zeroRegister; + } + + public List getIntRegisters() { + return intRegisters; + } + + public List getFloatRegisters() { + return floatRegisters; + } + + public int getStackInitialOffset() { + return stackInitialOffset; + } + + public int getAlignmentForType(DataType dt) { + while (dt instanceof TypeDef || dt instanceof Array) { + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + if (dt instanceof Array) { + dt = ((Array) dt).getDataType(); + } + } + if (isIntType(dt) && isIntrinsicSize(dt.getLength())) { + return Math.min(maxAlign, dt.getLength()); + } + if (dt instanceof Complex8DataType /* golang complex64 */ ) { + return 4; + } + if (dt instanceof AbstractFloatDataType) { + return Math.min(maxAlign, dt.getLength()); + } + return maxAlign; + } + + static boolean isIntType(DataType dt) { + return dt instanceof AbstractIntegerDataType || dt instanceof WideCharDataType || + dt instanceof WideChar16DataType || dt instanceof WideChar32DataType || + dt instanceof Enum || dt instanceof BooleanDataType; + } + + static boolean isIntrinsicSize(int size) { + return Integer.bitCount(size) == 1; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java new file mode 100644 index 0000000000..9a5ef465d3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java @@ -0,0 +1,221 @@ +/* ### + * 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.golang; + +import java.util.*; + +import java.io.IOException; +import java.io.InputStream; + +import org.jdom.*; +import org.jdom.input.SAXBuilder; + +import generic.jar.ResourceFile; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.program.model.lang.*; +import ghidra.util.Msg; +import ghidra.util.xml.XmlUtilities; + +/** + * XML config file format: + *

+ * 	<golang>
+ * 		<register_info versions="V1_17,V1_18">
+ * 			<int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
+ * 			<float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
+ * 			<stack initialoffset="8" maxalign="8"/>
+ * 			<current_goroutine register="R14"/>
+ * 			<zero_register register="XMM15"/>
+ * 		</register_info>
+ * 		<register_info versions="V1_2">
+ * 			...
+ * 		</register_info>
+ *	</golang> 
+ * 
+ */ +public class GoRegisterInfoManager { + private static final String REGISTER_INFO_EXTERNAL_NAME = "Golang.register.info.file"; + + private static class SingletonHolder { + private static GoRegisterInfoManager instance = new GoRegisterInfoManager(); + } + + public static GoRegisterInfoManager getInstance() { + return SingletonHolder.instance; + } + + private Map> cache = new HashMap<>(); + + /** + * Returns a {@link GoRegisterInfo} instance for the specified {@link Language}. + *

+ * If the language didn't define golang register info, a generic/empty instance will be + * returned that forces all parameters to be stack allocated. + * + * @param lang {@link Language} + * @param goVersion + * @return {@link GoRegisterInfo}, never null + */ + public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVersion) { + Map perVersionRegInfos = + cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang)); + GoRegisterInfo registerInfo = perVersionRegInfos.get(goVersion); + if (registerInfo == null) { + registerInfo = getDefault(lang); + perVersionRegInfos.put(goVersion, registerInfo); + int goSize = lang.getInstructionAlignment(); + Msg.warn(this, "Missing Golang register info for: " + lang.getLanguageID() + + ", defaulting to abi0, size=" + goSize); + } + return registerInfo; + } + + private Map loadRegisterInfo(Language lang) { + try { + ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME); + if (f != null) { + return read(f, lang); + } + Msg.warn(GoRegisterInfoManager.class, + "Missing Golang register info file for: %s".formatted(lang.getLanguageID())); + } + catch (IOException e) { + Msg.warn(GoRegisterInfoManager.class, "Failed to read Golang register info file", + e); + } + return new HashMap<>(); + } + + //------------------------------------------------------------------------------------------- + private Map read(ResourceFile f, Language lang) + throws IOException { + SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false); + try (InputStream fis = f.getInputStream()) { + Document doc = sax.build(fis); + Element rootElem = doc.getRootElement(); + return readFrom(rootElem, lang); + } + catch (JDOMException | IOException e) { + Msg.error(GoRegisterInfo.class, "Bad Golang register info file " + f, e); + throw new IOException("Failed to read Golang register info file " + f, e); + } + + } + + @SuppressWarnings("unchecked") + public Map readFrom(Element rootElem, Language lang) + throws IOException { + + Map result = new HashMap<>(); + for (Element regInfoElem : (List) rootElem.getChildren("register_info")) { + Map registerInfos = readRegInfoElement(regInfoElem, lang); + result.putAll(registerInfos); + } + return result; + } + + private Map readRegInfoElement(Element regInfoElem, Language lang) + throws IOException { + Set validGoVersions = + parseValidGoVersionsStr(XmlUtilities.requireStringAttr(regInfoElem, "versions")); + + Element intRegsElem = regInfoElem.getChild("int_registers"); + Element floatRegsElem = regInfoElem.getChild("float_registers"); + Element stackElem = regInfoElem.getChild("stack"); + Element goRoutineElem = regInfoElem.getChild("current_goroutine"); + Element zeroRegElem = regInfoElem.getChild("zero_register"); + if (intRegsElem == null || floatRegsElem == null || stackElem == null || + goRoutineElem == null || zeroRegElem == null) { + throw new IOException("Bad format"); + } + + List intRegs = parseRegListStr(intRegsElem.getAttributeValue("list"), lang); + List floatRegs = parseRegListStr(floatRegsElem.getAttributeValue("list"), lang); + + int stackInitialOffset = + XmlUtilities.parseBoundedIntAttr(stackElem, "initialoffset", 0, Integer.MAX_VALUE); + int maxAlign = + XmlUtilities.parseBoundedIntAttr(stackElem, "maxalign", 1, Integer.MAX_VALUE); + + Register currentGoRoutineReg = + parseRegStr(goRoutineElem.getAttributeValue("register"), lang); + Register zeroReg = parseRegStr(zeroRegElem.getAttributeValue("register"), lang); + + GoRegisterInfo registerInfo = + new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign, + currentGoRoutineReg, zeroReg); + Map result = new HashMap<>(); + for (GoVer goVer : validGoVersions) { + result.put(goVer, registerInfo); + } + return result; + } + + private GoRegisterInfo getDefault(Language lang) { + int goSize = lang.getInstructionAlignment(); + return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null); + } + + private List parseRegListStr(String s, Language lang) throws IOException { + List result = new ArrayList<>(); + for (String regName : s.split(",")) { + regName = regName.trim(); + if (regName.isEmpty()) { + continue; + } + Register register = parseRegStr(regName, lang); + if (register != null) { + result.add(register); + } + } + return result; + } + + private Register parseRegStr(String regName, Language lang) throws IOException { + if (regName == null || regName.isBlank()) { + return null; + } + Register register = lang.getRegister(regName); + if (register == null) { + throw new IOException("Unknown register: " + regName); + } + return register; + } + + private Set parseValidGoVersionsStr(String s) throws IOException { + if (s.trim().equalsIgnoreCase("all")) { + EnumSet allVers = EnumSet.allOf(GoVer.class); + allVers.remove(GoVer.UNKNOWN); + return allVers; + } + + EnumSet result = EnumSet.noneOf(GoVer.class); + for (String verStr : s.split(",")) { + verStr = verStr.trim(); + if (verStr.isEmpty()) { + continue; + } + try { + GoVer ver = GoVer.valueOf(verStr); + result.add(ver); + } + catch (IllegalArgumentException e) { + throw new IOException("Unknown go version: " + verStr); + } + } + return result; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java index e1a533b54d..eb251e24e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java @@ -15,6 +15,9 @@ */ package ghidra.app.util.bin.format.golang; +import ghidra.framework.options.Options; +import ghidra.program.model.listing.Program; + /** * Golang version numbers */ @@ -88,4 +91,16 @@ public enum GoVer { } return UNKNOWN; } + + public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version"; + public static GoVer fromProgramProperties(Program program) { + Options props = program.getOptions(Program.PROGRAM_INFO); + String verStr = props.getString(GOLANG_VERSION_PROPERTY_NAME, null); + return verStr != null ? parse(verStr) : UNKNOWN; + } + + public static void setProgramPropertiesWithOriginalVersionString(Options props, String s) { + props.setString(GOLANG_VERSION_PROPERTY_NAME, s); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java new file mode 100644 index 0000000000..9b88a51d3f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangDWARFFunctionFixup.java @@ -0,0 +1,352 @@ +/* ### + * 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.golang; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer; +import ghidra.app.util.bin.format.dwarf4.DIEAggregate; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; +import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup; +import ghidra.app.util.bin.format.dwarf4.next.*; +import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.SymbolType; +import ghidra.util.Msg; +import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.util.exception.DuplicateNameException; + +/** + * Fixups for golang functions. + *

+ * Fixes storage of parameters to match the go callspec and modifies parameter lists to match + * Ghidra's capabilities. + *

+ * Special characters used by golang in symbol names are fixed up in + * DWARFProgram.fixupSpecialMeaningCharacters(): + *

  • "\u00B7" (middle dot) -> "." + *
  • "\u2215" (weird slash) -> "/" + */ +@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY) +public class GolangDWARFFunctionFixup implements DWARFFunctionFixup { + + public static final CategoryPath GOLANG_API_EXPORT = + new CategoryPath(CategoryPath.ROOT, "GolangAPIExport"); + + public static boolean isGolangFunction(DWARFFunction dfunc) { + DIEAggregate diea = dfunc.diea; + int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage(); + if (cuLang != DWARFSourceLanguage.DW_LANG_Go) { + return false; + } + // sanity check: gofuncs always have a void return type in dwarf + if (!dfunc.retval.type.isEquivalent(VoidDataType.dataType)) { + return false; + } + return true; + } + + @Override + public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) { + if (!isGolangFunction(dfunc)) { + return; + } + GoVer goVersion = getGolangVersion(dfunc); + if (goVersion == GoVer.UNKNOWN) { + return; + } + + DataTypeManager dtm = gfunc.getProgram().getDataTypeManager(); + GoParamStorageAllocator storageAllocator = + new GoParamStorageAllocator(gfunc.getProgram(), goVersion); + + if (GoFunctionFixup.isGolangAbi0Func(gfunc)) { + // Some (typically lower level) functions in the binary will be marked with a + // symbol that ends in the string "abi0". + // Throw away all registers and force stack allocation for everything + storageAllocator.setAbi0Mode(); + dfunc.prototypeModel = storageAllocator.getAbi0CallingConvention(); + } + else { + dfunc.prototypeModel = storageAllocator.getAbiInternalCallingConvention(); + } + + GoFunctionMultiReturn multiReturnInfo = fixupFormalFuncDef(dfunc, storageAllocator, dtm); + fixupCustomStorage(dfunc, gfunc, storageAllocator, dtm, multiReturnInfo); + } + + private GoFunctionMultiReturn fixupFormalFuncDef(DWARFFunction dfunc, + GoParamStorageAllocator storageAllocator, DataTypeManager dtm) { + // Go funcs can have multiple return values, which are marked up in dwarf as parameters with + // a special boolean flag. Unnamed return values typically have a "~r0", "~r1", etc name + // auto-assigned. + // Pull them out of the param list and create a structure to hold them as the return value + // They also need to be sorted so that stack storage items appear last, after register items. + List realParams = new ArrayList<>(); + List returnParams = new ArrayList<>(); + for (DWARFVariable dvar : dfunc.params) { + if (dvar.isOutputParameter) { + returnParams.add(dvar); + } + else { + realParams.add(dvar); + } + } + + DataType returnType = VoidDataType.dataType; + GoFunctionMultiReturn multiReturn = null; + if (returnParams.size() == 1) { + returnType = returnParams.get(0).type; + } + else if (returnParams.size() > 1) { + multiReturn = new GoFunctionMultiReturn(returnParams, dfunc, dtm, storageAllocator); + returnType = multiReturn.getStruct(); + } + dfunc.retval = DWARFVariable.fromDataType(dfunc, returnType); + dfunc.params = realParams; + dfunc.varArg = false; // golang varargs are implemented via slice parameter, so this is always false + + return multiReturn; + } + + private void fixupCustomStorage(DWARFFunction dfunc, Function gfunc, + GoParamStorageAllocator storageAllocator, DataTypeManager dtm, + GoFunctionMultiReturn multiReturn) { + // + // This method implements the pseudo-code in + // https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md. + // + + // Allocate custom storage for each parameter + List spillVars = new ArrayList<>(); + for (DWARFVariable dvar : dfunc.params) { + List regStorage = storageAllocator.getRegistersFor(dvar.type); + if (regStorage != null && !regStorage.isEmpty()) { + dvar.setRegisterStorage(regStorage); + spillVars.add(dvar); + if (dvar.type instanceof Structure && + dvar.getStorageSize() != dvar.type.getLength()) { + Msg.warn(GoFunctionFixup.class, + "Known storage allocation problem: func %s@%s param %s register allocation for structs missing inter-field padding." + .formatted(dfunc.name.getName(), dfunc.address, + dvar.name.getName())); + } + + } + else { + if (!dvar.isZeroByte()) { + long stackOffset = storageAllocator.getStackAllocation(dvar.type); + dvar.setStackStorage(stackOffset); + } + else { + if (dvar.isEmptyArray()) { + dvar.type = GoFunctionFixup.makeEmptyArrayDataType(dvar.type); + } + Address zerobaseAddress = getZerobaseAddress(dfunc); + dvar.setRamStorage(zerobaseAddress.getOffset()); + } + } + } + storageAllocator.alignStack(); + storageAllocator.resetRegAllocation(); + + // Allocate custom storage for the return value + if (!dfunc.retval.isZeroByte()) { + dfunc.retval.clearStorage(); + + if (multiReturn != null) { + // allocate storage for individual elements of the struct because they were + // originally separate return values. + // Also turn off endianness fixups in the registers that are fetched + // because we will do it manually + for (DataTypeComponent dtc : multiReturn.getNormalStorageComponents()) { + allocateReturnStorage(dfunc, dfunc.retval, + dtc.getFieldName() + "-return-result-alias", dtc.getDataType(), + storageAllocator, false); + } + + // do items marked as "stack" last (because their order was modified to match + // the decompiler's expectations for storage layout) + for (DataTypeComponent dtc : multiReturn.getStackStorageComponents()) { + allocateReturnStorage(dfunc, dfunc.retval, + dtc.getFieldName() + "-return-result-alias", dtc.getDataType(), + storageAllocator, false); + } + + Program program = gfunc.getProgram(); + if (!program.getMemory().isBigEndian()) { + // revserse the ordering of the storage varnodes when little-endian + List varnodes = dfunc.retval.getVarnodes(); + GoFunctionFixup.reverseNonStackStorageLocations(varnodes); + dfunc.retval.setVarnodes(varnodes); + } + } + else { + allocateReturnStorage(dfunc, dfunc.retval, "return-value-alias-variable", + dfunc.retval.type, storageAllocator, true); + } + } + else { + if (dfunc.retval.isEmptyArray()) { + dfunc.retval.type = GoFunctionFixup.makeEmptyArrayDataType(dfunc.retval.type); + } + if (!dfunc.retval.isVoidType()) { + dfunc.retval.setRamStorage(getZerobaseAddress(dfunc).getOffset()); + } + } + storageAllocator.alignStack(); + + // For any parameters that were passed as registers, the golang caller pre-allocates + // space on the stack for the parameter value to be used when the register is overwritten. + // Ghidra decompilation results are improved if those storage locations are covered + // by variables that we create artificially. + for (DWARFVariable dvar : spillVars) { + DWARFVariable spill = DWARFVariable.fromDataType(dfunc, dvar.type); + String paramName = dvar.name.getName() + "-spill"; + spill.name = dvar.name.replaceName(paramName, paramName); + spill.setStackStorage(storageAllocator.getStackAllocation(spill.type)); + dfunc.localVars.add(spill); + } + + // Override "localVarErrors" because we are pretty sure go's dwarf output is + // trustworthy now that we've over-written everything. + // See SanityCheckDWARFFunctionFixup + dfunc.localVarErrors = false; + dfunc.signatureCommitMode = CommitMode.STORAGE; + } + + private void allocateReturnStorage(DWARFFunction dfunc, DWARFVariable dvar, String name, + DataType dt, GoParamStorageAllocator storageAllocator, boolean allowEndianFixups) { + + List regStorage = storageAllocator.getRegistersFor(dt, allowEndianFixups); + if (regStorage != null && !regStorage.isEmpty()) { + dvar.addRegisterStorage(regStorage); + } + else { + if (!DWARFUtil.isZeroByteDataType(dt)) { + long stackOffset = storageAllocator.getStackAllocation(dt); + dvar.addStackStorage(stackOffset, dt.getLength()); + dfunc.localVars.add(createReturnResultAliasVar(dfunc, dt, name, stackOffset)); + } + } + } + + private DWARFVariable createReturnResultAliasVar(DWARFFunction dfunc, DataType dataType, + String name, long stackOffset) { + DWARFVariable returnResultVar = + DWARFVariable.fromDataType(dfunc, dataType); + returnResultVar.name = dfunc.name.createChild(name, name, SymbolType.LOCAL_VAR); + returnResultVar.setStackStorage(stackOffset); + return returnResultVar; + } + +// /** +// * Create a structure that holds the multiple return values from a golang func. +// *

    +// * The contents of the structure may not be in the same order as the formal declaration, +// * but instead are ordered to make custom varnode storage work. +// *

    +// * Because stack varnodes must be placed in a certain order of storage, items that are +// * stack based are tagged with a text comment "stack" to allow storage to be correctly +// * recalculated later. +// * +// * @param returnParams +// * @param dfunc +// * @param dtm +// * @param storageAllocator +// * @return +// */ +// public static Structure createStructForReturnValues(List returnParams, +// DWARFFunction dfunc, DataTypeManager dtm, +// GoParamStorageAllocator storageAllocator) { +// +// String returnStructName = dfunc.name.getName() + MULTIVALUE_RETURNTYPE_SUFFIX; +// DWARFNameInfo structDNI = dfunc.name.replaceName(returnStructName, returnStructName); +// Structure struct = +// new StructureDataType(structDNI.getParentCP(), structDNI.getName(), 0, dtm); +// struct.setPackingEnabled(true); +// struct.setExplicitPackingValue(1); +// +// storageAllocator = storageAllocator.clone(); +// List stackResults = new ArrayList<>(); +// // TODO: zero-length items also need to be segregated at the end of the struct +// for (DWARFVariable dvar : returnParams) { +// List regs = storageAllocator.getRegistersFor(dvar.type); +// if (regs == null || regs.isEmpty()) { +// stackResults.add(dvar); +// } +// else { +// struct.add(dvar.type, dvar.name.getName(), regs.toString()); +// } +// } +// +// boolean be = dfunc.getProgram().isBigEndian(); +// +// // add these to the struct last or first, depending on endianness +// for (int i = 0; i < stackResults.size(); i++) { +// DWARFVariable dvar = stackResults.get(i); +// if (be) { +// struct.add(dvar.type, dvar.name.getName(), "stack"); +// } +// else { +// struct.insert(i, dvar.type, -1, dvar.name.getName(), "stack"); +// } +// } +// +// return struct; +// } + + private void exportOrigFuncDef(DWARFFunction dfunc, DataTypeManager dtm) { + try { + FunctionDefinition funcDef = dfunc.asFuncDef(); + funcDef.setCategoryPath(GOLANG_API_EXPORT); + dtm.addDataType(funcDef, DataTypeConflictHandler.KEEP_HANDLER); + } + catch (DuplicateNameException e) { + // skip + } + } + + private GoVer getGolangVersion(DWARFFunction dfunc) { + DWARFProgram dprog = dfunc.getProgram(); + GoVer ver = dprog.getOpaqueProperty(GoVer.class, null, GoVer.class); + if (ver == null) { + GoBuildInfo goBuildInfo = GoBuildInfo.fromProgram(dprog.getGhidraProgram()); + ver = goBuildInfo != null ? goBuildInfo.getVerEnum() : GoVer.UNKNOWN; + dprog.setOpaqueProperty(GoVer.class, ver); + } + return ver; + } + + private static final String GOLANG_ZEROBASE_ADDR = "GOLANG_ZEROBASE_ADDR"; + + private Address getZerobaseAddress(DWARFFunction dfunc) { + DWARFProgram dprog = dfunc.getProgram(); + Address zerobaseAddr = dprog.getOpaqueProperty(GOLANG_ZEROBASE_ADDR, null, Address.class); + if (zerobaseAddr == null) { + zerobaseAddr = GolangSymbolAnalyzer.getZerobaseAddress(dprog.getGhidraProgram()); + dprog.setOpaqueProperty(GOLANG_ZEROBASE_ADDR, zerobaseAddr); + } + return zerobaseAddr; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java index a525b3f3a8..5b99044858 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GolangElfInfoProducer.java @@ -22,7 +22,6 @@ import ghidra.app.util.bin.format.elf.ElfLoadHelper; import ghidra.app.util.bin.format.elf.info.ElfInfoItem; import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ReaderFunc; import ghidra.app.util.bin.format.elf.info.ElfInfoProducer; -import ghidra.program.model.data.CategoryPath; import ghidra.program.model.listing.Program; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -42,8 +41,6 @@ import ghidra.util.task.TaskMonitor; * */ public class GolangElfInfoProducer implements ElfInfoProducer { - public static final CategoryPath GO_CATEGORYPATH = new CategoryPath("/golang"); - private static final Map> GOLANGINFO_READERS = Map.of( GoBuildInfo.SECTION_NAME, GoBuildInfo::read, NoteGoBuildId.SECTION_NAME, NoteGoBuildId::read); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java index 7611bb82a0..3756ff9e7d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/NoteGoBuildId.java @@ -79,7 +79,7 @@ public class NoteGoBuildId extends ElfNote { @Override public StructureDataType toStructure(DataTypeManager dtm) { StructureDataType struct = - ElfNote.createNoteStructure(GolangElfInfoProducer.GO_CATEGORYPATH, + ElfNote.createNoteStructure(GoConstants.GOLANG_CATEGORYPATH, "NoteGoBuildId_%d".formatted(description.length), false, nameLen, 0, dtm); struct.add(StringUTF8DataType.dataType, description.length, "BuildId", null); return struct; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java new file mode 100644 index 0000000000..d4bf745fad --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/PEGoBuildId.java @@ -0,0 +1,131 @@ +/* ### + * 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.golang; + +import java.util.Arrays; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteArrayProvider; +import ghidra.app.util.bin.format.elf.info.ElfInfoItem; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities.ClearDataMode; +import ghidra.program.model.listing.Program; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.Msg; + +/** + * Similar to {@link NoteGoBuildId}, but re-implemented here because of the different + * serialization used in PE binaries. (the logic about the buildid payload is trivial so + * there is no worry about duplicating code) + *

    + * + */ +public class PEGoBuildId implements ElfInfoItem { + private static final byte[] GO_BUILDID_MAGIC = + "\u00ff Go build ID: \"".getBytes(StandardCharsets.ISO_8859_1); + private static final int BUILDID_STR_LEN = 83; + + public static ItemWithAddress findBuildId(Program program) { + ItemWithAddress wrappedItem = ElfInfoItem.readItemFromSection(program, + ".text", PEGoBuildId::read); + return wrappedItem; + } + + /** + * Attempts to read a PEGoBuildId from the specified stream. + * + * @param br BinaryReader stream (typically the beginning of the ".text" section) + * @param program_notused not used, but needed to match functional interface + * @return PEGoBuildId instance, or null if not present + */ + public static PEGoBuildId read(BinaryReader br, Program program_notused) { + try { + byte[] magic = br.readNextByteArray(GO_BUILDID_MAGIC.length); + if (!Arrays.equals(magic, GO_BUILDID_MAGIC)) { + return null; + } + String buildIdStr = br.readNextAsciiString(BUILDID_STR_LEN); + return new PEGoBuildId(buildIdStr); + } + catch (IOException e) { + // fall thru and return null + } + return null; + } + + /** + * Attempts to read a PEGoBuildId from the specified InputStream (useful for early compiler + * detection before file is loaded). + * + * @param is {@link InputStream} providing access to the ".text" section of a PE binary + * @return PEGoBuildId instance, or null if not present + */ + public static PEGoBuildId read(InputStream is) { + byte[] buffer = new byte[GO_BUILDID_MAGIC.length + BUILDID_STR_LEN]; + try { + int bytesRead = is.read(buffer); + if (bytesRead == buffer.length) { + ByteArrayProvider bap = new ByteArrayProvider(buffer); + return read(new BinaryReader(bap, false /* doesn't matter */), null); + } + } + catch (IOException e) { + // fall thru + } + return null; + } + + private final String buildId; + + public PEGoBuildId(String buildId) { + this.buildId = buildId; + } + + public String getBuildId() { + return buildId; + } + + @Override + public void markupProgram(Program program, Address address) { + program.getOptions(Program.PROGRAM_INFO).setString("Golang BuildId", getBuildId()); + + try { + StructureDataType struct = toStructure(program.getDataTypeManager()); + if (struct != null) { + DataUtilities.createData(program, address, struct, -1, false, + ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA); + } + } + catch (CodeUnitInsertionException e) { + Msg.error(this, "Failed to markup PEGoBuildId at %s: %s".formatted(address, this)); + } + + } + + private StructureDataType toStructure(DataTypeManager dtm) { + StructureDataType result = + new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildId", 0, dtm); + result.add(StringDataType.dataType, GO_BUILDID_MAGIC.length, "magic", null); + result.add(StringDataType.dataType, BUILDID_STR_LEN, "buildId", null); + + return result; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java new file mode 100644 index 0000000000..4ea4f410ec --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java @@ -0,0 +1,136 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.database.function.OverlappingFunctionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.SymbolUtilities; +import ghidra.util.Msg; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.InvalidInputException; + +@StructureMapping(structureName = "runtime._func") +public class GoFuncData implements StructureMarkup { + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @EOLComment("description") + @MarkupReference("funcAddress") + private long entryoff; + + @FieldMapping + @MarkupReference("nameAddress") + private long nameoff; + + public Address getFuncAddress() { + return getModuledata().getText().add(entryoff); + } + + public Address getNameAddress() { + return getModuledata().getFuncnametab().getArrayAddress().add(nameoff); + } + + public String getName() throws IOException { + BinaryReader reader = + programContext.getReader(getModuledata().getFuncnametab().getArrayOffset() + nameoff); + return reader.readNextUtf8String(); + } + + public String getDescription() throws IOException { + return getName() + "@" + getFuncAddress(); + } + + public boolean isInline() { + return entryoff == -1 || entryoff == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG; + } + + private GoModuledata getModuledata() { + return programContext.findContainingModuleByFuncData(context.getStructureStart()); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public String getStructureName() throws IOException { + return getName(); + } + + @Override + public void additionalMarkup() throws IOException { + Address addr = getFuncAddress(); + String name = SymbolUtilities.replaceInvalidChars(getName(), true); + Program program = programContext.getProgram(); + + Function function = program.getListing().getFunctionAt(addr); + if (function == null) { + try { + if (!program.getMemory() + .getLoadedAndInitializedAddressSet() + .contains(addr)) { + Msg.warn(this, + "Unable to create function not contained within loaded memory: %s@%s" + .formatted(name, addr)); + return; + } + function = program.getFunctionManager() + .createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED); + } + catch (OverlappingFunctionException | InvalidInputException e) { + Msg.error(this, e); + } + } + else { + // TODO: this does nothing. re-evalulate this logic + programContext.labelAddress(addr, name); + } + } + +} +/* +struct runtime._func +Length: 40 Alignment: 4 +{ + uint32 entryoff + int32 nameoff + int32 args + uint32 deferreturn + uint32 pcsp + uint32 pcfile + uint32 pcln + uint32 npcdata + uint32 cuOffset + runtime.funcID funcID + runtime.funcFlag flag + uint8[1] _ + uint8 nfuncdata +} pack() + +*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java new file mode 100644 index 0000000000..e53b47674d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java @@ -0,0 +1,52 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; + +@StructureMapping(structureName = "runtime.functab") +public class GoFunctabEntry { + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference("funcAddress") + private long entryoff; // offset relative to module's .text + + @FieldMapping + @MarkupReference("funcData") + private long funcoff; // offset into pclntable -> _func + + public Address getFuncAddress() { + return getModuledata().getText().add(entryoff); + } + + @Markup + public GoFuncData getFuncData() throws IOException { + return funcoff != 0 ? getModuledata().getFuncDataInstance(funcoff) : null; + } + + private GoModuledata getModuledata() { + return programContext.findContainingModuleByFuncData(context.getStructureStart()); + } +} + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java new file mode 100644 index 0000000000..7cc7dd963e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoIface.java @@ -0,0 +1,42 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; + +@StructureMapping(structureName = "runtime.iface") +public class GoIface { + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference("itab") + long tab; // runtime.itab * + + @FieldMapping + private long data; + + @Markup + public GoItab getItab() throws IOException { + return programContext.readStructure(GoItab.class, tab); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java new file mode 100644 index 0000000000..74e1fad680 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoItab.java @@ -0,0 +1,126 @@ +/* ### + * 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.golang.rtti; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.types.GoInterfaceType; +import ghidra.app.util.bin.format.golang.rtti.types.GoType; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; + +@PlateComment +@StructureMapping(structureName = "runtime.itab") +public class GoItab implements StructureMarkup { + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference("interfaceType") + long inter; // runtime.interfacetype * + + @FieldMapping + @MarkupReference("type") + long _type; // runtime._type * + + @FieldMapping + long fun; // inline varlen array, specd as uintptr[1], we are treating as simple long + + @Markup + public GoInterfaceType getInterfaceType() throws IOException { + return programContext.readStructure(GoInterfaceType.class, inter); + } + + @Markup + public GoType getType() throws IOException { + return programContext.getGoType(_type); + } + + public long getFuncCount() throws IOException { + GoInterfaceType iface = getInterfaceType(); + GoSlice methods = iface.getMethodsSlice(); + return Math.max(1, methods.getLen()); + } + + public GoSlice getFunSlice() throws IOException { + long funcCount = getFuncCount(); + long funOffset = context.getStructureEnd() - programContext.getPtrSize(); + return new GoSlice(funOffset, funcCount, funcCount, programContext); + } + + @Override + public String getStructureName() throws IOException { + return getInterfaceType().getStructureName(); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public void additionalMarkup() throws IOException { + GoSlice funSlice = getFunSlice(); + List

    funcAddrs = Arrays.stream(funSlice.readUIntList(programContext.getPtrSize())) + .mapToObj(offset -> programContext.getCodeAddress(offset)) + .collect(Collectors.toList()); + // this adds references from the elements of the artificial slice. However, the reference + // from element[0] of the real "fun" array won't show anything in the UI even though + // there is a outbound reference there. + funSlice.markupElementReferences(programContext.getPtrSize(), funcAddrs); + + GoSlice extraFunSlice = + funSlice.getSubSlice(1, funSlice.getLen() - 1, programContext.getPtrSize()); + extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", (DataType) null, + true); + } + + @Override + public String toString() { + try { + String s = "itab for " + getStructureName(); + GoInterfaceType ifaceType = getInterfaceType(); + String methodListString = ifaceType.getMethodListString(); + if (!methodListString.isEmpty()) { + s += "\n// Methods\n" + methodListString; + } + return s; + } + catch (IOException e) { + return super.toString(); + } + } + +} +/* +struct runtime.itab +Length: 20 Alignment: 4 +{ + runtime.interfacetype * inter + runtime._type * _type + uint32 hash + uint8[4] _ + uintptr[1] fun +} pack() +*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java new file mode 100644 index 0000000000..1a4754eb0d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java @@ -0,0 +1,328 @@ +/* ### + * 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.golang.rtti; + +import java.util.*; +import java.util.stream.Collectors; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.rtti.types.GoType; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.StringUTF8DataType; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolUtilities; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +/** + * Represents a golang moduledata structure, which contains a lot of invaluable bootstrapping + * data for RTTI and function data. + */ +@StructureMapping(structureName = "runtime.moduledata") +public class GoModuledata implements StructureMarkup { + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext structureContext; + + @FieldMapping + @MarkupReference + private long pcHeader; // pointer to the GoPcHeader instance, useful for bootstrapping + + @FieldMapping + @MarkupReference + private long text; + + @FieldMapping(fieldName = "types") + private long typesOffset; + + @FieldMapping(fieldName = "etypes") + private long typesEndOffset; + + @FieldMapping(fieldName = "typelinks") + private GoSlice typeLinks; + + @FieldMapping + private GoSlice funcnametab; // []uint8 blob of null term strings + + @FieldMapping + private GoSlice cutab; // []uint32 + + @FieldMapping + private GoSlice filetab; // []uint8 blob of null term strings + + @FieldMapping + private GoSlice pctab; // []uint8 + + @FieldMapping + private GoSlice pclntable; // []uint8, shares footprint with ftab + + @FieldMapping + private GoSlice ftab; // []runtime.functab, shares footprint with pclntable + + @FieldMapping + private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab) + + public GoModuledata() { + } + + public boolean matchesPclntab(GoPcHeader pclntab) { + return pclntab.getTextStart().equals(getText()) && + pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress()); + } + + @Markup + public GoPcHeader getPcHeader() throws IOException { + return programContext.readStructure(GoPcHeader.class, pcHeader); + } + + public Address getText() { + return programContext.getCodeAddress(text); + } + + public long getTypesOffset() { + return typesOffset; + } + + public long getTypesEndOffset() { + return typesEndOffset; + } + + public GoFuncData getFuncDataInstance(long offset) throws IOException { + return programContext.readStructure(GoFuncData.class, pclntable.getArrayOffset() + offset); + } + + public boolean containsFuncDataInstance(long offset) { + return pclntable.isOffsetWithinData(offset, 1); + } + + public boolean isValid() { + MemoryBlock txtBlock = programContext.getProgram().getMemory().getBlock(".text"); + if (txtBlock != null && txtBlock.getStart().getOffset() != text) { + return false; + } + + MemoryBlock typelinkBlock = programContext.getProgram().getMemory().getBlock(".typelink"); + if (typelinkBlock != null && + typelinkBlock.getStart().getOffset() != typeLinks.getArrayOffset()) { + return false; + } + + // all these static slices should be allocated with len == cap. If not true, fail. + if (!typeLinks.isFull() || !filetab.isFull() || !pctab.isFull() || !pclntable.isFull() || + !ftab.isFull()) { + return false; + } + + return true; + } + + public GoSlice getFuncnametab() { + return funcnametab; + } + + @Override + public StructureContext getStructureContext() { + return structureContext; + } + + @Override + public void additionalMarkup() throws IOException { + typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false); + typeLinks.markupElementReferences(4, getTypeList()); + + itablinks.markupArray("moduledata.itablinks", GoItab.class, true); + + //cutab.markupArray("moduledata.cutab", programContext.getUint32DT(), false); + markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen()); + markupStringTable(filetab.getArrayAddress(), filetab.getLen()); + + if (ftab.getLen() > 0) { + // chop off the last entry as it is not a full entry (it just points to the address + // at the end of the text segment) and can conflict with markup of the following structs + int entryLen = + programContext.getStructureMappingInfo(GoFunctabEntry.class).getStructureLength(); + GoSlice subSlice = ftab.getSubSlice(0, ftab.getLen() - 1, entryLen); + subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false); + subSlice.markupArrayElements(GoFunctabEntry.class); + } + } + + @Markup + public List getItabs() throws IOException { + List result = new ArrayList<>(); + + long[] itabAddrs = itablinks.readUIntList(programContext.getPtrSize()); + for (long itabAddr : itabAddrs) { + GoItab itab = programContext.readStructure(GoItab.class, itabAddr); + result.add(itab); + } + + return result; + } + + private void markupStringTable(Address addr, long stringTableLength) { + DataType stringDT = StringUTF8DataType.dataType; + long startOfString = addr.getOffset(); + long endOfStringTable = startOfString + stringTableLength; + BinaryReader reader = programContext.getReader(startOfString); + try { + while (startOfString < endOfStringTable) { + reader.readNextUtf8String(); // don't care about string, just stream position after read + long len = reader.getPointerIndex() - startOfString; + if (len > 0 && len < Integer.MAX_VALUE) { + Address stringAddr = addr.getNewAddress(startOfString); + programContext.markupAddress(stringAddr, stringDT, (int) len); + } + startOfString = reader.getPointerIndex(); + } + } + catch (IOException e) { + Msg.warn(this, "Failed when marking up string table at: " + addr, e); + } + } + + @Markup + public Iterator iterateTypes() throws IOException { + return getTypeList().stream() + .map(addr -> { + try { + return programContext.getGoType(addr); + } + catch (IOException e) { + return null; + } + }) + .filter(Objects::nonNull) + .iterator(); + } + + public List
    getTypeList() throws IOException { + long[] typeOffsets = typeLinks.readUIntList(4 /* always sizeof(int32) */); + Address typesBaseAddr = programContext.getDataAddress(typesOffset); + List
    result = Arrays.stream(typeOffsets) + .mapToObj(offset -> typesBaseAddr.add(offset)) + .collect(Collectors.toList()); + return result; + } + + //-------------------------------------------------------------------------------------------- + /** + * Returns an easily found first GoModuledata instance. + * + * @param context already initialized {@link GoRttiContext} + * @return new GoModuledata instance, or null if not found + * @throws IOException + */ + /* package */ static GoModuledata getFirstModuledata(GoRttiContext context) + throws IOException { + Program program = context.getProgram(); + Symbol firstModuleDataSymbol = + SymbolUtilities.getUniqueSymbol(program, "runtime.firstmoduledata"); + if (firstModuleDataSymbol == null) { + return null; + } + return context.readStructure(GoModuledata.class, firstModuleDataSymbol.getAddress()); + } + + /** + * Searches memory for a likely GoModuledata + * + * @param context already initialized {@link GoRttiContext} + * @param pclntabAddress address of an already found {@link GoPcHeader} + * @param pclntab the {@link GoPcHeader} + * @param range memory range to search. Will be different for different types of binaries + * @param monitor {@link TaskMonitor} + * @return new GoModuledata instance, or null if not found + * @throws IOException + */ + /* package */ static GoModuledata findFirstModule(GoRttiContext context, + Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor) + throws IOException { + if (range == null) { + return null; + } + + Program program = context.getProgram(); + Memory memory = program.getMemory(); + + int ptrSize = context.getPtrSize(); + byte[] searchBytes = new byte[ptrSize]; + context.getDataConverter().putValue(pclntabAddress.getOffset(), ptrSize, searchBytes, 0); + Address moduleAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(), + searchBytes, null, true, monitor); + if (moduleAddr == null) { + return null; + } + + GoModuledata moduleData = context.readStructure(GoModuledata.class, moduleAddr); + return moduleData.matchesPclntab(pclntab) ? moduleData : null; + } +} + +/* +struct runtime.moduledata Length:276 Alignment:4{ + runtime.pcHeader*pcHeader + []uint8 funcnametab + []uint32 cutab + []uint8 filetab + []uint8 pctab + []uint8 pclntable + []runtime.functab ftab +uintptr findfunctab +uintptr minpc +uintptr maxpc +uintptr text +uintptr etext +uintptr noptrdata +uintptr enoptrdata +uintptr data +uintptr edata +uintptr bss +uintptr ebss +uintptr noptrbss +uintptr enoptrbss +uintptr end +uintptr gcdata +uintptr gcbss +uintptr types +uintptr etypes +uintptr rodata +uintptr gofunc + []runtime.textsect textsectmap + []int32 typelinks + []*runtime.itab itablinks + [] +runtime.ptabEntry ptab +string pluginpath + []runtime.modulehash pkghashes +string modulename + []runtime.modulehash modulehashes +uint8 hasmain +runtime.bitvector gcdatamask +runtime.bitvector gcbssmask map[runtime.typeOff]*runtime._type typemap +bool bad runtime.moduledata*next +}pack()*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java new file mode 100644 index 0000000000..e8b4ada0a3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoName.java @@ -0,0 +1,147 @@ +/* ### + * 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.golang.rtti; + +import java.util.EnumSet; +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.DataType; + +/** + * Represents a golang "name" construct, which isn't represented in go as a normal structure + * since it is full of variable length and optional fields. + *
    + * struct {
    + * 	byte flag;
    + * 	varint strlen;
    + * 	char[strlen] chars; 
    + * 	(optional: varint tag_strlen; char [tag_strlen];)
    + * 	(optional: int32 pkgpath)
    + * }
    + * 
    + * Because this type has variable length fields (@FieldOutput(isVariableLength=true)), there will + * be unique structure data types produced for each size combination of a GoName structure, and + * will be named "GoName_N_M", where N and M are the lengths of the variable fields [name, tag] + */ +@StructureMapping(structureName = "GoName") +public class GoName implements StructureReader, StructureMarkup { + public enum Flag { + EXPORTED(1 << 0), + HAS_TAG(1 << 1), + HAS_PKGPATH(1 << 2), + EMBEDDED(1 << 3); + + private final int flagValue; + + private Flag(int flagValue) { + this.flagValue = flagValue; + } + + public boolean isSet(int value) { + return (value & flagValue) != 0; + } + + public static Set parseFlags(int b) { + EnumSet result = EnumSet.noneOf(Flag.class); + for (Flag flag : values()) { + if (flag.isSet(b)) { + result.add(flag); + } + } + return result; + } + + } + + @ContextField + private StructureContext context; + + @ContextField + private GoRttiContext programContext; + + @FieldOutput(dataTypeName = "byte") + @EOLComment("flagsSet") + int flags; + + @FieldOutput(isVariableLength = true) + @EOLComment("fullNameString") + GoVarlenString name; + + @FieldOutput(isVariableLength = true) + GoVarlenString tag; + + @FieldOutput(isVariableLength = true, getter = "pkgPathDataType") + @MarkupReference("pkgPath") + long pkgPath; // uint32, nameoffset, only present if flags.HAS_PKGPATH + + @Override + public void readStructure() throws IOException { + flags = context.getReader().readNextUnsignedByte(); + name = programContext.readStructure(GoVarlenString.class, context.getReader()); + tag = Flag.HAS_TAG.isSet(flags) + ? programContext.readStructure(GoVarlenString.class, context.getReader()) + : null; + pkgPath = Flag.HAS_PKGPATH.isSet(flags) + ? context.getReader().readNextUnsignedInt() + : 0; + } + + public String getName() { + return name.getString(); + } + + public String getTag() { + return tag != null ? tag.getString() : ""; + } + + @Markup + public GoName getPkgPath() throws IOException { + return programContext.resolveNameOff(context.getStructureStart(), pkgPath); + } + + public DataType getPkgPathDataType() { + return Flag.HAS_PKGPATH.isSet(flags) + ? programContext.getInt32DT() + : null; + } + + public String getFullNameString() throws IOException { + GoName pkgPathName = getPkgPath(); + return (pkgPathName != null ? pkgPathName.getFullNameString() + "." : "") + getName(); + } + + public int getFlags() { + return flags; + } + + public Set getFlagsSet() { + return Flag.parseFlags(flags); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public String getStructureName() throws IOException { + return getName(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java new file mode 100644 index 0000000000..dbc93ca912 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java @@ -0,0 +1,239 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; + +import ghidra.app.util.bin.*; +import ghidra.app.util.bin.format.golang.GoVer; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.*; +import ghidra.util.task.TaskMonitor; + +/** + * A low-level structure embedded in golang binaries that contains useful bootstrapping + * information. + *

    + * + */ +@StructureMapping(structureName = "runtime.pcHeader") +public class GoPcHeader { + private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab"; + public static final String GOPCLNTAB_SECTION_NAME = ".gopclntab"; + public static final int GO_1_2_MAGIC = 0xfffffffb; + public static final int GO_1_16_MAGIC = 0xfffffffa; + public static final int GO_1_18_MAGIC = 0xfffffff0; + + /** + * Returns the {@link Address} (if present) of the go pclntab section or symbol. + * + * @param program {@link Program} + * @return {@link Address} of go pclntab, or null if not present + */ + public static Address getPclntabAddress(Program program) { + MemoryBlock pclntabBlock = program.getMemory().getBlock(GOPCLNTAB_SECTION_NAME); + if (pclntabBlock != null) { + return pclntabBlock.getStart(); + } + // PE binaries have a symbol instead of a named section + Symbol pclntabSymbol = SymbolUtilities.getUniqueSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME); + return pclntabSymbol != null + ? pclntabSymbol.getAddress() + : null; + } + + /** + * Returns true if the specified program has an easily found pclntab + * + * @param program {@link Program} + * @return boolean true if program has a pclntab, false otherwise + */ + public static boolean hasPclntab(Program program) { + Address addr = getPclntabAddress(program); + if (addr != null) { + try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) { + return isPclntab(provider); + } + catch (IOException e) { + // fall thru + } + } + return false; + } + + /** + * Searches (possibly slowly) for a pclntab structure in the specified memory range, which + * is typically necessary in stripped PE binaries. + * + * @param programContext {@link GoRttiContext} + * @param range memory range to search (typically .rdata or .noptrdata sections) + * @param monitor {@link TaskMonitor} that will let the user cancel + * @return {@link Address} of the found pclntab structure, or null if not found + * @throws IOException + */ + public static Address findPclntabAddress(GoRttiContext programContext, AddressRange range, + TaskMonitor monitor) throws IOException { + if (range == null) { + return null; + } + // search for magic signature + padding + wildcard_minLc + ptrSize + byte[] searchBytes = new byte[/*4 + 2 + 1 + 1*/] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // magic signature + 0, 0, // padding + 0, // unknown minLc, masked + (byte) programContext.getPtrSize() // ptrSize + }; + byte[] searchMask = new byte[] { + (byte) 0xf0, (byte) 0xff, (byte) 0xff, (byte) 0xf0, // magic, first byte nibble and last byte nibble is wildcard to handle either endian matching + (byte) 0xff, (byte) 0xff, // padding + 0, // unknown minLc - wildcarded + (byte) 0xff // ptrSize + }; + Memory memory = programContext.getProgram().getMemory(); + Address pclntabAddr = + memory.findBytes(range.getMinAddress(), range.getMaxAddress(), searchBytes, searchMask, + true, TaskMonitor.DUMMY); + if (pclntabAddr == null) { + return null; + } + MemoryByteProvider bp = + new MemoryByteProvider(memory, pclntabAddr, range.getMaxAddress()); + return isPclntab(bp) ? pclntabAddr : null; + } + + /** + * Returns true if there is a pclntab at the current position of the specified ByteProvider. + * + * @param provider {@link ByteProvider} + * @return boolean true if the byte provider has the magic signature of a pclntab + * @throws IOException + */ + public static boolean isPclntab(ByteProvider provider) throws IOException { + byte[] header = provider.readBytes(0, 8); + // logic from pclntab.go parsePclnTab() + if (provider.length() < 16 || + header[4] != 0 || header[5] != 0 || // pad bytes == 0 + (header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4 + (header[7] != 4 && header[7] != 8) // ptrSize == 4,8 + ) { + return false; + } + return readMagic(provider) != null; + } + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @EOLComment("goVersion") + private int magic; + + @FieldMapping + private byte ptrSize; + + @FieldMapping + @MarkupReference + private long textStart; // should be same as offset of ".text" + + @FieldMapping + @MarkupReference("funcnameAddress") + private long funcnameOffset; + + @FieldMapping + @MarkupReference("cuAddress") + private long cuOffset; + + @FieldMapping + @MarkupReference("filetabAddress") + private long filetabOffset; + + @FieldMapping + @MarkupReference("pctabAddress") + private long pctabOffset; + + @FieldMapping + @MarkupReference("pclnAddress") + private long pclnOffset; + + public GoVer getGoVersion() { + // TODO: this might be better as a static helper method that can be used by multiple + // GoPcHeader struct versions (if necessary) + GoVer ver = switch (magic) { + case GO_1_2_MAGIC -> GoVer.V1_2; + case GO_1_16_MAGIC -> GoVer.V1_16; + case GO_1_18_MAGIC -> GoVer.V1_18; + default -> GoVer.UNKNOWN; + }; + return ver; + } + + public Address getTextStart() { + return programContext.getDataAddress(textStart); + } + + public Address getFuncnameAddress() { + return programContext.getDataAddress(context.getStructureStart() + funcnameOffset); + } + + public Address getCuAddress() { + return programContext.getDataAddress(context.getStructureStart() + cuOffset); + } + + public Address getFiletabAddress() { + return programContext.getDataAddress(context.getStructureStart() + filetabOffset); + } + + public Address getPctabAddress() { + return programContext.getDataAddress(context.getStructureStart() + pctabOffset); + } + + public Address getPclnAddress() { + return programContext.getDataAddress(context.getStructureStart() + pclnOffset); + } + + //-------------------------------------------------------------------------------------------- + record GoVerEndian(GoVer goVer, Endian endian) { + GoVerEndian(GoVer goVer, boolean isLittleEndian) { + this(goVer, isLittleEndian ? Endian.LITTLE : Endian.BIG); + } + } + + private static GoVerEndian readMagic(ByteProvider provider) throws IOException { + int leMagic = new BinaryReader(provider, true /* little endian */).readInt(0); + int beMagic = new BinaryReader(provider, false /* big endian */).readInt(0); + + if (leMagic == GO_1_2_MAGIC || beMagic == GO_1_2_MAGIC) { + return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC); + } + else if (leMagic == GO_1_16_MAGIC || beMagic == GO_1_16_MAGIC) { + return new GoVerEndian(GoVer.V1_16, leMagic == GO_1_16_MAGIC); + } + else if (leMagic == GO_1_18_MAGIC || beMagic == GO_1_18_MAGIC) { + return new GoVerEndian(GoVer.V1_18, leMagic == GO_1_18_MAGIC); + } + return null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiContext.java new file mode 100644 index 0000000000..f3f0da11c7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiContext.java @@ -0,0 +1,595 @@ +/* ### + * 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.golang.rtti; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram; +import ghidra.app.util.bin.format.golang.*; +import ghidra.app.util.bin.format.golang.rtti.types.*; +import ghidra.app.util.bin.format.golang.structmapping.ProgramContext; +import ghidra.app.util.bin.format.golang.structmapping.StructureMapping; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.ElfLoader; +import ghidra.app.util.opinion.PeLoader; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.util.Msg; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; +import ghidra.util.task.UnknownProgressWrappingTaskMonitor; + +/** + * {@link ProgramContext} for golang binaries. + *

    + * When bootstrapping golang binaries, the following steps are used: + *

      + *
    • Find the GoBuildInfo struct. This struct is the easiest to locate, even when the binary + * is stripped. This gives us the go pointerSize (probably same as ghidra pointer size) and the + * goVersion. This struct does not rely on StructureMapping, allowing its use before a + * ProgramContext is created. + *
    • Create ProgramContext + *
    • Find the runtime.firstmoduledata structure. + *
        + *
      • If there are symbols, just use the symbol or named memory block. + *
      • If stripped: + *
          + *
        • Find the pclntab. This has a magic signature, a pointerSize, and references + * to a couple of tables that are also referenced in the moduledata structure. + *
        • Search memory for a pointer to the pclntab struct. This should be the first + * field of the moduledata structure. The values that are duplicated between the + * two structures can be compared to ensure validity. + *
        • Different binary formats (Elf vs PE) will determine which memory blocks to + * search. + *
        + *
      + *
    + */ +public class GoRttiContext extends ProgramContext { + + /** + * Returns a new {@link GoRttiContext} for the specified program, or null if the binary + * is not a supported golang binary. + * + * @param program {@link Program} + * @param log + * @return new {@link GoRttiContext}, or null if not a golang binary + * @throws IOException + */ + public static GoRttiContext getContextFor(Program program, MessageLog log) throws IOException { + GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program); + GoVer goVer; + if (buildInfo == null || (goVer = buildInfo.getVerEnum()) == GoVer.UNKNOWN) { + return null; + } + ResourceFile gdtFile = + findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program)); + if (gdtFile == null) { + Msg.error(GoRttiContext.class, "Missing golang gdt archive for " + goVer); + } + + try { + return new GoRttiContext(program, buildInfo.getPointerSize(), buildInfo.getEndian(), + buildInfo.getVerEnum(), gdtFile); + } + catch (IllegalArgumentException e) { + // user deserves a warning because the binary wasn't supported + log.appendMsg(e.getMessage()); + return null; + } + } + + public static String getGDTFilename(GoVer goVer, int pointerSizeInBytes, String osName) { + String bitSize = pointerSizeInBytes > 0 + ? Integer.toString(pointerSizeInBytes * 8) + : "any"; + String gdtFilename = + "golang_%d.%d_%sbit_%s.gdt".formatted(goVer.getMajor(), goVer.getMinor(), + bitSize, osName); + return gdtFilename; + } + + public static String getGolangOSString(Program program) { + String loaderName = program.getExecutableFormat(); + if (ElfLoader.ELF_NAME.equals(loaderName)) { + // TODO: this will require additional work to map all Golang OSs to Ghidra loader info + return "linux"; + } + else if (PeLoader.PE_NAME.equals(loaderName)) { + return "win"; + } + else { + return null; + } + } + + /** + * Searches for a golang bootstrap gdt file that matches the specified Go version/size/OS. + *

    + * First looks for a gdt with an exact match, then for a gdt with version/size match and + * "any" OS, and finally, a gdt that matches the version and "any" size and "any" OS. + * + * @param goVer version of Go + * @param ptrSize size of pointers + * @param osName name of OS + * @return + */ + public static ResourceFile findGolangBootstrapGDT(GoVer goVer, int ptrSize, String osName) { + ResourceFile result = null; + if (osName != null) { + result = DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, ptrSize, osName)); + } + if (result == null) { + result = DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, ptrSize, "any")); + } + if (result == null) { + result = + DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, -1 /*any*/, "any")); + } + return result; + } + + private static final CategoryPath RECOVERED_TYPES_CP = new CategoryPath("/golang-recovered"); + private static final CategoryPath GOLANG_CP = GoConstants.GOLANG_CATEGORYPATH; + private static final CategoryPath VARLEN_STRUCTS_CP = GoConstants.GOLANG_CATEGORYPATH; + + /** + * List of java classes that are {@link StructureMapping structure mapped}. + *

    + * If a class isn't included in this list, it can't be used. + */ + private static final List> GOLANG_STRUCTMAPPED_CLASSES = + List.of(GoModuledata.class, GoName.class, GoVarlenString.class, GoSlice.class, + GoBaseType.class, GoTypeDetector.class, GoPlainType.class, GoUncommonType.class, + GoArrayType.class, GoChanType.class, GoFuncType.class, GoInterfaceType.class, + GoMapType.class, GoPointerType.class, GoSliceType.class, GoIface.class, + GoStructType.class, GoMethod.class, GoStructField.class, GoIMethod.class, + GoFunctabEntry.class, GoFuncData.class, GoItab.class, GoString.class, GoPcHeader.class); + + private final BinaryReader reader; + private final GoVer goVersion; + private final int ptrSize; + private final Endian endian; + private final DataType uintptrDT; + private final DataType int32DT; + private final DataType uint32DT; + private final Map goTypes = new HashMap<>(); + private final Map typeNameIndex = new HashMap<>(); + private final Map cachedRecoveredDataTypes = new HashMap<>(); + private final List modules = new ArrayList<>(); + private GoType mapGoType; + private GoType chanGoType; + + public GoRttiContext(Program program, int ptrSize, Endian endian, GoVer goVersion, + ResourceFile archiveGDT) throws IOException { + super(program, archiveGDT); + + this.goVersion = goVersion; + this.ptrSize = ptrSize; + this.endian = endian; + + reader = super.createProgramReader(); + + addArchiveSearchCategoryPath(CategoryPath.ROOT, GOLANG_CP); + addProgramSearchCategoryPath(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH); + + this.uintptrDT = getTypeOrDefault("uintptr", DataType.class, + AbstractIntegerDataType.getUnsignedDataType(ptrSize, getDTM())); + this.int32DT = getTypeOrDefault("int32", DataType.class, + AbstractIntegerDataType.getSignedDataType(4, null)); + this.uint32DT = getTypeOrDefault("uint32", DataType.class, + AbstractIntegerDataType.getUnsignedDataType(4, null)); + + try { + registerStructures(GOLANG_STRUCTMAPPED_CLASSES); + } + catch (IOException e) { + if (archiveGDT == null) { + // a normal'ish situation where there isn't a .gdt for this arch/binary and there + // isn't any DWARF. + throw new IllegalArgumentException( + "Missing golang .gdt archive for %s, no fallback DWARF info, unable to extract golang RTTI info." + .formatted(goVersion)); + } + // a bad situation where the data type info is corrupted + throw new IOException("Invalid or missing Golang bootstrap GDT file: %s" + .formatted(archiveGDT.getAbsolutePath())); + } + } + + public GoVer getGolangVersion() { + return goVersion; + } + + public GoModuledata getFirstModule() { + return modules.get(0); + } + + public void addModule(GoModuledata module) { + modules.add(module); + } + + public GoModuledata findContainingModule(long offset) { + for (GoModuledata module : modules) { + if (module.getTypesOffset() <= offset && offset < module.getTypesEndOffset()) { + return module; + } + } + return null; + } + + public GoModuledata findContainingModuleByFuncData(long offset) { + for (GoModuledata module : modules) { + if (module.containsFuncDataInstance(offset)) { + return module; + } + } + return null; + } + + @Override + public CategoryPath getDefaultVariableLengthStructCategoryPath() { + return VARLEN_STRUCTS_CP; + } + + public DataType getUintptrDT() { + return uintptrDT; + } + + public DataType getInt32DT() { + return int32DT; + } + + public DataType getUint32DT() { + return uint32DT; + } + + public Structure getGenericSliceDT() { + return getStructureDataType(GoSlice.class); + } + + public GoType getMapGoType() { + return mapGoType; + } + + public GoType getChanGoType() { + return chanGoType; + } + + @Override + protected BinaryReader createProgramReader() { + return reader.clone(); + } + + public int getPtrSize() { + return ptrSize; + } + + public GoName resolveNameOff(long ptrInModule, long off) throws IOException { + if (off == 0) { + return null; + } + GoModuledata module = findContainingModule(ptrInModule); + long nameStart = module.getTypesOffset() + off; + return getGoName(nameStart); + } + + public GoName getGoName(long offset) throws IOException { + return offset != 0 ? readStructure(GoName.class, offset) : null; + } + + public GoType resolveTypeOff(long ptrInModule, long off) throws IOException { + if (off == 0 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG || off == -1) { + return null; + } + GoModuledata module = findContainingModule(ptrInModule); + return getGoType(module.getTypesOffset() + off); + } + + public GoType getGoType(long offset) throws IOException { + if (offset == 0) { + return null; + } + GoType goType = goTypes.get(offset); + if (goType == null) { + Class typeClass = GoType.getSpecializedTypeClass(this, offset); + goType = readStructure(typeClass, offset); + goTypes.put(offset, goType); + } + return goType; + } + + public GoType getGoType(Address addr) throws IOException { + return getGoType(addr.getOffset()); + } + + public GoType findGoType(String typeName) { + return typeNameIndex.get(typeName); + } + + public T getGhidraDataType(String goTypeName, Class clazz) { + T dt = getType(goTypeName, clazz); + if (dt == null) { + GoType goType = findGoType(goTypeName); + if (goType != null) { + try { + DataType tmpDT = goType.recoverDataType(); + if (clazz.isInstance(tmpDT)) { + dt = clazz.cast(tmpDT); + } + } + catch (IOException e) { + Msg.warn(this, "Failed to get Ghidra data type from go type: %s[%x]" + .formatted(goTypeName, + goType.getStructureContext().getStructureStart())); + } + } + } + return dt; + } + + public Address resolveTextOff(long ptrInModule, long off) { + if (off == -1 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) { + return null; + } + GoModuledata module = findContainingModule(ptrInModule); + return module != null ? module.getText().add(off) : null; + } + + /** + * Export the currently registered struct mapping types to a gdt file. + *

    + * The struct data types will either be from the current program's DWARF data, or + * from an earlier golang.gdt (if this binary doesn't have DWARF) + * + * @param gdtFile + * @throws IOException + */ + public void exportTypesToGDT(File gdtFile, TaskMonitor monitor) throws IOException { + + List registeredStructDTs = mappingInfo.values() + .stream() + .map(smi -> { + if (smi.getStructureDataType() == null) { + return null; + } + DataType existingDT = findType(smi.getStructureName(), + List.of(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH), + programDTM); + if (existingDT == null) { + existingDT = smi.getStructureDataType(); + } + if (existingDT == null) { + Msg.warn(this, "Missing type: " + smi.getDescription()); + } + return existingDT; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + // Copy the data types into a tmp gdt, and then copy them again into the final gdt + // to avoid traces of the original program name as a deleted source archive link in the + // gdt data base. This method only leaves the target gdt filename + ".step1" in the db. + File tmpGDTFile = new File(gdtFile.getParentFile(), gdtFile.getName() + ".step1.gdt"); + FileDataTypeManager tmpFdtm = FileDataTypeManager.createFileArchive(tmpGDTFile); + int tx = -1; + try { + tx = tmpFdtm.startTransaction("Import"); + tmpFdtm.addDataTypes(registeredStructDTs, DataTypeConflictHandler.DEFAULT_HANDLER, + monitor); + moveAllDataTypesTo(tmpFdtm, DWARFProgram.DWARF_ROOT_CATPATH, GOLANG_CP); + for (SourceArchive sa : tmpFdtm.getSourceArchives()) { + tmpFdtm.removeSourceArchive(sa); + } + tmpFdtm.getRootCategory() + .removeCategory(DWARFProgram.DWARF_ROOT_CATPATH.getName(), monitor); + // TODO: could also clear out any description strings on types + } + catch (CancelledException | DuplicateNameException e) { + Msg.error(this, "Error when exporting types to file: %s".formatted(gdtFile), e); + } + finally { + if (tx != -1) { + tmpFdtm.endTransaction(tx, true); + } + } + + tmpFdtm.save(); + + FileDataTypeManager fdtm = FileDataTypeManager.createFileArchive(gdtFile); + tx = -1; + try { + tx = fdtm.startTransaction("Import"); + tmpFdtm.getAllDataTypes() + .forEachRemaining( + dt -> fdtm.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER)); + for (SourceArchive sa : fdtm.getSourceArchives()) { + fdtm.removeSourceArchive(sa); + } + } + finally { + if (tx != -1) { + fdtm.endTransaction(tx, true); + } + } + + fdtm.save(); + + tmpFdtm.close(); + fdtm.close(); + + tmpGDTFile.delete(); + + } + + private void moveAllDataTypesTo(DataTypeManager dtm, CategoryPath srcCP, CategoryPath destCP) + throws DuplicateNameException { + Category srcCat = dtm.getCategory(srcCP); + if (srcCat != null) { + for (DataType dt : srcCat.getDataTypes()) { + if (dt instanceof Array || dt instanceof Pointer) { + continue; + } + dt.setCategoryPath(destCP); + } + for (Category subcat : srcCat.getCategories()) { + moveAllDataTypesTo(dtm, subcat.getCategoryPath(), destCP); // flatten everything + } + } + } + + public CategoryPath getRecoveredTypesCp() { + return RECOVERED_TYPES_CP; + } + + public DataType getRecoveredType(GoType typ) throws IOException { + long offset = getExistingStructureAddress(typ).getOffset(); + DataType dt = cachedRecoveredDataTypes.get(offset); + if (dt != null) { + return dt; + } + dt = typ.recoverDataType(); + cachedRecoveredDataTypes.put(offset, dt); + return dt; + } + + public void cacheRecoveredDataType(GoType typ, DataType dt) throws IOException { + long offset = getExistingStructureAddress(typ).getOffset(); + cachedRecoveredDataTypes.put(offset, dt); + } + + public DataType getCachedRecoveredDataType(GoType typ) throws IOException { + long offset = getExistingStructureAddress(typ).getOffset(); + return cachedRecoveredDataTypes.get(offset); + } + + /** + * Converts all discovered golang rtti type records to Ghidra data types, placing them + * in the program's DTM in /golang-recovered + * + * @throws IOException + * @throws CancelledException + */ + public void recoverDataTypes(TaskMonitor monitor) throws IOException, CancelledException { + monitor.setMessage("Converting Golang types to Ghidra data types"); + monitor.initialize(goTypes.size()); + List typeOffsets = goTypes.keySet().stream().sorted().collect(Collectors.toList()); + for (Long typeOffset : typeOffsets) { + monitor.checkCanceled(); + monitor.incrementProgress(1); + GoType typ = getGoType(typeOffset); + DataType dt = typ.recoverDataType(); + if (programDTM.getDataType(dt.getDataTypePath()) == null) { + programDTM.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER); + } + } + } + + public void discoverGoTypes(TaskMonitor monitor) throws IOException, CancelledException { + GoModuledata firstModule = findFirstModuledata(monitor); + if (firstModule == null) { + return; + } + addModule(firstModule); + + UnknownProgressWrappingTaskMonitor upwtm = + new UnknownProgressWrappingTaskMonitor(monitor, 50); + upwtm.setMessage("Iterating Golang RTTI types"); + upwtm.initialize(0); + + goTypes.clear(); + Set discoveredTypes = new HashSet<>(); + for (Iterator it = firstModule.iterateTypes(); it.hasNext();) { + upwtm.checkCanceled(); + upwtm.setProgress(discoveredTypes.size()); + + GoType type = it.next(); + type.discoverGoTypes(discoveredTypes); + } + typeNameIndex.clear(); + for (GoType goType : goTypes.values()) { + String typeName = goType.getBaseType().getNameString(); + typeNameIndex.put(typeName, goType); + } + Msg.info(this, "Found %d golang types".formatted(goTypes.size())); + initHiddenCompilerTypes(); + } + + private void initHiddenCompilerTypes() { + // these structure types are what golang map and chan types actually point to. + mapGoType = findGoType("runtime.hmap"); + chanGoType = findGoType("runtime.hchan"); + } + + private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException { + GoModuledata result = GoModuledata.getFirstModuledata(this); + if (result == null) { + monitor.setMessage("Searching for Golang pclntab"); + monitor.initialize(0); + Address pclntabAddress = GoPcHeader.getPclntabAddress(program); + if (pclntabAddress == null) { + pclntabAddress = + GoPcHeader.findPclntabAddress(this, getPclntabSearchRange(), monitor); + } + if (pclntabAddress != null) { + monitor.setMessage("Searching for Golang firstmoduledata"); + monitor.initialize(0); + GoPcHeader pclntab = readStructure(GoPcHeader.class, pclntabAddress); + result = GoModuledata.findFirstModule(this, pclntabAddress, pclntab, + getModuledataSearchRange(), monitor); + } + } + if (result != null && !result.isValid()) { + throw new IOException("Invalid Golang moduledata at %s" + .formatted(result.getStructureContext().getStructureAddress())); + } + return result; + } + + private AddressRange getPclntabSearchRange() { + Memory memory = program.getMemory(); + for (String blockToSearch : List.of(".noptrdata", ".rdata")) { + MemoryBlock noptrdataBlock = memory.getBlock(blockToSearch); + if (noptrdataBlock != null) { + return new AddressRangeImpl(noptrdataBlock.getStart(), noptrdataBlock.getEnd()); + } + } + return null; + } + + private AddressRange getModuledataSearchRange() { + Memory memory = program.getMemory(); + for (String blockToSearch : List.of(".noptrdata", ".data")) { + MemoryBlock noptrdataBlock = memory.getBlock(blockToSearch); + if (noptrdataBlock != null) { + return new AddressRangeImpl(noptrdataBlock.getStart(), noptrdataBlock.getEnd()); + } + } + return null; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java new file mode 100644 index 0000000000..ca3c624794 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoSlice.java @@ -0,0 +1,271 @@ +/* ### + * 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.golang.rtti; + +import java.util.ArrayList; +import java.util.List; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.BinaryReader.ReaderFunction; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.symbol.*; +import ghidra.util.Msg; + +@StructureMapping(structureName = "runtime.slice") +public class GoSlice { + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + private long array; + @FieldMapping + private long len; + @FieldMapping + private long cap; + + public GoSlice() { + } + + public GoSlice(long array, long len, long cap) { + this.array = array; + this.len = len; + this.cap = cap; + } + + public GoSlice(long array, long len, long cap, GoRttiContext programContext) { + this(array, len, cap); + this.programContext = programContext; + } + + /** + * Return a artificial view of a portion of this slice's contents. + * + * @param startElement + * @param elementCount + * @param elementSize + * @param programContext + * @return + */ + public GoSlice getSubSlice(long startElement, long elementCount, long elementSize) { + return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, programContext); + } + + public boolean isValid(int elementSize) { + try { + Memory memory = programContext.getProgram().getMemory(); + Address arrayAddr = getArrayAddress(); + return memory.contains(arrayAddr) && + memory.contains(arrayAddr.addNoWrap(len * elementSize)); + } + catch (AddressOverflowException | AddressOutOfBoundsException e) { + return false; + } + } + + public long getArrayOffset() { + return array; + } + + public Address getArrayAddress() { + return programContext.getDataAddress(array); + } + + public long getLen() { + return len; + } + + public long getCap() { + return cap; + } + + public boolean isFull() { + return len == cap; + } + + public boolean isOffsetWithinData(long offset, int sizeofElement) { + return array <= offset && offset < array + (cap * sizeofElement); + } + + /** + * Reads the content of the slice, treating each element as an instance of the specified + * structure mapped class. + * + * @param + * @param clazz element type + * @return list of instances + * @throws IOException + */ + public List readList(Class clazz) throws IOException { + return readList((reader) -> programContext.readStructure(clazz, reader)); + } + + /** + * Reads the contents of the slice, treating each element as an instance of an object that can + * be read using the supplied reading function. + * + * @param + * @param readFunc function that will read an instance from a BinaryReader + * @return list of instances + * @throws IOException + */ + public List readList(ReaderFunction readFunc) throws IOException { + List result = new ArrayList<>(); + + long elementSize = 0; + BinaryReader reader = programContext.getReader(array); + for (int i = 0; i < len; i++) { + T t = readFunc.get(reader); + result.add(t); + if (i == 0) { + elementSize = reader.getPointerIndex() - array; + } + else { + // ensure that the reader func is doing correct thing + if (elementSize > 0 && reader.getPointerIndex() != array + (i + 1) * elementSize) { + Msg.warn(this, "Bad element size when reading slice element (size: %d) at %d" + .formatted(elementSize, reader.getPointerIndex())); + elementSize = 0; + } + } + } + return result; + } + + /** + * Treats this slice as a array of unsigned integers, of the specified intSize. + *

    + * @param intSize size of integer + * @return array of longs, containing the (possibly smaller) integers contained in the slice + * @throws IOException if error reading + */ + public long[] readUIntList(int intSize) throws IOException { + BinaryReader reader = programContext.getReader(array); + return readUIntList(reader, array, intSize, (int) len); + } + + /** + * Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType, + * which has elements who's type is determined by the specified structure class. + * + * @param sliceName used to label the memory location + * @param elementClazz structure mapped class of the element of the array + * @param ptr boolean flag, if true the element type is really a pointer to the supplied + * data type + * @throws IOException if error + */ + public void markupArray(String sliceName, Class elementClazz, boolean ptr) + throws IOException { + DataType dt = programContext.getStructureDataType(elementClazz); + markupArray(sliceName, dt, ptr); + } + + /** + * Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType. + * + * @param sliceName used to label the memory location + * @param elementType Ghidra datatype of the array elements, null ok if ptr == true + * @param ptr boolean flag, if true the element type is really a pointer to the supplied + * data type + * @throws IOException if error + */ + public void markupArray(String sliceName, DataType elementType, boolean ptr) + throws IOException { + if (len == 0) { + return; + } + DataTypeManager dtm = programContext.getDTM(); + if (ptr) { + elementType = new PointerDataType(elementType, programContext.getPtrSize(), dtm); + } + + ArrayDataType arrayDT = new ArrayDataType(elementType, (int) cap, -1, dtm); + Address addr = programContext.getDataAddress(array); + programContext.markupAddress(addr, arrayDT); + if (sliceName != null) { + programContext.labelAddress(addr, sliceName); + } + } + + /** + * Marks up each element of the array, useful when the elements are themselves structures. + * + * @param structure type + * @param clazz class of the structure type + * @return list of element instances + * @throws IOException if error reading + */ + public List markupArrayElements(Class clazz) throws IOException { + if (len == 0) { + return List.of(); + } + + List elementList = readList(clazz); + programContext.markup(elementList, true); + return elementList; + } + + /** + * Marks up each element of the array with an outbound reference to the corresponding address + * in the targetAddrs list. + *

    + * Useful when marking up an array of offsets. + *

    + * The Listing UI doesn't show the outbound reference from each element (for arrays of primitive + * types), but the target will show the inbound reference. + * + * @param elementSize size of each element in the array + * @param targetAddrs list of addresses, should be same size as this slice + * @throws IOException + */ + public void markupElementReferences(int elementSize, List

    targetAddrs) + throws IOException { + if (!targetAddrs.isEmpty()) { + ReferenceManager refMgr = programContext.getProgram().getReferenceManager(); + + Address srcAddr = programContext.getDataAddress(array); + for (Address targetAddr : targetAddrs) { + if (targetAddr != null) { + refMgr.addMemoryReference(srcAddr, targetAddr, RefType.DATA, + SourceType.IMPORTED, 0); + } + srcAddr = srcAddr.add(elementSize); + } + } + + } + + private static long[] readUIntList(BinaryReader reader, long index, int intSize, int count) + throws IOException { + long[] result = new long[count]; + + for (int i = 0; i < count; i++) { + long l = reader.readUnsignedValue(index, intSize); + result[i] = l; + index += intSize; + } + return result; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java new file mode 100644 index 0000000000..eda35746b9 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoString.java @@ -0,0 +1,50 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; + +@StructureMapping(structureName = "string") +public class GoString { + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference("stringAddr") + @EOLComment("stringValue") + private long str; + + @FieldMapping + private long len; + + public Address getStringAddr() { + return context.getProgramContext().getDataAddress(str); + } + + public long getLength() { + return len; + } + + public String getStringValue() throws IOException { + BinaryReader reader = context.getProgramContext().getReader(str); + return reader.readNextUtf8String((int) len); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java new file mode 100644 index 0000000000..75d8f84994 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java @@ -0,0 +1,86 @@ +/* ### + * 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.golang.rtti; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; + +/** + * A pascal-ish string, using a LEB128 value as the length of the following bytes. + *

    + * Used mainly in lower-level RTTI structures, this class is a ghidra'ism used to parse the + * golang rtti data and does not have a counterpart in the golang src. + */ +@StructureMapping(structureName = "GoVarlenString") +public class GoVarlenString implements StructureReader { + + @ContextField + private StructureContext context; + + @FieldMapping(fieldName = "strlen") + @FieldOutput(isVariableLength = true, getter = "strlenDataType") + private int strlenLen; // store the len of the leb128, not the value of the leb128 number + + @FieldMapping(fieldName = "value") + @FieldOutput(isVariableLength = true, getter = "valueDataType") + private byte[] bytes; + + public GoVarlenString() { + } + + @Override + public void readStructure() throws IOException { + readFrom(context.getReader()); + } + + private void readFrom(BinaryReader reader) throws IOException { + long startPos = reader.getPointerIndex(); + int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + this.strlenLen = (int) (reader.getPointerIndex() - startPos); + this.bytes = reader.readNextByteArray(strLen); + } + + public int getStrlen() { + return bytes.length; + } + + public int getStrlenLen() { + return strlenLen; + } + + public byte[] getBytes() { + return bytes; + } + + public String getString() { + return new String(bytes, StandardCharsets.UTF_8); + } + + public DataTypeInstance getStrlenDataType() { + return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen, + false); + } + + public DataType getValueDataType() { + return new ArrayDataType(CharDataType.dataType, bytes.length, -1, + context.getProgramContext().getDTM()); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java new file mode 100644 index 0000000000..bb7dc84b85 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java @@ -0,0 +1,77 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.ArrayDataType; +import ghidra.program.model.data.DataType; + +@StructureMapping(structureName = "runtime.arraytype") +public class GoArrayType extends GoType { + + @FieldMapping + private long elem; // pointer to element type + + @FieldMapping + private long slice; // pointer to slice type + + @FieldMapping + private long len; + + public GoArrayType() { + } + + @Markup + public GoType getElement() throws IOException { + return programContext.getGoType(elem); + } + + @Markup + public GoType getSliceType() throws IOException { + return programContext.getGoType(slice); + } + + @Override + public DataType recoverDataType() throws IOException { + DataType elementDt = programContext.getRecoveredType(getElement()); + DataType self = programContext.getCachedRecoveredDataType(this); + if (self != null) { + return self; + } + return new ArrayDataType(elementDt, (int) len, -1); + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + GoType elementType = getElement(); + GoType sliceType = getSliceType(); + if (elementType != null) { + elementType.discoverGoTypes(discoveredTypes); + } + if (sliceType != null) { + sliceType.discoverGoTypes(discoveredTypes); + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java new file mode 100644 index 0000000000..e00feb28ee --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoBaseType.java @@ -0,0 +1,104 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoName; +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.bin.format.golang.structmapping.*; + +/** + * Represents the fundamental golang rtti type information. + *

    + * The in-memory instance will typically be part of a specialized type structure, depending + * on the 'kind' of this type. + *

    + * Additionally, there will be an GoUncommonType structure immediately after this type, if + * the uncommon bit is set in tflag. + *

    + *

    + * struct specialized_type { basetype_struct; (various_fields)* } struct uncommon; 
    + * 
    + */ +@StructureMapping(structureName = "runtime._type") +public class GoBaseType { + + @ContextField + private StructureContext context; + + @ContextField + private GoRttiContext programContext; + + @FieldMapping(signedness = Signedness.Unsigned) + private long size; + + @FieldMapping + private long ptrdata; + + @FieldMapping + @EOLComment("flags") + private int tflag; + + @FieldMapping + @EOLComment + private int kind; + + @FieldMapping + @MarkupReference("name") + private long str; // an offset relative to containing moduledata's type base addr + + @FieldMapping + @MarkupReference + private long ptrToThis; // an offset relative to containing moduledata's type base addr + + public long getSize() { + return size; + } + + public GoKind getKind() { + return GoKind.parseByte(kind); + } + + public Set getFlags() { + return GoTypeFlag.parseFlags(tflag); + } + + public int getTflag() { + return tflag; + } + + public boolean hasUncommonType() { + return GoTypeFlag.Uncommon.isSet(tflag); + } + + @Markup + public GoName getName() throws IOException { + return programContext.resolveNameOff(context.getStructureStart(), str); + } + + public String getNameString() throws IOException { + String s = getName().getName(); + return GoTypeFlag.ExtraStar.isSet(tflag) ? s.substring(1) : s; + } + + @Markup + public GoType getPtrToThis() throws IOException { + return programContext.resolveTypeOff(context.getStructureStart(), ptrToThis); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java new file mode 100644 index 0000000000..9d40e0c01e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoChanType.java @@ -0,0 +1,76 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; +import ghidra.util.Msg; + +@StructureMapping(structureName = "runtime.chantype") +public class GoChanType extends GoType { + + @FieldMapping + @MarkupReference("element") + private long elem; + + @FieldMapping + private long dir; + + public GoChanType() { + } + + @Markup + public GoType getElement() throws IOException { + return programContext.getGoType(elem); + } + + @Override + public DataType recoverDataType() throws IOException { + GoType chanGoType = programContext.getChanGoType(); + if (chanGoType == null) { + // if we couldn't find the underlying/hidden runtime.hchan struct type, just return + // a void* + return programContext.getDTM().getPointer(null); + } + + DataType chanDT = chanGoType.recoverDataType(); + Pointer ptrChanDt = programContext.getDTM().getPointer(chanDT); + if (typ.getSize() != ptrChanDt.getLength()) { + Msg.warn(this, "Size mismatch between chan type and recovered type"); + } + TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(), + getStructureName(), ptrChanDt, programContext.getDTM()); + return typedef; + + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + GoType element = getElement(); + if (element != null) { + element.discoverGoTypes(discoveredTypes); + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java new file mode 100644 index 0000000000..4e9f6f8d92 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoFuncType.java @@ -0,0 +1,191 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.*; +import java.util.stream.Collectors; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.GoFunctionMultiReturn; +import ghidra.app.util.bin.format.golang.rtti.GoSlice; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; + +@StructureMapping(structureName = "runtime.functype") +public class GoFuncType extends GoType { + + @FieldMapping + private int inCount; // uint16 + + @FieldMapping + private int outCount; // uint16 + + public GoFuncType() { + } + + public boolean isVarArg() { + return (outCount & 0x8000) != 0; + } + + public int getInCount() { + return inCount; + } + + public int getOutCount() { + return outCount & 0x7fff; + } + + public int getParamCount() { + return inCount + (outCount & 0x7fff); + } + + public List
    getParamTypeAddrs() throws IOException { + GoSlice slice = getParamListSlice(); + long[] typeOffsets = slice.readUIntList(programContext.getPtrSize()); + return Arrays.stream(typeOffsets) + .mapToObj(programContext::getDataAddress) + .collect(Collectors.toList()); + } + + private GoSlice getParamListSlice() { + int count = getParamCount(); + return new GoSlice(getOffsetEndOfFullType(), count, count, programContext); + } + + @Markup + public List getParamTypes() throws IOException { + return getParamTypeAddrs().stream() + .map(addr -> { + try { + return programContext.getGoType(addr); + } + catch (IOException e) { + return null; + } + }) + .collect(Collectors.toList()); + } + + @Override + public void additionalMarkup() throws IOException { + GoSlice slice = getParamListSlice(); + slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true); + } + + public String getFuncPrototypeString(String funcName) throws IOException { + StringBuilder sb = new StringBuilder(); + sb.append("func"); + if (funcName != null && !funcName.isBlank()) { + sb.append(" ").append(funcName); + } + sb.append("("); + + List paramTypes = getParamTypes(); + List inParamTypes = paramTypes.subList(0, inCount); + List outParamTypes = paramTypes.subList(inCount, paramTypes.size()); + for (int i = 0; i < inParamTypes.size(); i++) { + GoType paramType = inParamTypes.get(i); + if (i != 0) { + sb.append(", "); + } + sb.append(paramType.getBaseType().getNameString()); + } + sb.append(")"); + if (!outParamTypes.isEmpty()) { + sb.append(" ("); + for (int i = 0; i < outParamTypes.size(); i++) { + GoType paramType = outParamTypes.get(i); + if (i != 0) { + sb.append(", "); + } + sb.append(paramType.getBaseType().getNameString()); + } + sb.append(")"); + } + return sb.toString(); + } + + @Override + public DataType recoverDataType() throws IOException { + String name = typ.getNameString(); + DataTypeManager dtm = programContext.getDTM(); + + List paramTypes = getParamTypes(); + List inParamTypes = paramTypes.subList(0, inCount); + List outParamTypes = paramTypes.subList(inCount, paramTypes.size()); + + List params = new ArrayList<>(); + for (int i = 0; i < inParamTypes.size(); i++) { + GoType paramType = inParamTypes.get(i); + DataType paramDT = paramType.recoverDataType(); + params.add(new ParameterDefinitionImpl(null, paramDT, null)); + } + DataType returnDT; + if (outParamTypes.size() == 0) { + returnDT = VoidDataType.dataType; + } + else if (outParamTypes.size() == 1) { + returnDT = outParamTypes.get(0).recoverDataType(); + } + else { + List paramDataTypes = recoverTypes(outParamTypes); + GoFunctionMultiReturn multiReturn = new GoFunctionMultiReturn( + programContext.getRecoveredTypesCp(), name, paramDataTypes, dtm, null); + returnDT = multiReturn.getStruct(); + } + + FunctionDefinitionDataType funcDef = new FunctionDefinitionDataType( + programContext.getRecoveredTypesCp(), name, dtm); + funcDef.setArguments(params.toArray(ParameterDefinition[]::new)); + funcDef.setReturnType(returnDT); + + return dtm.getPointer(funcDef); + } + + private List recoverTypes(List types) throws IOException { + List result = new ArrayList<>(); + for (GoType type : types) { + result.add(type.recoverDataType()); + } + return result; + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + for (GoType paramType : getParamTypes()) { + if (paramType != null) { + paramType.discoverGoTypes(discoveredTypes); + } + } + return true; + } + + @Override + public String toString() { + try { + return getFuncPrototypeString(null); + } + catch (IOException e) { + return super.toString(); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java new file mode 100644 index 0000000000..22f7871418 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoIMethod.java @@ -0,0 +1,74 @@ +/* ### + * 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.golang.rtti.types; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoName; +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.bin.format.golang.structmapping.*; + +@StructureMapping(structureName = "runtime.imethod") +public class GoIMethod implements StructureMarkup { + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference + @EOLComment("nameString") + private long name; + + @FieldMapping + @MarkupReference("type") + private long ityp; + + @Markup + public GoName getName() throws IOException { + return programContext.resolveNameOff(context.getStructureStart(), name); + } + + public String getNameString() throws IOException { + GoName n = getName(); + return n != null ? n.getName() : "_blank_"; + } + + @Markup + public GoType getType() throws IOException { + return programContext.resolveTypeOff(context.getStructureStart(), ityp); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public String getStructureName() throws IOException { + return getNameString(); + } + +} +/* +struct runtime.imethod // Length: 8 Alignment: 4 +{ + runtime.nameOff name + runtime.typeOff ityp +} pack() +*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java new file mode 100644 index 0000000000..bfb48451b8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoInterfaceType.java @@ -0,0 +1,111 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.List; +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.*; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.TypedefDataType; + +@StructureMapping(structureName = "runtime.interfacetype") +public class GoInterfaceType extends GoType { + + @FieldMapping + @MarkupReference("pkgPath") + private long pkgpath; // pointer to name + + @FieldMapping + private GoSlice mhdr; + + public GoInterfaceType() { + } + + @Markup + public GoName getPkgPath() throws IOException { + return programContext.getGoName(pkgpath); + } + + public String getPkgPathString() throws IOException { + GoName n = getPkgPath(); + return n != null ? n.getName() : ""; + } + + public GoSlice getMethodsSlice() { + return mhdr; + } + + public List getMethods() throws IOException { + return mhdr.readList(GoIMethod.class); + } + + @Override + public void additionalMarkup() throws IOException { + mhdr.markupArray(null, GoIMethod.class, false); + mhdr.markupArrayElements(GoIMethod.class); + } + + @Override + public DataType recoverDataType() throws IOException { + DataType dt = programContext.getStructureDataType(GoIface.class); + + String name = typ.getNameString(); + if (!dt.getName().equals(name)) { + dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, + programContext.getDTM()); + } + return dt; + } + + @Override + public String getMethodListString() throws IOException { + StringBuilder sb = new StringBuilder(); + for (GoIMethod imethod : getMethods()) { + if (!sb.isEmpty()) { + sb.append("\n"); + } + String methodStr = imethod.getNameString(); + GoType type = imethod.getType(); + if (type instanceof GoFuncType funcType) { + methodStr = funcType.getFuncPrototypeString(methodStr); + } + else { + methodStr = "func %s()".formatted(methodStr); + } + sb.append(methodStr); + } + return sb.toString(); + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + for (GoIMethod imethod : getMethods()) { + GoType type = imethod.getType(); + if (type != null) { + type.discoverGoTypes(discoveredTypes); + } + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java new file mode 100644 index 0000000000..cb628fb566 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoKind.java @@ -0,0 +1,57 @@ +/* ### + * 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.golang.rtti.types; + +public enum GoKind { + invalid, + Bool, // 1 + Int, // 2 + Int8, // 3 + Int16, // 4 + Int32, // 5 + Int64, // 6 + Uint, // 7 + Uint8, // 8 + Uint16, // 9 + Uint32, // 10 + Uint64, // 11 + Uintptr, // 12 + Float32, // 13 + Float64, // 14 + Complex64, // 15 + Complex128, // 16 + Array, // 17 + Chan, // 18 + Func, // 19 + Interface, // 20 + Map, // 21 + Pointer, // 22 + Slice, // 23 + String, // 24 + Struct, // 25 + UnsafePointer; // 26 + + public static final int KIND_MASK = (1 << 5) - 1; + public static final int GC_PROG = (1 << 6); + public static final int DIRECT_IFACE = (1 << 5); + + public static GoKind parseByte(int b) { + int ordinal = b & KIND_MASK; + return Bool.ordinal() <= ordinal && ordinal <= UnsafePointer.ordinal() + ? values()[ordinal] + : invalid; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java new file mode 100644 index 0000000000..06826e60cc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMapType.java @@ -0,0 +1,108 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; +import ghidra.util.Msg; + +@StructureMapping(structureName = "runtime.maptype") +public class GoMapType extends GoType { + + @FieldMapping + private long key; // ptr to type + + @FieldMapping + private long elem; // ptr to type + + @FieldMapping + private long bucket; // ptr to type + + @FieldMapping + private long hasher; // pointer to "func(Pointer, pointer) pointer" + + @FieldMapping + private int keysize; + + @FieldMapping + private int elemsize; + + @FieldMapping + private int bucketsize; + + @FieldMapping + private int flags; + + public GoMapType() { + } + + @Markup + public GoType getKey() throws IOException { + return programContext.getGoType(key); + } + + @Markup + public GoType getElement() throws IOException { + return programContext.getGoType(elem); + } + + @Markup + public GoType getBucket() throws IOException { + return programContext.getGoType(bucket); + } + + @Override + public DataType recoverDataType() throws IOException { + GoType mapGoType = programContext.getMapGoType(); + if (mapGoType == null) { + // if we couldn't find the underlying/hidden runtime.hmap struct type, just return + // a void* + return programContext.getDTM().getPointer(null); + } + DataType mapDT = mapGoType.recoverDataType(); + Pointer ptrMapDt = programContext.getDTM().getPointer(mapDT); + if (typ.getSize() != ptrMapDt.getLength()) { + Msg.warn(this, "Size mismatch between map type and recovered type"); + } + TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(), + getStructureName(), ptrMapDt, programContext.getDTM()); + return typedef; + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + GoType keyType = getKey(); + GoType elemType = getElement(); + GoType bucketType = getBucket(); + if (keyType != null) { + keyType.discoverGoTypes(discoveredTypes); + } + if (elemType != null) { + elemType.discoverGoTypes(discoveredTypes); + } + if (bucketType != null) { + bucketType.discoverGoTypes(discoveredTypes); + } + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java new file mode 100644 index 0000000000..1ec66c848c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoMethod.java @@ -0,0 +1,83 @@ +/* ### + * 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.golang.rtti.types; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoName; +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.address.Address; + +@StructureMapping(structureName = "runtime.method") +public class GoMethod implements StructureMarkup { + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference + @EOLComment("nameString") + private long name; // nameOff + + @FieldMapping + @MarkupReference("type") + private long mtyp; // typeOff + + @FieldMapping + @MarkupReference + private long ifn; // textOff + + @FieldMapping + @MarkupReference + private long tfn; // textOff + + @Markup + public GoName getName() throws IOException { + return programContext.resolveNameOff(context.getStructureStart(), name); + } + + public String getNameString() throws IOException { + GoName n = getName(); + return n != null ? n.getName() : "_blank_"; + } + + @Markup + public GoType getType() throws IOException { + return programContext.resolveTypeOff(context.getStructureStart(), mtyp); + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public String getStructureName() throws IOException { + return getNameString(); + } + + public Address getIfn() { + return programContext.resolveTextOff(context.getStructureStart(), ifn); + } + + public Address getTfn() { + return programContext.resolveTextOff(context.getStructureStart(), tfn); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java new file mode 100644 index 0000000000..c4d6af2256 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPlainType.java @@ -0,0 +1,86 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoString; +import ghidra.app.util.bin.format.golang.structmapping.StructureMapping; +import ghidra.app.util.bin.format.golang.structmapping.StructureReader; +import ghidra.program.model.data.*; +import ghidra.util.Msg; + +/** + * WARNING: tricky code / class layout here! + *

    + * To coerce java inheritance and structmapping features to match the layout of go rtti type structs, + * this class is constructed strangely. + */ +@StructureMapping(structureName = "runtime._type") +public class GoPlainType extends GoType implements StructureReader { + @Override + public void readStructure() throws IOException { + this.typ = context.getProgramContext().readStructure(GoBaseType.class, context.getReader()); + } + + @Override + public DataType recoverDataType() throws IOException { + DataTypeManager dtm = programContext.getDTM(); + DataType dt = switch (typ.getKind()) { + case Bool -> BooleanDataType.dataType; + case Float32 -> AbstractFloatDataType.getFloatDataType(32 / 8, null); + case Float64 -> AbstractFloatDataType.getFloatDataType(64 / 8, null); + case Int -> AbstractIntegerDataType.getSignedDataType(programContext.getPtrSize(), dtm); + case Int8 -> AbstractIntegerDataType.getSignedDataType(8 / 8, null); + case Int16 -> AbstractIntegerDataType.getSignedDataType(16 / 8, null); + case Int32 -> AbstractIntegerDataType.getSignedDataType(32 / 8, null); + case Int64 -> AbstractIntegerDataType.getSignedDataType(64 / 8, null); + case Uint -> AbstractIntegerDataType.getUnsignedDataType(programContext.getPtrSize(), + dtm); + case Uint8 -> AbstractIntegerDataType.getUnsignedDataType(8 / 8, null); + case Uint16 -> AbstractIntegerDataType.getUnsignedDataType(16 / 8, null); + case Uint32 -> AbstractIntegerDataType.getUnsignedDataType(32 / 8, null); + case Uint64 -> AbstractIntegerDataType.getUnsignedDataType(64 / 8, null); + case Uintptr -> AbstractIntegerDataType.getUnsignedDataType(programContext.getPtrSize(), + dtm); + case String -> programContext.getStructureDataType(GoString.class); + case UnsafePointer -> dtm.getPointer(null); + default -> null; + }; + if (dt == null) { + dt = super.recoverDataType(); + } + + String name = typ.getNameString(); + if (!dt.getName().equalsIgnoreCase(name)) { + dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, dtm); + } + if (dt.getLength() != typ.getSize()) { + Msg.warn(this, + "Recovered golang data type size mismatch: %s, %d != %d".formatted(getDebugId(), + typ.getSize(), dt.getLength())); + } + return dt; + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + return super.discoverGoTypes(discoveredTypes); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java new file mode 100644 index 0000000000..b832c3eb7a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoPointerType.java @@ -0,0 +1,61 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.PointerDataType; + +@StructureMapping(structureName = "runtime.ptrtype") +public class GoPointerType extends GoType { + @FieldMapping + @MarkupReference("element") + private long elem; + + public GoPointerType() { + } + + @Markup + public GoType getElement() throws IOException { + return programContext.getGoType(elem); + } + + @Override + public DataType recoverDataType() throws IOException { + DataType elementDT = programContext.getRecoveredType(getElement()); + DataType self = programContext.getCachedRecoveredDataType(this); + if (self != null) { + return self; + } + return new PointerDataType(elementDT, programContext.getDTM()); + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + GoType element = getElement(); + if (element != null) { + element.discoverGoTypes(discoveredTypes); + } + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java new file mode 100644 index 0000000000..b0c784fabd --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoSliceType.java @@ -0,0 +1,72 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; + +@StructureMapping(structureName = "runtime.slicetype") +public class GoSliceType extends GoType { + + @FieldMapping + @MarkupReference("element") + private long elem; + + public GoSliceType() { + } + + @Markup + public GoType getElement() throws IOException { + return programContext.getGoType(elem); + } + + @Override + public DataType recoverDataType() throws IOException { + int arrayPtrComponentIndex = 0; /* HACK, field ordinal of void* data field in slice type */ + Structure genericSliceDT = programContext.getGenericSliceDT(); + DataTypeComponent arrayDTC = genericSliceDT.getComponent(arrayPtrComponentIndex); + + GoType elementType = getElement(); + DataType elementDT = elementType.recoverDataType(); + Pointer elementPtrDT = programContext.getDTM().getPointer(elementDT); + + StructureDataType sliceDT = + new StructureDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), 0, + programContext.getDTM()); + sliceDT.replaceWith(genericSliceDT); + sliceDT.replace(arrayPtrComponentIndex, elementPtrDT, -1, arrayDTC.getFieldName(), + arrayDTC.getComment()); + + return sliceDT; + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + GoType elementType = getElement(); + if (elementType != null) { + elementType.discoverGoTypes(discoveredTypes); + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java new file mode 100644 index 0000000000..29d58758f1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java @@ -0,0 +1,79 @@ +/* ### + * 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.golang.rtti.types; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoName; +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.bin.format.golang.structmapping.*; + +@StructureMapping(structureName = "runtime.structfield") +public class GoStructField { + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping + @MarkupReference + @EOLComment("nameString") + private long name; // direct ptr to GoName + + @FieldMapping + @MarkupReference("type") + private long typ; // direct ptr to GoType + + @FieldMapping(fieldName = "offsetAnon" /* TODO or "anon" */) + private long offset; // offset >> 1 == actual offset, bit 0 = embedded + + @Markup + public GoName getName() throws IOException { + return name != 0 + ? context.getProgramContext().readStructure(GoName.class, name) + : null; + } + + @Markup + public GoType getType() throws IOException { + return programContext.getGoType(typ); + } + + public long getOffset() { + return offset >> 1; + } + + public boolean isEmbedded() { + return (offset & 0x1) != 0; + } + + public String getNameString() throws IOException { + GoName n = getName(); + return n != null ? n.getName() : null; + } +} +/* + +struct runtime.structfield + Length: 24 Alignment: 8 +{ + runtime.name name + runtime._type * typ + uintptr offsetAnon ---- name changed to offset in next golang ver +} pack() +*/ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java new file mode 100644 index 0000000000..432cc959d3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructType.java @@ -0,0 +1,161 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.*; + +import java.io.IOException; + +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.app.util.bin.format.golang.rtti.GoName; +import ghidra.app.util.bin.format.golang.rtti.GoSlice; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; +import ghidra.util.Msg; + +@StructureMapping(structureName = "runtime.structtype") +public class GoStructType extends GoType { + + @FieldMapping + @MarkupReference + private long pkgPath; // name + + @FieldMapping + private GoSlice fields; + + public GoStructType() { + } + + @Markup + public GoName getPkgPath() throws IOException { + return programContext.getGoName(pkgPath); + } + + public String getPkgPathString() throws IOException { + GoName n = getPkgPath(); + return n != null ? n.getName() : ""; + } + + public List getFields() throws IOException { + return fields.readList(GoStructField.class); + } + + @Override + public void additionalMarkup() throws IOException { + super.additionalMarkup(); + fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false); + fields.markupArrayElements(GoStructField.class); + } + + @Override + public String getTypeDeclString() throws IOException { + String methodListStr = getMethodListString(); + if (methodListStr == null || methodListStr.isEmpty()) { + methodListStr = "// No methods"; + } + else { + methodListStr = "// Methods\n" + methodListStr; + } + + return """ + // size: %d + type %s struct { + %s + %s + } + """.formatted( + typ.getSize(), + typ.getNameString(), + getFieldListString().indent(2), + methodListStr.indent(2)); + } + + private String getFieldListString() throws IOException { + StringBuilder sb = new StringBuilder(); + for (GoStructField field : getFields()) { + if (!sb.isEmpty()) { + sb.append("\n"); + } + long offset = field.getOffset(); + long fieldSize = field.getType().getBaseType().getSize(); + sb.append("%s %s // %d..%d".formatted(field.getNameString(), + field.getType().getBaseType().getNameString(), offset, offset + fieldSize)); + } + return sb.toString(); + } + + @Override + public DataType recoverDataType() throws IOException { + StructureDataType struct = new StructureDataType(programContext.getRecoveredTypesCp(), + typ.getNameString(), (int) typ.getSize(), programContext.getDTM()); + programContext.cacheRecoveredDataType(this, struct); + + List skippedFields = new ArrayList<>(); + List fieldList = getFields(); + for (int i = 0; i < fieldList.size(); i++) { + GoStructField field = fieldList.get(i); + GoStructField nextField = i < fieldList.size() - 1 ? fieldList.get(i + 1) : null; + long availSpace = nextField != null + ? nextField.getOffset() - field.getOffset() + : typ.getSize() - field.getOffset(); + + GoType fieldType = field.getType(); + long fieldSize = fieldType.getBaseType().getSize(); + + if (fieldSize == 0) { + skippedFields.add(field); + continue; + } + + try { + DataType fieldDT = programContext.getRecoveredType(fieldType); + struct.replaceAtOffset((int) field.getOffset(), fieldDT, (int) fieldSize, + field.getNameString(), null); + } + catch (IllegalArgumentException e) { + Msg.warn(this, + "Failed to add field to go recovered struct: %s".formatted(getDebugId()), e); + } + } + for (GoStructField skippedField : skippedFields) { + DataTypeComponent dtc = + struct.getDefinedComponentAtOrAfterOffset((int) skippedField.getOffset()); + GoType skippedFieldType = skippedField.getType(); + if (dtc != null) { + String comment = dtc.getComment(); + comment = comment == null ? "" : (comment + "\n"); + comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getNameString(), + skippedFieldType.getBaseType().getNameString()); + dtc.setComment(comment); + } + } + + DWARFUtil.packCompositeIfPossible(struct, programContext.getDTM()); + return struct; + } + + @Override + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!super.discoverGoTypes(discoveredTypes)) { + return false; + } + for (GoStructField field : getFields()) { + field.getType().discoverGoTypes(discoveredTypes); + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java new file mode 100644 index 0000000000..1cdaf5f477 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoType.java @@ -0,0 +1,180 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.Map; +import java.util.Set; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.GoRttiContext; +import ghidra.app.util.bin.format.golang.rtti.GoSlice; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.program.model.data.*; + +@PlateComment() +public abstract class GoType implements StructureMarkup { + private static final Map> specializedTypeClasses = + Map.ofEntries( + Map.entry(GoKind.Struct, GoStructType.class), + Map.entry(GoKind.Pointer, GoPointerType.class), + Map.entry(GoKind.Func, GoFuncType.class), + Map.entry(GoKind.Slice, GoSliceType.class), + Map.entry(GoKind.Array, GoArrayType.class), + Map.entry(GoKind.Chan, GoChanType.class), + Map.entry(GoKind.Map, GoMapType.class), + Map.entry(GoKind.Interface, GoInterfaceType.class)); + + public static Class getSpecializedTypeClass(GoRttiContext programContext, + long offset) throws IOException { + GoTypeDetector typeDetector = programContext.readStructure(GoTypeDetector.class, offset); + Class result = specializedTypeClasses.get(typeDetector.getKind()); + if (result == null) { + result = GoPlainType.class; + } + return result; + } + + @ContextField + protected GoRttiContext programContext; + + @ContextField + protected StructureContext context; + + @FieldMapping + @Markup + @FieldOutput + protected GoBaseType typ; + + public GoBaseType getBaseType() { + return typ; + } + + public String getDebugId() { + return "%s@%s".formatted( + context.getMappingInfo().getDescription(), + context.getStructureAddress()); + } + + protected long getOffsetEndOfFullType() { + return context.getStructureEnd() + + (typ.hasUncommonType() + ? programContext.getStructureMappingInfo(GoUncommonType.class) + .getStructureLength() + : 0); + } + + @Markup + public GoUncommonType getUncommonType() throws IOException { + return typ.hasUncommonType() + ? programContext.readStructure(GoUncommonType.class, context.getStructureEnd()) + : null; + } + + @Override + public StructureContext getStructureContext() { + return context; + } + + @Override + public String getStructureName() throws IOException { + return typ.getNameString(); + } + + @Override + public void additionalMarkup() throws IOException { + GoUncommonType uncommonType = getUncommonType(); + if (uncommonType != null) { + GoSlice slice = uncommonType.getMethodsSlice(); + slice.markupArray(getStructureName() + "_methods", GoMethod.class, false); + slice.markupArrayElements(GoMethod.class); + + programContext.labelStructure(uncommonType, typ.getNameString() + "_" + + programContext.getStructureDataTypeName(GoUncommonType.class)); + } + } + + public String getMethodListString() throws IOException { + GoUncommonType uncommonType = getUncommonType(); + if (uncommonType == null || uncommonType.mcount == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (GoMethod method : uncommonType.getMethods()) { + if (!sb.isEmpty()) { + sb.append("\n"); + } + String methodStr = method.getNameString(); + GoType type = method.getType(); + if (type instanceof GoFuncType funcType) { + methodStr = funcType.getFuncPrototypeString(methodStr); + } + else { + methodStr = "func %s()".formatted(methodStr); + } + sb.append(methodStr); + } + return sb.toString(); + } + + protected String getTypeDeclString() throws IOException { + String s = "type " + typ.getNameString() + " " + typ.getKind(); + String methodListString = getMethodListString(); + if (!methodListString.isEmpty()) { + s += "\n// Methods\n" + methodListString; + } + return s; + } + + @Override + public String toString() { + try { + return getTypeDeclString(); + } + catch (IOException e) { + return super.toString(); + } + } + + /** + * Converts a golang RTTI type structure into a Ghidra data type. + * + * @return {@link DataType} that represents the golang type + * @throws IOException + */ + public DataType recoverDataType() throws IOException { + DataType dt = Undefined.getUndefinedDataType((int) typ.getSize()); + return new TypedefDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), dt, + programContext.getDTM()); + } + + public boolean discoverGoTypes(Set discoveredTypes) throws IOException { + if (!discoveredTypes.add(context.getStructureStart())) { + return false; + } + GoUncommonType uncommonType = getUncommonType(); + if (uncommonType != null) { + for (GoMethod method : uncommonType.getMethods()) { + GoType methodType = method.getType(); + if (methodType != null) { + methodType.discoverGoTypes(discoveredTypes); + } + } + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeDetector.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeDetector.java new file mode 100644 index 0000000000..2d9ef56caa --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeDetector.java @@ -0,0 +1,33 @@ +/* ### + * 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.golang.rtti.types; + +import ghidra.app.util.bin.format.golang.structmapping.FieldMapping; +import ghidra.app.util.bin.format.golang.structmapping.StructureMapping; + +/** + * Small stub that is only used to fetch the "kind" field so that the real gotype can be detected + */ +@StructureMapping(structureName = "runtime._type") +public class GoTypeDetector { + @FieldMapping + private int kind; + + public GoKind getKind() { + return GoKind.parseByte(kind); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java new file mode 100644 index 0000000000..c29d0f244b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoTypeFlag.java @@ -0,0 +1,51 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.EnumSet; +import java.util.Set; + +public enum GoTypeFlag { + Uncommon(1 << 0), // 1 + ExtraStar(1 << 1), // 2 + Named(1 << 2), // 4 + RegularMemory(1 << 3); // 8 + + private final int value; + + private GoTypeFlag(int i) { + this.value = i; + } + + public int getValue() { + return value; + } + + public boolean isSet(int i) { + return (i & value) != 0; + } + + public static Set parseFlags(int b) { + EnumSet result = EnumSet.noneOf(GoTypeFlag.class); + for (GoTypeFlag flag : values()) { + if (flag.isSet(b)) { + result.add(flag); + } + } + return result; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java new file mode 100644 index 0000000000..4d7f0c0325 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoUncommonType.java @@ -0,0 +1,73 @@ +/* ### + * 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.golang.rtti.types; + +import java.util.List; + +import java.io.IOException; + +import ghidra.app.util.bin.format.golang.rtti.*; +import ghidra.app.util.bin.format.golang.structmapping.*; +import ghidra.util.Msg; + +@StructureMapping(structureName = "runtime.uncommontype") +public class GoUncommonType { + + @ContextField + private GoRttiContext programContext; + + @ContextField + private StructureContext context; + + @FieldMapping(fieldName = "pkgpath") + @MarkupReference("pkgPath") + @EOLComment("packagePathString") + long pkgpath_nameOff; + + @FieldMapping + int mcount; + + @FieldMapping + int xcount; + + @FieldMapping + long moff; + + @Markup + public GoName getPkgPath() throws IOException { + return programContext.resolveNameOff(context.getStructureStart(), pkgpath_nameOff); + } + + public String getPackagePathString() throws IOException { + GoName pkgPath = getPkgPath(); + return pkgPath != null ? pkgPath.getName() : null; + } + + public GoSlice getMethodsSlice() { + return new GoSlice(context.getFieldLocation(moff), mcount, mcount, programContext); + } + + public List getMethods() throws IOException { + GoSlice slice = getMethodsSlice(); + if (!slice.isValid( + programContext.getStructureMappingInfo(GoMethod.class).getStructureLength())) { + Msg.warn(this, "Bad uncommon method list: %s".formatted(context.getStructureAddress())); + return List.of(); + } + return slice.readList(GoMethod.class); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/AfterStructureRead.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/AfterStructureRead.java new file mode 100644 index 0000000000..53296119ea --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/AfterStructureRead.java @@ -0,0 +1,33 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the tagged method should be executed after the structure has been created + * and its data read into its structure mapped fields. + * + */ +@Retention(RUNTIME) +@Target(METHOD) +public @interface AfterStructureRead { + // nothing +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ContextField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ContextField.java new file mode 100644 index 0000000000..1e95bb4068 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ContextField.java @@ -0,0 +1,32 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the tagged field should be initialized with the value of the appropriate + * context object, either {@link ProgramContext} or {@link StructureContext}. + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface ContextField { + // nothing +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/EOLComment.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/EOLComment.java new file mode 100644 index 0000000000..ac63524224 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/EOLComment.java @@ -0,0 +1,39 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the tagged field should have an "end-of-line" comment placed + * at each instance of the field, using the return value of a getter method for the field + * or the return value of a specified method as the string. + */ +@Retention(RUNTIME) +@Target({ FIELD }) +public @interface EOLComment { + /** + * Name of a "getter" method that's return value will be converted to a string and used + * as the EOL comment + * + * @return + */ + String value() default ""; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldContext.java new file mode 100644 index 0000000000..5cf6bb4c29 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldContext.java @@ -0,0 +1,66 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.symbol.*; + +/** + * Context of an individual field that is being deserialized + * + * @param the class that contains this field + */ +public record FieldContext ( + StructureContext structureContext, + FieldMappingInfo fieldInfo, + DataTypeComponent dtc, + BinaryReader reader) { + + public T getStructureInstance() { + return structureContext.getStructureInstance(); + } + + public ProgramContext getProgramContext() { + return structureContext().getProgramContext(); + } + + public void appendComment(int commentType, String prefix, String comment, String sep) + throws IOException { + DWARFUtil.appendComment(structureContext.getProgram(), getAddress(), commentType, prefix, + comment, sep); + } + + public void addReference(Address refDest) throws IOException { + ReferenceManager refMgr = structureContext.getProgram().getReferenceManager(); + + Address fieldAddr = getAddress(); + refMgr.addMemoryReference(fieldAddr, refDest, RefType.DATA, SourceType.IMPORTED, 0); + } + + public Address getAddress() { + return structureContext.getStructureAddress().add(dtc.getOffset()); + } + + public R getValue(Class expectedType) throws IOException { + return fieldInfo.getValue(structureContext.getStructureInstance(), expectedType); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java new file mode 100644 index 0000000000..30ac0041ed --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java @@ -0,0 +1,50 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the tagged java field corresponds to a field in a Ghidra structure. + *

    + * If the name of the java field does not match the Ghidra structure field name, the + * {@link #fieldName()} property can be used to manually specify the Ghidra field name. + *

    + * The type of the tagged java field can be a java primitive, or a + * {@link StructureMapping structure mapped} class. + * + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface FieldMapping { + String fieldName() default ""; + + @SuppressWarnings("rawtypes") + Class readFunc() default FieldReadFunction.class; + + int length() default -1; + + /** + * Override the signedness the underlying numeric field. + * + * @return + */ + Signedness signedness() default Signedness.Unspecified; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java new file mode 100644 index 0000000000..f17b6f481c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMappingInfo.java @@ -0,0 +1,327 @@ +/* ### + * 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.golang.structmapping; + +import java.util.*; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.CodeUnit; + +/** + * Immutable information needed to deserialize a field in a structure mapped class. + * + * @param + */ +public class FieldMappingInfo { + /** + * Creates a FieldMappingInfo instance, used when the structure is not variable length. + * + * @param + * @param field + * @param dtc + * @param signedness + * @param length + * @return + */ + public static FieldMappingInfo createEarlyBinding(Field field, DataTypeComponent dtc, + Signedness signedness, int length) { + + signedness = signedness == Signedness.Unspecified + ? ReflectionHelper.getDataTypeSignedness(dtc.getDataType()) + : signedness; + length = length != -1 ? length : dtc.getLength(); + + return new FieldMappingInfo<>(field, dtc.getFieldName(), dtc, signedness, length); + } + + /** + * Creates a FieldMappingInfo instance, used when the structure is variable length and there is + * no pre-defined Ghidra Structure data type. + * + * @param + * @param field + * @param fieldName + * @param signedness + * @param length + * @return + */ + public static FieldMappingInfo createLateBinding(Field field, String fieldName, + Signedness signedness, int length) { + + return new FieldMappingInfo<>(field, fieldName, null, signedness, length); + } + + private final Field field; + private final String dtcFieldName; + private final DataTypeComponent dtc; + private final Signedness signedness; + private final int length; + private final List> markupFuncs = new ArrayList<>(); + + private FieldReadFunction readerFunc; + + private FieldMappingInfo(Field field, String dtcFieldName, DataTypeComponent dtc, + Signedness signedness, int length) { + this.field = field; + this.dtcFieldName = dtcFieldName; + this.dtc = dtc; + this.signedness = signedness; + this.length = length; + } + + public Field getField() { + return field; + } + + public String getFieldName() { + return dtcFieldName; + } + + public DataTypeComponent getDtc() { + return dtc; + } + + public DataTypeComponent getDtc(Structure structure) { + return dtc != null + ? dtc + : findDtc(structure); + } + + public DataTypeComponent findDtc(Structure struct) { + for (DataTypeComponent dtc : struct.getDefinedComponents()) { + if (dtcFieldName.equals(dtc.getFieldName())) { + return dtc; + } + } + return null; + } + + public FieldReadFunction getReaderFunc() { + return readerFunc; + } + + public List> getMarkupFuncs() { + return markupFuncs; + } + + public void addMarkupFunc(FieldMarkupFunction func) { + markupFuncs.add(func); + } + + public int getLength() { + return length; + } + + public Signedness getSignedness() { + return signedness; + } + + public boolean isUnsigned() { + return signedness == Signedness.Unsigned; + } + + public boolean isStructureMappedType() { + return ReflectionHelper.hasStructureMapping(field.getType()); + } + + public R getValue(T structInstance, Class expectedType) throws IOException { + return ReflectionHelper.getFieldValue(structInstance, field, expectedType); + } + + public void addMarkupNestedFuncs() { + Markup ma = field.getAnnotation(Markup.class); + if (ma != null) { + Class fieldType = field.getType(); + if (!ReflectionHelper.hasStructureMapping(fieldType)) { + throw new IllegalArgumentException("Invalid @Markup, %s is not structure mapped. %s" + .formatted(field.getType().getSimpleName(), field)); + } + markupFuncs.add(this::markupNestedStructure); + } + } + + public void addMarkupReferenceFunc() { + MarkupReference mufa = field.getAnnotation(MarkupReference.class); + if (mufa != null) { + markupFuncs.add(makeMarkupReferenceFunc(mufa.value())); + } + + } + + public void addCommentMarkupFuncs() { + Class clazz = field.getDeclaringClass(); + PlateComment pca = field.getAnnotation(PlateComment.class); + if (pca != null) { + Method commentGetter = + ReflectionHelper.getCommentMethod(clazz, pca.value(), field.getName()); + markupFuncs.add(createCommentMarkupFunc(commentGetter, CodeUnit.PLATE_COMMENT, "\n")); + } + + EOLComment eca = field.getAnnotation(EOLComment.class); + if (eca != null) { + Method commentGetter = + ReflectionHelper.getCommentMethod(clazz, eca.value(), field.getName()); + markupFuncs.add(createCommentMarkupFunc(commentGetter, CodeUnit.EOL_COMMENT, ";")); + } + } + + private FieldMarkupFunction createCommentMarkupFunc(Method commentGetter, int commentType, + String sep) { + return context -> { + T obj = context.getStructureInstance(); + Object val = ReflectionHelper.callGetter(commentGetter, obj); + if (val != null) { + if (val instanceof Collection c && c.isEmpty()) { + return; + } + context.appendComment(commentType, null, val.toString(), sep); + } + }; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void setReadFuncClass(Class readFuncClass) { + if (readFuncClass != FieldReadFunction.class) { + this.readerFunc = ReflectionHelper.createInstance(readFuncClass, this); + return; + } + + Class fieldType = field.getType(); + if (ReflectionHelper.isPrimitiveType(fieldType)) { + this.readerFunc = getReadPrimitiveValueFunc(fieldType); + return; + } + + if (ReflectionHelper.hasStructureMapping(fieldType)) { + this.readerFunc = this::readStructureMappedTypeFunc; + return; + } + + } + + private FieldReadFunction getReadPrimitiveValueFunc(Class destClass) { + + if (destClass == Long.class || destClass == Long.TYPE) { + return (context) -> context.fieldInfo().isUnsigned() + ? context.reader().readNextUnsignedValue(context.fieldInfo().getLength()) + : context.reader().readNextValue(context.fieldInfo().getLength()); + } + if (destClass == Integer.class || destClass == Integer.TYPE) { + return (context) -> context.fieldInfo().isUnsigned() + ? (int) context.reader().readNextUnsignedValue(context.fieldInfo().getLength()) + : (int) context.reader().readNextValue(context.fieldInfo().getLength()); + } + if (destClass == Short.class || destClass == Short.TYPE) { + return (context) -> context.fieldInfo().isUnsigned() + ? (short) context.reader() + .readNextUnsignedValue(context.fieldInfo().getLength()) + : (short) context.reader().readNextValue(context.fieldInfo().getLength()); + } + if (destClass == Byte.class || destClass == Byte.TYPE) { + return (context) -> context.fieldInfo().isUnsigned() + ? (byte) context.reader().readNextUnsignedValue(context.fieldInfo().getLength()) + : (byte) context.reader().readNextValue(context.fieldInfo().getLength()); + } + if (destClass == Character.class || destClass == Character.TYPE) { + return (context) -> context.fieldInfo().isUnsigned() + ? (char) context.reader().readNextUnsignedValue(context.fieldInfo().getLength()) + : (char) context.reader().readNextValue(context.fieldInfo().getLength()); + } + return null; + } + + private Object readStructureMappedTypeFunc(FieldContext context) throws IOException { + return context.structureContext() + .getProgramContext() + .readStructure(field.getType(), context.reader()); + } + +// @SuppressWarnings({ "unchecked", "rawtypes" }) +// private FieldMarkupFunction makeMarkupFunc( +// Class markupFuncClass, String getterName) { +// if (markupFuncClass != FieldMarkupFunction.class) { +// return ReflectionHelper.createInstance(markupFuncClass, this); +// } +// +// if (getterName != null && !getterName.isBlank()) { +// Method getter = ReflectionHelper.findGetter(field.getDeclaringClass(), getterName); +// if (getter == null) { +// throw new IllegalArgumentException( +// "Missing FieldMarkup getter %s for %s".formatted(getterName, field)); +// } +// getter.setAccessible(true); +// return (context) -> markupFieldWithGetter(getter, context); +// } +// +// Class fieldType = field.getType(); +// if (ReflectionHelper.hasStructureMapping(fieldType)) { +// return this::markupNestedStructure; +// } +// +// Method getter = ReflectionHelper.findGetter(field.getDeclaringClass(), field.getName()); +// if (getter != null) { +// getter.setAccessible(true); +// return (context) -> markupFieldWithGetter(getter, context); +// } +// +// throw new IllegalArgumentException("Invalid FieldMarkup: " + field); +// } + +// private void markupFieldWithGetter(Method getterMethod, FieldContext fieldContext) +// throws IOException { +// Object getterValue = +// ReflectionHelper.callGetter(getterMethod, fieldContext.getStructureInstance()); +// if (getterValue != null) { +// if (getterValue instanceof Collection c && c.isEmpty()) { +// return; +// } +// fieldContext.appendComment(CodeUnit.EOL_COMMENT, "", getterValue.toString(), ";"); +// } +// } + + private void markupNestedStructure(FieldContext fieldContext) throws IOException { + fieldContext.getProgramContext().markup(fieldContext.getValue(Object.class), true); + } + + private FieldMarkupFunction makeMarkupReferenceFunc(String getterName) { + getterName = getterName == null || getterName.isBlank() ? field.getName() : getterName; + + Method getter = ReflectionHelper.requireGetter(field.getDeclaringClass(), getterName); + getter.setAccessible(true); + return (context) -> addRefToFieldWithGetter(getter, context); + } + + private void addRefToFieldWithGetter(Method getterMethod, FieldContext fieldContext) + throws IOException { + Object getterValue = + ReflectionHelper.callGetter(getterMethod, fieldContext.getStructureInstance()); + if (getterValue != null) { + Address addr = getterValue instanceof Address getterAddr + ? getterAddr + : fieldContext.getProgramContext().getExistingStructureAddress(getterValue); + if (addr != null) { + fieldContext.addReference(addr); + } + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java new file mode 100644 index 0000000000..4f77968b54 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMarkupFunction.java @@ -0,0 +1,22 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +public interface FieldMarkupFunction { + void markupField(FieldContext fieldContext) throws IOException; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutput.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutput.java new file mode 100644 index 0000000000..6569231190 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutput.java @@ -0,0 +1,67 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import ghidra.program.model.data.DataType; + +/** + * Indicates that the tagged java field is to be included when constructing a variable length + * Ghidra structure data type. + *

    + * Using this annotation on a field indicates that the containing Ghidra structure has + * variable length fields, and the containing class must implement the + * {@link StructureReader} interface to allow deserialization of instances of the containing class. + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface FieldOutput { + Class fieldOutputFunc() default FieldOutputFunction.class; + + int ordinal() default -1; + + int offset() default -1; + + /** + * Specifies the name of a Ghidra {@link DataType} that will be used for this field when + * creating a Ghidra structure. + * + * @return + */ + String dataTypeName() default ""; + + /** + * Marks this field as variable length, which will cause the Ghidra structure containing + * this field to have a "_NN" name suffix that specifies the length of this instance. + * + * @return + */ + boolean isVariableLength() default false; + + /** + * Specifies a method that will return a Ghidra {@link DataType} that should be used for this + * field when creating a Ghidra structure. + * + * @return + */ + String getter() default ""; + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputFunction.java new file mode 100644 index 0000000000..fc98ab42ee --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputFunction.java @@ -0,0 +1,32 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; + +/** + * A function that helps construct a Ghidra {@link DataType} from annotated field information + * found in a Java class. + * + * @param + */ +public interface FieldOutputFunction { + void addFieldToStructure(StructureContext context, Structure structure, + FieldOutputInfo fieldOutputInfo) throws IOException; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputInfo.java new file mode 100644 index 0000000000..9ced662626 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldOutputInfo.java @@ -0,0 +1,215 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import ghidra.program.model.data.*; + +/** + * Immutable information needed to create fields in a Ghidra structure data type, using information + * from a java field. + * + * @param + */ +public class FieldOutputInfo { + private final FieldMappingInfo fmi; + private final String dataTypeName; + private final int fieldOffset; + private final boolean isVariableLength; + private final int ordinal; + private FieldOutputFunction outputFunc; + + public FieldOutputInfo(FieldMappingInfo fmi, String dataTypeName, boolean isVariableLength, + int ordinal, int fieldOffset) { + this.fmi = fmi; + this.dataTypeName = dataTypeName; + this.isVariableLength = isVariableLength; + this.ordinal = ordinal; + this.fieldOffset = fieldOffset; + } + + public Field getField() { + return fmi.getField(); + } + + public int getOrdinal() { + return ordinal; + } + + public boolean isVariableLength() { + return isVariableLength; + } + + public FieldOutputFunction getOutputFunc() { + return outputFunc; + } + + /** + * Returns the value of this java field. + * + * @param + * @param structInstance object containing the field + * @param expectedType expected class of the value + * @return value of the field + * @throws IOException + */ + public R getValue(T structInstance, Class expectedType) throws IOException { + try { + Object val = fmi.getField().get(structInstance); + if (expectedType.isInstance(val)) { + return expectedType.cast(val); + } + } + catch (IllegalArgumentException | IllegalAccessException e) { + throw new IOException(e); + } + return null; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void setOutputFuncClass(Class funcClass, + String getterName) { + + if (funcClass != FieldOutputFunction.class) { + this.outputFunc = ReflectionHelper.createInstance(funcClass, this); + return; + } + + if (getterName != null && !getterName.isBlank()) { + Method getter = + ReflectionHelper.requireGetter(fmi.getField().getDeclaringClass(), getterName); + getter.setAccessible(true); + this.outputFunc = + (context, structure, unused) -> outputFuncWithGetter(getter, context, structure); + return; + } + + if (dataTypeName != null && !dataTypeName.isBlank()) { + this.outputFunc = this::dataTypeNameOutputFunc; + return; + } + + Class fieldType = fmi.getField().getType(); + if (ReflectionHelper.isPrimitiveType(fieldType)) { + this.outputFunc = this::primitiveOutputFunc; + return; + } + if (fieldType.isArray() && ReflectionHelper.isPrimitiveType(fieldType.getComponentType())) { + this.outputFunc = this::arrayOutputFunc; + return; + } + + if (ReflectionHelper.hasStructureMapping(fieldType)) { + this.outputFunc = this::nestedStructureOutputFunc; + return; + } + + throw new IllegalArgumentException("Invalid FieldOutput " + fmi.getField()); + } + + private void preAddField(Structure structure) throws IOException { + if (fieldOffset >= 0) { + int currentOffset = getStructLength(structure); + if (currentOffset > fieldOffset) { + throw new IOException("Invalid field offset %d, structure is already %d" + .formatted(fieldOffset, currentOffset)); + } + if (currentOffset < fieldOffset) { + structure.add(Undefined.getUndefinedDataType(fieldOffset - currentOffset), -1); + } + } + } + + private void outputFuncWithGetter(Method getter, StructureContext context, + Structure structure) throws IOException { + Object getterResult = + ReflectionHelper.callGetter(getter, context.getStructureInstance(), Object.class); + if (getterResult == null) { + //do nothing + } + else if (getterResult instanceof DataType dt) { + preAddField(structure); + structure.add(dt, fmi.getFieldName(), null); + } + else if (getterResult instanceof DataTypeInstance dti) { + preAddField(structure); + structure.add(dti.getDataType(), dti.getLength(), fmi.getFieldName(), null); + } + else { + throw new IOException("Bad result type for FieldOutput.getter: %s" + .formatted(getterResult.getClass().getSimpleName())); + } + } + + private void dataTypeNameOutputFunc(StructureContext context, Structure structure, + FieldOutputInfo foi) throws IOException { + DataType dt = context.getProgramContext().getType(dataTypeName, DataType.class); + if (dt == null) { + throw new IOException( + "Missing data type %s for field %s".formatted(dataTypeName, fmi.getFieldName())); + } + preAddField(structure); + if (dt instanceof Dynamic) { + throw new IOException("Invalid dynamic sized data type %s for field %s" + .formatted(dt.getName(), fmi.getFieldName())); + } + structure.add(dt, fmi.getFieldName(), null); + } + + private void primitiveOutputFunc(StructureContext context, Structure structure, + FieldOutputInfo foi) throws IOException { + DataType dt = ReflectionHelper.getPrimitiveOutputDataType(fmi.getField().getType(), + fmi.getLength(), fmi.getSignedness(), context.getProgramContext()); + preAddField(structure); + structure.add(dt, fmi.getFieldName(), null); + } + + private void arrayOutputFunc(StructureContext context, Structure structure, + FieldOutputInfo foi) throws IOException { + // only outputs array of primitive value + Object fieldValue = foi.getValue(context.getStructureInstance(), Object.class); + DataType dt = ReflectionHelper.getArrayOutputDataType(fieldValue, fmi.getField().getType(), + fmi.getLength(), fmi.getSignedness(), context.getProgramContext()); + preAddField(structure); + structure.add(dt, fmi.getFieldName(), null); + } + + private void nestedStructureOutputFunc(StructureContext context, Structure structure, + FieldOutputInfo foi) throws IOException { + Object nestedStruct = foi.getValue(context.getStructureInstance(), Object.class); + if (nestedStruct == null) { + return; + } + + StructureContext nestedStructContext = + context.getProgramContext().getExistingStructureContext(nestedStruct); + if (nestedStructContext == null) { + throw new IOException( + "Missing StructureContext for " + nestedStruct.getClass().getSimpleName()); + } + DataType nestedStructDT = nestedStructContext.getStructureDataType(); + preAddField(structure); + structure.add(nestedStructDT, fmi.getFieldName(), null); + } + + private static int getStructLength(Structure struct) { + return struct.isZeroLength() ? 0 : struct.getLength(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldReadFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldReadFunction.java new file mode 100644 index 0000000000..eb7d1992bc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldReadFunction.java @@ -0,0 +1,24 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +@FunctionalInterface +public interface FieldReadFunction { + Object get(FieldContext context) throws IOException; + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Markup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Markup.java new file mode 100644 index 0000000000..45da131542 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Markup.java @@ -0,0 +1,36 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the objects in the return value of the tagged method should be recursively + * marked up, or if a tagged field, the object in the field should be recursively marked up. + *

    + * The return value can be an object with {@link StructureMapping} info, or a + * collection / iterator of those objects. + */ +@Retention(RUNTIME) +@Target({ FIELD, METHOD }) +public @interface Markup { + // nothing +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupReference.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupReference.java new file mode 100644 index 0000000000..5f92547b08 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/MarkupReference.java @@ -0,0 +1,42 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import ghidra.program.model.address.Address; + +/** + * Indicates that the target of the tagged field should be decorated in Ghidra as + * receiving a data reference from the location of the field. + *

    + * The tagged field must have a 'getter' method that returns something that can be converted + * to an {@link Address}. This can either be an actual Address return value, or a return value + * that is an instance of an object that can be mapped to an address in the program. + *

    + * The name of the 'getter' method can be overridden by providing a string that directly specifies + * the getter method name, or its sans-"get" name (eg. for getter method getXyz(), "getXyz", or + * "Xyz" are equally valid) + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface MarkupReference { + String value() default ""; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/PlateComment.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/PlateComment.java new file mode 100644 index 0000000000..7c0fc95063 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/PlateComment.java @@ -0,0 +1,40 @@ +/* ### + * 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.golang.structmapping; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the tagged class or field should have an plate comment placed + * before each instance of the object or field, using the return value of the field's + * getter method, or if a class, the object's "toString()" method. + */ +@Retention(RUNTIME) +@Target({ FIELD, TYPE }) +public @interface PlateComment { + /** + * Name of a "getter" method that's return value will be converted to a string and used + * as the comment + * + * @return + */ + String value() default ""; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ProgramContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ProgramContext.java new file mode 100644 index 0000000000..82d14a5516 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ProgramContext.java @@ -0,0 +1,406 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.*; + +import generic.jar.ResourceFile; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.MemoryByteProvider; +import ghidra.app.util.bin.format.dwarf4.next.DWARFDataTypeConflictHandler; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities.ClearDataMode; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.DataConverter; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +/** + * Information about {@link StructureMapping} classes and their metadata, as well as + * accumulated information about structure instances that have been deserialized. + *

    + * To use the full might and majesty of StructureMapping(tm), a ProgramContext must be created. It + * must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find} + * ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data + * types being used, and it must {@link #registerStructure(Class) know} about all classes that are + * going to participate during deserialization and markup. + *

    + * Structure mapped classes can receive a reference to the specific ProgramContext type that + * created them by declaring a {@code ProgramContext} field, and tagging it with + * the @{@link ContextField} annotation: + * + *

    + * class MyProgramContext extends ProgramContext {
    + *  public MyProgramContext() {
    + *    ...
    + *   registerStructure(MyDataType.class);
    + *  }
    + *  public void foo() { ... }
    + * }
    + * 
    + * @StructureMapping(structureName = "mydatatype")
    + * class MyDataType {
    + * 
    + *  @ContextField
    + *  private MyProgramContext myProgramContext;
    + *  
    + *  @ContextField
    + *  private StructureContext<MyDataType> context;
    + * 
    + *  @FieldMapping
    + *  private long someField;
    + * 
    + * void bar() {
    + *  context.getProgramContext().getProgram(); // can only access methods defined on base ProgramContext type
    + *  myProgramContext.foo(); // same context as previous line, but typed correctly
    + * ...
    + * 
    + * + */ +public class ProgramContext implements AutoCloseable { + protected Program program; + protected DataTypeManager programDTM; + protected DataTypeManager archiveDTM; + protected List programSearchCPs = new ArrayList<>(); + protected List archiveSearchCPs = new ArrayList<>(); + protected Map, StructureMappingInfo> mappingInfo = new HashMap<>(); + protected Set
    markedupStructs = new HashSet<>(); + protected TaskMonitor markupTaskMonitor = TaskMonitor.DUMMY; + + /** + * + * @param program + * @param archiveGDT + * @throws IOException + */ + protected ProgramContext(Program program, ResourceFile archiveGDT) throws IOException { + this.program = program; + this.programDTM = program.getDataTypeManager(); + this.archiveDTM = archiveGDT != null + ? FileDataTypeManager.openFileArchive(archiveGDT, false) + : null; + } + + @Override + public void close() { + if (archiveDTM != null) { + archiveDTM.close(); + archiveDTM = null; + } + } + + public CategoryPath getDefaultVariableLengthStructCategoryPath() { + return CategoryPath.ROOT; + } + + public Program getProgram() { + return program; + } + + protected BinaryReader createProgramReader() { + MemoryByteProvider bp = + new MemoryByteProvider(program.getMemory(), program.getImageBase().getAddressSpace()); + return new BinaryReader(bp, !program.getMemory().isBigEndian()); + } + + public DataConverter getDataConverter() { + return DataConverter.getInstance(program.getMemory().isBigEndian()); + } + + public ProgramContext addProgramSearchCategoryPath(CategoryPath... paths) { + programSearchCPs.addAll(Arrays.asList(paths)); + return this; + } + + public ProgramContext addArchiveSearchCategoryPath(CategoryPath... paths) { + archiveSearchCPs.addAll(Arrays.asList(paths)); + return this; + } + + /** + * Registers a class that has {@link StructureMapping structure mapping} information. + * + * @param + * @param clazz + * @throws IOException if the class's Ghidra structure data type could not be found + */ + public void registerStructure(Class clazz) throws IOException { + Structure structDT = null; + String structName = StructureMappingInfo.getStructureDataTypeNameForClass(clazz); + if (structName != null && !structName.isBlank()) { + structDT = getType(structName, Structure.class); + } + if (!StructureReader.class.isAssignableFrom(clazz) && structDT == null) { + if (structName == null || structName.isBlank()) { + structName = ""; + } + throw new IOException( + "Missing struct definition %s - %s".formatted(clazz.getSimpleName(), + structName)); + } + + StructureMappingInfo structMappingInfo = StructureMappingInfo.fromClass(clazz, structDT); + mappingInfo.put(clazz, structMappingInfo); + } + + public void registerStructures(List> classes) throws IOException { + for (Class clazz : classes) { + registerStructure(clazz); + } + } + + @SuppressWarnings("unchecked") + public StructureMappingInfo getStructureMappingInfo(Class clazz) { + StructureMappingInfo smi = mappingInfo.get(clazz); + return (StructureMappingInfo) smi; + } + + @SuppressWarnings("unchecked") + public StructureMappingInfo getStructureMappingInfo(T structureInstance) { + return structureInstance != null + ? (StructureMappingInfo) mappingInfo.get(structureInstance.getClass()) + : null; + } + + /** + * Returns a Ghidra structure data type representing the specified class. + * + * @param clazz a structure mapped class + * @return {@link Structure} data type, or null if the class was a struct with variable length + * fields + */ + public Structure getStructureDataType(Class clazz) { + StructureMappingInfo mi = mappingInfo.get(clazz); + return mi != null ? mi.getStructureDataType() : null; + } + + /** + * Returns the name of the Ghidra structure that has been registered for the specified + * structure mapped class. + * + * @param clazz + * @return + */ + public String getStructureDataTypeName(Class clazz) { + StructureMappingInfo mi = mappingInfo.get(clazz); + return mi != null ? mi.getStructureName() : null; + } + + protected DataType findType(String name, List searchList, DataTypeManager dtm) { + for (CategoryPath searchCP : searchList) { + DataType dataType = dtm.getDataType(searchCP, name); + if (dataType != null) { + return dataType; + } + } + return null; + } + + /** + * Returns a named {@link DataType}, searching the registered + * {@link #addProgramSearchCategoryPath(CategoryPath...) program} + * and {@link #addArchiveSearchCategoryPath(CategoryPath...) archive} category paths. + * + * @param + * @param name + * @param clazz + * @return + */ + public T getType(String name, Class clazz) { + DataType dataType = findType(name, programSearchCPs, programDTM); + if (dataType == null && archiveDTM != null) { + dataType = findType(name, archiveSearchCPs, archiveDTM); + if (dataType != null) { + dataType = programDTM.resolve(dataType, DWARFDataTypeConflictHandler.INSTANCE); + } + } + if (dataType == null) { + dataType = + BuiltInDataTypeManager.getDataTypeManager().getDataType(CategoryPath.ROOT, name); + } + return clazz.isInstance(dataType) ? clazz.cast(dataType) : null; + } + + public T getTypeOrDefault(String name, Class clazz, T defaultValue) { + T result = getType(name, clazz); + return result != null ? result : defaultValue; + } + + public DataTypeManager getDTM() { + return programDTM; + } + + private StructureContext getStructureContext(Class structureClass, + BinaryReader reader) { + StructureMappingInfo smi = getStructureMappingInfo(structureClass); + if (smi == null) { + throw new IllegalArgumentException( + "Unknown structure mapped class: " + structureClass.getSimpleName()); + } + return new StructureContext<>(this, smi, reader); + } + + public StructureContext getExistingStructureContext(T structureInstance) + throws IOException { + StructureMappingInfo smi = structureInstance != null + ? getStructureMappingInfo(structureInstance) + : null; + return smi != null ? smi.recoverStructureContext(structureInstance) : null; + } + + /** + * Attempts to convert an instance of an object (that represents a chunk of memory in + * the program) into its Address. + * + * @param type of the object + * @param structureInstance instance of an object that represents something in the program's + * memory + * @return {@link Address} of the object, or null if not found or not a supported object + * @throws IOException + */ + public Address getExistingStructureAddress(T structureInstance) throws IOException { + StructureMappingInfo smi = structureInstance != null + ? getStructureMappingInfo(structureInstance) + : null; + StructureContext structureContext = smi != null + ? smi.recoverStructureContext(structureInstance) + : null; + return structureContext != null + ? structureContext.getStructureAddress() + : null; + } + + public void setMarkupTaskMonitor(TaskMonitor monitor) { + this.markupTaskMonitor = Objects.requireNonNullElse(monitor, TaskMonitor.DUMMY); + } + + public void markup(T obj, boolean nested) throws IOException { + if (markupTaskMonitor.isCancelled()) { + throw new IOException("Markup canceled"); + } + if (obj == null) { + return; + } + if (obj instanceof Collection list) { + for (Object listElement : list) { + markup(listElement, nested); + } + } + else if (obj.getClass().isArray()) { + int len = Array.getLength(obj); + for (int i = 0; i < len; i++) { + markup(Array.get(obj, i), nested); + } + } + else if (obj instanceof Iterator it) { + while (it.hasNext()) { + Object itElement = it.next(); + markup(itElement, nested); + } + } + else { + StructureContext structureContext = getExistingStructureContext(obj); + if (structureContext == null) { + throw new IllegalArgumentException(); + } + markupTaskMonitor.incrementProgress(1); + structureContext.markupStructure(nested); + } + } + + public T readStructure(Class structureClass, BinaryReader structReader) + throws IOException { + StructureContext structureContext = getStructureContext(structureClass, structReader); + + T result = structureContext.readNewInstance(); + return result; + } + + public T readStructure(Class structureClass, long position) throws IOException { + return readStructure(structureClass, getReader(position)); + } + + public T readStructure(Class structureClass, Address address) throws IOException { + return readStructure(structureClass, getReader(address.getOffset())); + } + + public BinaryReader getReader(long position) { + BinaryReader reader = createProgramReader(); + reader.setPointerIndex(position); + return reader; + } + + + public Address getDataAddress(long offset) { + return program.getImageBase().getNewAddress(offset); + } + + public Address getCodeAddress(long offset) { + return program.getImageBase().getNewAddress(offset); + } + + public void labelAddress(Address addr, String symbolName) throws IOException { + try { + SymbolTable symbolTable = getProgram().getSymbolTable(); + Symbol[] symbols = symbolTable.getSymbols(addr); + if (symbols.length == 0 || symbols[0].isDynamic()) { + symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true); + symbolTable.createLabel(addr, symbolName, SourceType.IMPORTED); + } + } + catch (InvalidInputException e) { + throw new IOException(e); + } + } + + public void labelStructure(T obj, String symbolName) throws IOException { + Address addr = getExistingStructureAddress(obj); + labelAddress(addr, symbolName); + } + + public void markupAddress(Address addr, DataType dt) throws IOException { + markupAddress(addr, dt, -1); + } + + public void markupAddress(Address addr, DataType dt, int length) throws IOException { + try { + DataUtilities.createData(program, addr, dt, length, false, + ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA); + } + catch (CodeUnitInsertionException e) { + throw new IOException(e); + } + + } + + public void markupAddressIfUndefined(Address addr, DataType dt) throws IOException { + Data data = DataUtilities.getDataAtAddress(program, addr); + if (data == null || Undefined.isUndefined(data.getBaseDataType())) { + markupAddress(addr, dt); + } + } + + @Override + public String toString() { + return "ProgramContext { program: %s}".formatted(program.getName()); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ReflectionHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ReflectionHelper.java new file mode 100644 index 0000000000..68b938232e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/ReflectionHelper.java @@ -0,0 +1,352 @@ +/* ### + * 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.golang.structmapping; + +import java.util.*; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.lang.reflect.Array; + +import ghidra.program.model.data.*; + +public class ReflectionHelper { + private static final Set> NUM_CLASSES = + Set.of(Long.class, Long.TYPE, Integer.class, Integer.TYPE, Short.class, Short.TYPE, + Byte.class, Byte.TYPE, Character.class, Character.TYPE); + private static final Map, Integer> SIZEOF_NUM_CLASSES = + Map.ofEntries( + Map.entry(Long.class, Long.BYTES), + Map.entry(Long.TYPE, Long.BYTES), + Map.entry(Integer.class, Integer.BYTES), + Map.entry(Integer.TYPE, Integer.BYTES), + Map.entry(Short.class, Short.BYTES), + Map.entry(Short.TYPE, Short.BYTES), + Map.entry(Byte.class, Byte.BYTES), + Map.entry(Byte.TYPE, Byte.BYTES), + Map.entry(Character.class, Character.BYTES), + Map.entry(Character.TYPE, Character.BYTES)); + private static final Map, String> DEFAULT_DATATYPE_NAME = + Map.ofEntries( + Map.entry(Long.class, "long"), + Map.entry(Long.TYPE, "long"), + Map.entry(Integer.class, "int"), + Map.entry(Integer.TYPE, "int"), + Map.entry(Short.class, "word"), + Map.entry(Short.TYPE, "word"), + Map.entry(Byte.class, "byte"), + Map.entry(Byte.TYPE, "byte"), + Map.entry(Character.class, "wchar"), + Map.entry(Character.TYPE, "wchar")); + + public static boolean isPrimitiveType(Class clazz) { + return NUM_CLASSES.contains(clazz); + } + + /** + * Write a value to a field in a java class. + * + * @param field reflection {@link Field} + * @param obj java instance that contains the field + * @param value value to write + * @throws IOException + */ + public static void assignField(Field field, Object obj, Object value) throws IOException { + Class fieldType = field.getType(); + + try { + if (fieldType.isPrimitive() && NUM_CLASSES.contains(value.getClass())) { + field.set(obj, value); + } + else { + if (!fieldType.isInstance(value)) { + throw new IOException("Bad conversion from %s to %s.%s:%s".formatted( + value.getClass().getSimpleName(), + obj.getClass().getSimpleName(), + field.getName(), + fieldType.getSimpleName())); + } + field.set(obj, value); + } + } + catch (IllegalArgumentException | IllegalAccessException e) { + throw new IOException(e); + } + } + + /** + * Return Ghidra data type representing an array of primitive values. + * + * @param array_value java array object + * @param fieldType + * @param length + * @param signedness + * @param programContext + * @return + */ + public static DataType getArrayOutputDataType(Object array_value, Class fieldType, int length, + Signedness signedness, ProgramContext programContext) { + int arrayLen = array_value != null ? Array.getLength(array_value) : 0; + Class elementType = fieldType.getComponentType(); + DataType elementDT = + getPrimitiveOutputDataType(elementType, length, signedness, programContext); + + return new ArrayDataType(elementDT, arrayLen, -1, programContext.getDTM()); + } + + public static DataType getPrimitiveOutputDataType(Class fieldType, int length, + Signedness signedness, ProgramContext programContext) { + + boolean isChar = (fieldType == Character.class || fieldType == Character.TYPE); + DataTypeManager dtm = programContext.getDTM(); + + if (length == -1) { + length = getPrimitiveSizeof(fieldType); + } + if (signedness == Signedness.Unspecified) { + signedness = isChar ? Signedness.Unsigned : Signedness.Signed; + } + + String defaultDtName = DEFAULT_DATATYPE_NAME.get(fieldType); + if (isChar && length == 1) { + defaultDtName = "char"; + } + DataType dt = programContext.getType(defaultDtName, DataType.class); + if (dt == null && isChar) { + dt = switch (length) { + case 1 -> CharDataType.dataType; + case 2 -> WideChar16DataType.dataType; + default -> null; + }; + } + + if (dt == null || !matches(dt, length, signedness)) { + dt = signedness == Signedness.Signed + ? AbstractIntegerDataType.getSignedDataType(length, dtm) + : AbstractIntegerDataType.getSignedDataType(length, dtm); + } + return dt; + } + + private static boolean matches(DataType dt, int length, Signedness signedness) { + return (length == -1 || length == dt.getLength()) && + (signedness == Signedness.Unspecified || + (dt instanceof AbstractIntegerDataType intDT) && + (signedness == Signedness.Signed) == intDT.isSigned()); + } + + public static int getPrimitiveSizeof(Class fieldType) { + return SIZEOF_NUM_CLASSES.getOrDefault(fieldType, 1); + } + + public static boolean hasStructureMapping(Class clazz) { + return clazz.getAnnotation(StructureMapping.class) != null; + } + + public static Signedness getDataTypeSignedness(DataType dt) { + if (dt instanceof TypeDef typedefDT) { + dt = typedefDT.getBaseDataType(); + } + if (dt instanceof AbstractIntegerDataType intDT) { + return intDT.isSigned() ? Signedness.Signed : Signedness.Unsigned; + } + return Signedness.Signed; // default + } + + public static Method getCommentMethod(Class clazz, String commentGetterName, + String defaultGetterName) { + if (commentGetterName.isBlank()) { + commentGetterName = defaultGetterName; + } + Method commentGetter = ReflectionHelper.requireGetter(clazz, commentGetterName); + if (commentGetter == null) { + throw new IllegalArgumentException( + "Missing getter %s for %s".formatted(commentGetterName, clazz)); + } + return commentGetter; + } + + public static Method requireGetter(Class clazz, String getterName) { + Method method = findGetter(clazz, getterName); + if (method == null) { + throw new IllegalArgumentException( + "Missing getter %s for %s".formatted(getterName, clazz)); + } + return method; + } + + public static Method findGetter(Class structClass, String getterName) { + Method getter = getMethod(structClass, getterName); + if (getter == null) { + String getGetterName = "get%s%s".formatted(getterName.substring(0, 1).toUpperCase(), + getterName.substring(1)); + getter = getMethod(structClass, getGetterName); + } + return getter; + } + + public static Constructor getCtor(Class clazz, Class... paramTypes) { + try { + return clazz.getDeclaredConstructor(paramTypes); + } + catch (NoSuchMethodException | SecurityException e) { + // fail + } + return null; + } + + static Method getMethod(Class clazz, String methodName, Class... paramTypes) { + try { + // try both public and private methods in the class + return clazz.getDeclaredMethod(methodName, paramTypes); + } + catch (NoSuchMethodException | SecurityException e) { + // fail, next try inherited public methods + } + try { + return clazz.getMethod(methodName, paramTypes); + } + catch (NoSuchMethodException | SecurityException e) { + // fail + } + return null; + } + + public static void invokeMethods(List methods, Object obj, Object... params) + throws IOException { + for (Method method : methods) { + try { + method.invoke(obj, params); + } + catch (IllegalAccessException | InvocationTargetException e) { + throw new IOException(e); + } + } + + } + + public static T createInstance(Class targetClass, CTX optionalContext) + throws IllegalArgumentException { + + try { + if (optionalContext != null) { + Constructor ctor = getCtor(targetClass, optionalContext.getClass()); + if (ctor != null) { + ctor.setAccessible(true); + return ctor.newInstance(optionalContext); + } + } + Constructor ctor = getCtor(targetClass); + if (ctor != null) { + ctor.setAccessible(true); + return ctor.newInstance(); + } + throw new IllegalArgumentException("Missing ctor for " + targetClass.getSimpleName()); + } + catch (SecurityException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalArgumentException(e); + } + } + + public static T callCtor(Constructor ctor, Object... params) + throws IllegalArgumentException { + try { + return ctor.newInstance(params); + } + catch (SecurityException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalArgumentException(e); + } + } + + public static Object callGetter(Method getterMethod, T obj) + throws IOException { + return callGetter(getterMethod, obj, Object.class); + } + + public static R callGetter(Method getterMethod, T obj, Class expectedType) + throws IOException { + try { + Object getterValue = getterMethod.invoke(obj); + if (getterValue == null || expectedType.isInstance(getterValue)) { + return expectedType.cast(getterValue); + } + return null; + } + catch (IllegalAccessException | InvocationTargetException e) { + throw new IOException(e); + } + } + + public static void getMarkedMethods(Class targetClass, + Class annotationClass, List methods, + boolean includeParentClasses, Class... paramClasses) { + if (includeParentClasses && targetClass.getSuperclass() != null) { + getMarkedMethods(targetClass.getSuperclass(), annotationClass, methods, + includeParentClasses, paramClasses); + } + methodloop: for (Method method : targetClass.getDeclaredMethods()) { + if (method.getParameterCount() == paramClasses.length && + method.getAnnotation(annotationClass) != null) { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != paramClasses.length) { + continue; + } + for (int i = 0; i < parameterTypes.length; i++) { + Class methodParamClass = parameterTypes[i]; + if (!methodParamClass.isAssignableFrom(paramClasses[i])) { + continue methodloop; + } + } + method.setAccessible(true); + methods.add(method); + } + } + } + + public static List getAnnotations(Class targetClass, + Class annotationClass, List result) { + if (result == null) { + result = new ArrayList<>(); + } + T annotation = targetClass.getAnnotation(annotationClass); + if (annotation != null) { + result.add(annotation); + } + if (targetClass.getSuperclass() != null) { + getAnnotations(targetClass.getSuperclass(), annotationClass, result); + } + return result; + } + + public static R getFieldValue(Object obj, Field field, Class expectedType) + throws IOException { + try { + Object val = field.get(obj); + if (val != null && !expectedType.isInstance(val)) { + throw new IOException( + "Unexpected field value type: " + val.getClass() + " in " + field); + } + return expectedType.cast(val); + } + catch (IllegalArgumentException | IllegalAccessException e) { + throw new IOException(e); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Signedness.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Signedness.java new file mode 100644 index 0000000000..e6d9dc15bc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/Signedness.java @@ -0,0 +1,20 @@ +/* ### + * 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.golang.structmapping; + +public enum Signedness { + Unspecified, Signed, Unsigned +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java new file mode 100644 index 0000000000..b60add1760 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureContext.java @@ -0,0 +1,286 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; + +/** + * Information about an instance of a structure that has been read from the memory of a + * Ghidra program. + *

    + * All {@link StructureMapping} tagged classes must have a {@link ContextField} tagged + * StructureContext field for that class to be able to access meta-data about its self, and + * for other classes to reference it when performing markup: + *

    + * @StructureMapping(structureName = "mydatatype")
    + * class MyDataType {
    + * 	@ContextField
    + * 	private StructureContext<MyDataType> context;
    + * 
    + * 	@FieldMapping
    + * 	private long someField;
    + *  ...
    + * 
    + * + * @param a java class that has been tagged with a {@link StructureMapping} annotation. + */ +public class StructureContext { + protected final ProgramContext programContext; + protected final StructureMappingInfo mappingInfo; + protected final BinaryReader reader; + protected final long structureStart; + protected T structureInstance; + protected Structure structureDataType; + + public StructureContext(ProgramContext programContext, StructureMappingInfo mappingInfo, + BinaryReader reader) { + this.programContext = programContext; + this.mappingInfo = mappingInfo; + this.reader = reader; + this.structureStart = reader.getPointerIndex(); + this.structureDataType = mappingInfo.getStructureDataType(); + } + + public T readNewInstance() throws IOException { + structureInstance = mappingInfo.getInstanceCreator().get(this); + + mappingInfo.assignContextFieldValues(this); + + mappingInfo.readStructure(this); + + ReflectionHelper.invokeMethods(mappingInfo.getAfterMethods(), structureInstance); + + // TODO: capture actual structure length by checking BinaryReader position? + + return structureInstance; + } + + /** + * Returns the {@link StructureMappingInfo} for this structure's class. + * + * @return + */ + public StructureMappingInfo getMappingInfo() { + return mappingInfo; + } + + /** + * Returns a reference to the root {@link ProgramContext}, as a plain ProgramContext type. If + * a more specific ProgramContext type is needed, either type-cast this value, or use + * a {@link ContextField} tag on a field in your class that specifies the correct + * ProgramContext type. + * + * @return + */ + public ProgramContext getProgramContext() { + return programContext; + } + + public Program getProgram() { + return programContext.program; + } + + /** + * Returns the address in the program of this structure instance. + * + * @return {@link Address} + */ + public Address getStructureAddress() { + return programContext.getDataAddress(structureStart); + } + + /** + * Returns the address of an offset from the start of this structure instance. + * + * @param fieldOffset + * @return + */ + public Address getFieldAddress(long fieldOffset) { + return getStructureAddress().add(fieldOffset); + } + + /** + * Returns the stream location of an offset from the start of this structure instance. + * + * @param fieldOffset + * @return + */ + public long getFieldLocation(long fieldOffset) { + return structureStart + fieldOffset; + } + + /** + * Returns the stream location of this structure instance. + * + * @return + */ + public long getStructureStart() { + return structureStart; + } + + /** + * Returns the stream location of the end of this structure instance. + * + * @return + */ + public long getStructureEnd() { + return structureStart + getStructureLength(); + } + + /** + * Returns the length of this structure instance. + * + * @return + */ + public int getStructureLength() { + return structureDataType != null + ? structureDataType.getLength() + : 0; + } + + public T getStructureInstance() { + return structureInstance; + } + + public BinaryReader getReader() { + return reader; + } + + /** + * Returns an independent {@link BinaryReader} that is positioned at the start of the + * specified field. + * + * @param fieldOffset + * @return + */ + public BinaryReader getFieldReader(long fieldOffset) { + return reader.clone(structureStart + fieldOffset); + } + + + public FieldContext createFieldContext(FieldMappingInfo fmi, boolean includeReader) { + DataTypeComponent dtc = fmi.getDtc(structureDataType); + BinaryReader fieldReader = includeReader ? getFieldReader(dtc.getOffset()) : null; + FieldContext readContext = new FieldContext<>(this, fmi, dtc, fieldReader); + return readContext; + } + + /** + * Places a comment at the start of this structure, appending to any previous values + * already there. + * + * @param commentType + * @param prefix + * @param comment + * @param sep + * @throws IOException + */ + public void appendComment(int commentType, String prefix, String comment, String sep) + throws IOException { + DWARFUtil.appendComment(programContext.getProgram(), getStructureAddress(), commentType, + prefix, comment, sep); + } + + public boolean isAlreadyMarkedup() { + Address addr = getStructureAddress(); + Data data = getProgram().getListing().getDataContaining(addr); + if (data != null && data.getBaseDataType() instanceof Structure) { + return true; + } + return false; + } + + /** + * @param nested if true, it is assumed that the Ghidra data types have already been + * placed and only markup needs to be performed. + * + * @throws IOException + */ + public void markupStructure(boolean nested) throws IOException { + Address addr = getStructureAddress(); + if (!nested && !programContext.markedupStructs.add(addr)) { + return; + } + + if (!nested) { + try { + Structure structDT = getStructureDataType(); + programContext.markupAddress(addr, structDT); + } + catch (IOException e) { + throw new IOException("Markup failed for structure %s at %s" + .formatted(mappingInfo.getDescription(), getStructureAddress()), + e); + } + + if (structureInstance instanceof StructureMarkup sm) { + String structureLabel = sm.getStructureLabel(); + if (structureLabel != null) { + programContext.labelAddress(addr, structureLabel); + } + } + } + + markupFields(); + + if (structureInstance instanceof StructureMarkup sm) { + sm.additionalMarkup(); + } + + } + + public void markupFields() throws IOException { + for (FieldMappingInfo fmi : mappingInfo.getFields()) { + for (FieldMarkupFunction func : fmi.getMarkupFuncs()) { + func.markupField(createFieldContext(fmi, false)); + } + } + if (structureInstance instanceof StructureMarkup sm) { + for (Object externalInstance : sm.getExternalInstancesToMarkup()) { + programContext.markup(externalInstance, false); + } + } + + for (StructureMarkupFunction markupFunc : mappingInfo.getMarkupFuncs()) { + markupFunc.markupStructure(this); + } + + } + + public Structure getStructureDataType() throws IOException { + if (structureDataType == null) { + // if this is a variable length struct, a new custom struct datatype needs to be created + structureDataType = mappingInfo.createStructureDataType(this); + } + return structureDataType; + } + + @Override + public String toString() { + return "StructureContext<%s> { offset: %s}".formatted( + mappingInfo.getTargetClass().getSimpleName(), + Long.toUnsignedString(structureStart, 16)); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMapping.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMapping.java new file mode 100644 index 0000000000..62a3e9eeb0 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMapping.java @@ -0,0 +1,70 @@ +/* ### + * 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.golang.structmapping; + +import java.lang.annotation.*; + +/** + * Indicates that the tagged class corresponds to a Ghidra structure. + *

    + * For fixed/static length structures, an existing Ghidra structure data type will be found and + * then bound to the tagged class, and it will control how instances of the tagged class + * are deserialized. Only fields that are interesting / relevant need to be tagged with + * a {@link FieldMapping} annotation, which causes them to be pulled into the java structure. + *

    + * For {@link FieldOutput#isVariableLength() variable} length structures, a unique Ghidra + * structure data type will be created for each combination of field lengths, and the tagged + * class must deserialize itself by implementing the {@link StructureReader} interface. (each + * field that needs to be mapped into the Ghidra structure must be tagged with a {@link FieldOutput} + * annotation) + *

    + * In either case, various annotations on fields and methods will control how this structure + * will be marked up in the Ghidra program. + *

    + * The tagged class must be {@link ProgramContext#registerStructure(Class) registered} with + * the program context to enable the suite of structure mapped classes to work together when + * applied to a Ghidra binary. + *

    + * For variable length structure classes, when the struct mapping system creates a custom-fitted + * structure to markup a specific location with its specific data, the new struct data type's name + * will be patterned as "structurename_NN_MM_...", where NN and MM and etc are the lengths of the + * variable length fields found in the structure. + *

    + * Structure mapped classes must have a {@link StructureContext} member variable that is tagged + * with the {@link ContextField} annotation, and probably should have a {@link ProgramContext} + * member variable (that corresponds to a more specific type of ProgramContext) that is also + * tagged with the ContextField annotation. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface StructureMapping { + /** + * Specifies the name of a Ghidra structure that the tagged class represents. For fixed + * length structures, the {@link ProgramContext} will search for this Ghidra data type + * in it's configured + * {@link ProgramContext#addArchiveSearchCategoryPath(ghidra.program.model.data.CategoryPath...) archive} + * and + * {@link ProgramContext#addProgramSearchCategoryPath(ghidra.program.model.data.CategoryPath...) program} + * search paths. + * + * @return + */ + String structureName(); + + @SuppressWarnings("rawtypes") + Class markupFunc() default StructureMarkupFunction.class; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java new file mode 100644 index 0000000000..af65f1918d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java @@ -0,0 +1,346 @@ +/* ### + * 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.golang.structmapping; + +import java.util.*; + +import java.io.IOException; +import java.lang.reflect.*; + +import ghidra.program.model.data.*; +import ghidra.program.model.listing.CodeUnit; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * Contains immutable information about a structure mapped class needed to deserialize + * a new object from the data found in a Ghidra program. + * + * @param the class that is being mapped into a structure + */ +public class StructureMappingInfo { + + public static String getStructureDataTypeNameForClass(Class targetClass) { + StructureMapping sma = targetClass.getAnnotation(StructureMapping.class); + return sma != null ? sma.structureName() : null; + } + + public static StructureMappingInfo fromClass(Class targetClass, + Structure structDataType) { + StructureMapping sma = targetClass.getAnnotation(StructureMapping.class); + if (sma == null) { + throw new IllegalArgumentException( + "Missing @StructureMapping annotation on " + targetClass.getSimpleName()); + } + + return new StructureMappingInfo<>(targetClass, structDataType, sma); + } + + private final Class targetClass; + private final ObjectInstanceCreator instanceCreator; + + private final String structureName; + private final Structure structureDataType; // null if variable length fields + + private final List> fields = new ArrayList<>(); + private final List> outputFields = new ArrayList<>(); + private final List> markupFuncs = new ArrayList<>(); + private final List contextFields = new ArrayList<>(); + private final List afterMethods = new ArrayList<>(); + private final boolean useFieldMappingInfo; + private Field structureContextField; + + private StructureMappingInfo(Class targetClass, Structure structDataType, + StructureMapping sma) { + this.targetClass = targetClass; + this.structureDataType = structDataType; + this.structureName = structureDataType != null + ? structureDataType.getName() + : sma.structureName(); + this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass); + this.instanceCreator = findInstanceCreator(); + + readFieldInfo(targetClass); + Collections.sort(outputFields, + (foi1, foi2) -> Integer.compare(foi1.getOrdinal(), foi2.getOrdinal())); + + ReflectionHelper.getMarkedMethods(targetClass, AfterStructureRead.class, afterMethods, + true); + + List markupGetters = new ArrayList<>(); + ReflectionHelper.getMarkedMethods(targetClass, Markup.class, markupGetters, true); + for (Method markupGetterMethod : markupGetters) { + markupFuncs.add(context -> { + T obj = context.getStructureInstance(); + Object val = ReflectionHelper.callGetter(markupGetterMethod, obj); + context.getProgramContext().markup(val, false); + }); + } + + for (PlateComment pca : ReflectionHelper.getAnnotations(targetClass, PlateComment.class, + null)) { + addPlateCommentMarkupFuncs(pca); + } + } + + public String getDescription() { + return "%s-%s".formatted(targetClass.getSimpleName(), structureName); + } + + public Structure getStructureDataType() { + return structureDataType; + } + + public String getStructureName() { + return structureName; + } + + public int getStructureLength() { + if (structureDataType == null) { + throw new IllegalArgumentException(); + } + return structureDataType.getLength(); + } + + public Class getTargetClass() { + return targetClass; + } + + public ObjectInstanceCreator getInstanceCreator() { + return instanceCreator; + } + + public List> getFields() { + return fields; + } + + public List getAfterMethods() { + return afterMethods; + } + + public void readStructure(StructureContext context) throws IOException { + T newInstance = context.getStructureInstance(); + if (newInstance instanceof StructureReader selfReader) { + selfReader.readStructure(); + } + else { + for (FieldMappingInfo fieldInfo : fields) { + FieldContext fieldReadContext = context.createFieldContext(fieldInfo, true); + FieldReadFunction readFunc = fieldInfo.getReaderFunc(); + if (readFunc == null) { + throw new IOException("Missing read info for field: " + fieldInfo.getField()); + } + Object value = readFunc.get(fieldReadContext); + assignField(fieldReadContext, value); + } + context.reader.setPointerIndex(context.getStructureEnd()); + } + } + + public List> getMarkupFuncs() { + return markupFuncs; + } + + public Structure createStructureDataType(StructureContext context) throws IOException { + // used to create a structure that has variable length fields + + Structure newStruct = new StructureDataType( + context.getProgramContext().getDefaultVariableLengthStructCategoryPath(), + structureName, + 0, + context.getProgramContext().getDTM()); + + // TODO: set struct packing? + + String nameSuffix = ""; + for (FieldOutputInfo foi : outputFields) { + long structSizeBefore = getStructLength(newStruct); + foi.getOutputFunc().addFieldToStructure(context, newStruct, foi); + long sizeDelta = getStructLength(newStruct) - structSizeBefore; + if (foi.isVariableLength()) { + nameSuffix += "_%d".formatted(sizeDelta); + } + } + if (!nameSuffix.isEmpty()) { + try { + newStruct.setName(structureName + nameSuffix); + } + catch (InvalidNameException | DuplicateNameException e) { + throw new IOException(e); + } + } + return newStruct; + } + + @SuppressWarnings("unchecked") + public StructureContext recoverStructureContext(T structureInstance) throws IOException { + return structureContextField != null + ? ReflectionHelper.getFieldValue(structureInstance, structureContextField, + StructureContext.class) + : null; + } + + /** + * Initializes any {@link ContextField} fields in a new structure instance. + * + * @param context + * @throws IOException + */ + public void assignContextFieldValues(StructureContext context) throws IOException { + Class programContextType = context.getProgramContext().getClass(); + Class structureContextType = context.getClass(); + T obj = context.getStructureInstance(); + + for (Field f : contextFields) { + Class fieldType = f.getType(); + if (fieldType.isAssignableFrom(programContextType)) { + ReflectionHelper.assignField(f, obj, context.getProgramContext()); + } + else if (fieldType.isAssignableFrom(structureContextType)) { + ReflectionHelper.assignField(f, obj, context); + } + else { + throw new IOException("Unsupported context field: " + f); + } + } + } + + private DataTypeComponent getField(String name) { + if (!useFieldMappingInfo || name == null || name.isBlank()) { + return null; + } + for (DataTypeComponent dtc : structureDataType.getDefinedComponents()) { + if (name.equals(dtc.getFieldName())) { + return dtc; + } + } + return null; + } + + private void assignField(FieldContext readContext, Object value) + throws IOException { + Field field = readContext.fieldInfo().getField(); + T structureInstance = readContext.getStructureInstance(); + + ReflectionHelper.assignField(field, structureInstance, value); + } + + private void readFieldInfo(Class clazz) { + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + readFieldInfo(superclass); + } + + for (Field field : clazz.getDeclaredFields()) { + + FieldMapping fma = field.getAnnotation(FieldMapping.class); + FieldOutput foa = field.getAnnotation(FieldOutput.class); + if (fma != null || foa != null) { + FieldMappingInfo fmi = readFieldMappingInfo(field, fma); + field.setAccessible(true); + fields.add(fmi); + + if (foa != null) { + FieldOutputInfo foi = readFieldOutputInfo(fmi, foa); + field.setAccessible(true); + outputFields.add(foi); + } + continue; + } + + ContextField cfa = field.getAnnotation(ContextField.class); + if (cfa != null) { + field.setAccessible(true); + contextFields.add(field); + if (StructureContext.class.isAssignableFrom(field.getType())) { + structureContextField = field; + } + } + } + } + + private FieldMappingInfo readFieldMappingInfo(Field field, FieldMapping fma) { + String fieldName = fma != null && !fma.fieldName().isBlank() + ? fma.fieldName() + : field.getName(); + DataTypeComponent dtc = getField(fieldName); + if (useFieldMappingInfo && dtc == null) { + throw new IllegalArgumentException("Missing structure field: %s in %s" + .formatted(fieldName, targetClass.getSimpleName())); + } + + Signedness signedness = fma != null ? fma.signedness() : Signedness.Unspecified; + int length = fma != null ? fma.length() : -1; + FieldMappingInfo fmi = useFieldMappingInfo + ? FieldMappingInfo.createEarlyBinding(field, dtc, signedness, length) + : FieldMappingInfo.createLateBinding(field, fieldName, signedness, length); + + fmi.setReadFuncClass(fma != null ? fma.readFunc() : FieldReadFunction.class); + fmi.addMarkupNestedFuncs(); + fmi.addCommentMarkupFuncs(); + fmi.addMarkupReferenceFunc(); + + return fmi; + } + + private FieldOutputInfo readFieldOutputInfo(FieldMappingInfo fmi, FieldOutput foa) { + FieldOutputInfo foi = new FieldOutputInfo<>(fmi, foa.dataTypeName(), + foa.isVariableLength(), foa.ordinal(), foa.offset()); + + foi.setOutputFuncClass(foa.fieldOutputFunc(), foa.getter()); + + return foi; + } + + private ObjectInstanceCreator findInstanceCreator() { + Constructor ctor1 = ReflectionHelper.getCtor(targetClass, StructureContext.class); + if (ctor1 != null) { + return (context) -> ReflectionHelper.callCtor(ctor1, context); + } + Constructor ctor2 = ReflectionHelper.getCtor(targetClass); + if (ctor2 != null) { + return (context) -> ReflectionHelper.callCtor(ctor2); + } + throw new IllegalArgumentException( + "Bad instance creator for " + targetClass.getSimpleName()); + } + + private void addPlateCommentMarkupFuncs(PlateComment pca) { + Method commentGetter = + ReflectionHelper.getCommentMethod(targetClass, pca.value(), "toString"); + markupFuncs.add(context -> { + T obj = context.getStructureInstance(); + Object val = ReflectionHelper.callGetter(commentGetter, obj); + if (val != null) { + context.appendComment(CodeUnit.PLATE_COMMENT, null, val.toString(), "\n"); + } + }); + } + + private static int getStructLength(Structure struct) { + return struct.isZeroLength() ? 0 : struct.getLength(); + } + + //--------------------------------------------------------------------------------------------- + interface ReadFromStructureFunction { + T readStructure(StructureContext context) throws IOException; + } + + interface ObjectInstanceCreator { + T get(StructureContext context) throws IOException; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java new file mode 100644 index 0000000000..ef646d4068 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkup.java @@ -0,0 +1,77 @@ +/* ### + * 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.golang.structmapping; + +import java.util.List; + +import java.io.IOException; + +/** + * Optional interface that structure mapped classes can implement that allows them to control how + * their class is marked up. + *

    + * TODO: possibly refactor these methods to take a StructureContext parameter, which will + * allow removing the getStructureContext method. + * + * @param structure mapped class + */ +public interface StructureMarkup { + + StructureContext getStructureContext(); + + /** + * Returns the name of the instance, typically retrieved from data found inside the instance. + * + * @return string name, or null if this instance does not have a name + * @throws IOException + */ + default String getStructureName() throws IOException { + return null; + } + + /** + * Returns a string that can be used to place a label on the instance. + * + * @return string to be used as a labe, or null if there is not a valid label for the instance + * @throws IOException + */ + default String getStructureLabel() throws IOException { + String name = getStructureName(); + return name != null + ? "%s___%s".formatted(name, + getStructureContext().getMappingInfo().getStructureName()) + : null; + } + + /** + * Called to allow the implementor to perform custom markup of itself. + * + * @throws IOException + */ + default void additionalMarkup() throws IOException { + // empty + } + + /** + * Returns a list of items that should be recursively marked up. + * + * @return list of structure mapped object instances that should be marked up + * @throws IOException + */ + default List getExternalInstancesToMarkup() throws IOException { + return List.of(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java new file mode 100644 index 0000000000..2ef490bbae --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMarkupFunction.java @@ -0,0 +1,22 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +public interface StructureMarkupFunction { + void markupStructure(StructureContext context) throws IOException; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureReader.java new file mode 100644 index 0000000000..c5fdd39cdc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureReader.java @@ -0,0 +1,33 @@ +/* ### + * 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.golang.structmapping; + +import java.io.IOException; + +/** + * Interface used by structure mapped classes that need to manually deserialize themselves from + * the raw data, required when the structure contains variable length fields. + * + * @param + */ +public interface StructureReader { + /** + * Called after an instance has been created and its context has been initialized. + * + * @throws IOException + */ + void readStructure() throws IOException; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java index 4fbf813a3a..a3e06f8aa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java @@ -15,15 +15,19 @@ */ package ghidra.app.util.opinion; -import java.io.IOException; import java.util.*; +import java.io.IOException; +import java.io.InputStream; + import com.google.common.primitives.Bytes; import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.Option; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.golang.GoBuildInfo; +import ghidra.app.util.bin.format.golang.PEGoBuildId; import ghidra.app.util.bin.format.mz.DOSHeader; import ghidra.app.util.bin.format.pe.*; import ghidra.app.util.bin.format.pe.ImageCor20Header.ImageCor20Flags; @@ -878,6 +882,7 @@ public class PeLoader extends AbstractPeDebugLoader { BorlandCpp("borland:c++", "borlandcpp"), BorlandUnk("borland:unknown", "borlandcpp"), CLI("cli", "cli"), + GOLANG("golang", "golang"), Unknown("unknown", "unknown"), // The following values represent the presence of ambiguous indicators @@ -1011,6 +1016,10 @@ public class PeLoader extends AbstractPeDebugLoader { // return CompilerEnum.VisualStudio // } + if (isGolang(pe, provider)) { + return CompilerEnum.GOLANG; + } + // Now look for PointerToSymbols (0 for VS, non-zero for gcc) int ptrSymTable = br.readInt(dh.e_lfanew() + 12); if (ptrSymTable != 0) { @@ -1062,5 +1071,31 @@ public class PeLoader extends AbstractPeDebugLoader { return CompilerEnum.Unknown; } + + private static boolean isGolang(PortableExecutable pe, ByteProvider provider) { + boolean buildIdPresent = false; + boolean buildInfoPresent = false; + + SectionHeader textSection = pe.getNTHeader().getFileHeader().getSectionHeader(".text"); + if (textSection != null) { + try (InputStream is = textSection.getDataStream()) { + PEGoBuildId buildId = PEGoBuildId.read(is); + buildIdPresent = buildId != null; + } + catch (IOException e) { + // fail + } + } + SectionHeader dataSection = pe.getNTHeader().getFileHeader().getSectionHeader(".data"); + if (dataSection != null) { + try (InputStream is = dataSection.getDataStream()) { + buildInfoPresent = GoBuildInfo.isPresent(is); + } + catch (IOException e) { + // fail + } + } + return buildIdPresent || buildInfoPresent; + } } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java index 31f0436ce3..fd4a2e1e52 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java @@ -18,8 +18,9 @@ package ghidra.app.plugin.core.datamgr; import static org.junit.Assert.*; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.Test; @@ -37,46 +38,62 @@ public class DataTypeArchiveIDTest extends AbstractGenericTest { private static final String GENERIC_CLIB_64_GDT_PATH = "typeinfo/generic/generic_clib_64.gdt"; private static final String MAC_OS_10_9_GDT_PATH = "typeinfo/mac_10.9/mac_osx.gdt"; - private static final HashMap archiveIdMap = new HashMap<>(); - static { - archiveIdMap.put(WIN_VS12_32_GDT_PATH, "2644092282468053077"); - archiveIdMap.put(WIN_VS12_64_GDT_PATH, "3193696833254024484"); - archiveIdMap.put(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"); - archiveIdMap.put(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"); - archiveIdMap.put(MAC_OS_10_9_GDT_PATH, "2650667045259492112"); + private static final Map archiveIdMap = Map.of( + WIN_VS12_32_GDT_PATH, "2644092282468053077", + WIN_VS12_64_GDT_PATH, "3193696833254024484", + GENERIC_CLIB_32_GDT_PATH, "2644097909188870631", + GENERIC_CLIB_64_GDT_PATH, "3193699959493190971", + MAC_OS_10_9_GDT_PATH, "2650667045259492112", + "typeinfo/golang/golang_1.18_32bit_any.gdt", "3516036612698499343", + "typeinfo/golang/golang_1.18_64bit_any.gdt", "3516036958082656531"); + + private Map getCurrentGdts() { + return Application.findFilesByExtensionInApplication(".gdt") + .stream() + .filter(f -> f.getAbsolutePath().contains("/data/typeinfo/")) + .collect(Collectors.toMap(Function.identity(), f -> getGdtUniversalId(f))); + } + + private String getGdtUniversalId(ResourceFile gdtFile) { + FileDataTypeManager dtm = null; + try { + dtm = FileDataTypeManager.openFileArchive(gdtFile, false); + assertEquals(ArchiveWarning.NONE, dtm.getWarning()); + return dtm.getUniversalID().toString(); + } + catch (IOException e) { + return "failed to read " + gdtFile.getName(); + } + finally { + dtm.close(); + } + } + + private String getGdtRelativePath(ResourceFile gdtFile) { + String path = gdtFile.getAbsolutePath(); + int ix = path.indexOf("/typeinfo/"); + path = path.substring(ix + 1); + return path; } @Test - public void testArchiveIDMatch() throws IOException { + public void testArchiveIDMatch() { - HashSet notFound = new HashSet<>(archiveIdMap.keySet()); + Map currentGdts = getCurrentGdts(); + Set notFound = new HashSet<>(archiveIdMap.keySet()); + for (ResourceFile gdtFile : currentGdts.keySet()) { + String currentID = currentGdts.get(gdtFile); - for (ResourceFile gdtFile : Application.findFilesByExtensionInApplication(".gdt")) { - - String path = gdtFile.getAbsolutePath(); - if (!path.contains("/data/typeinfo/")) { - continue; // only verify standard archives - } - - int ix = path.indexOf("/typeinfo/"); - path = path.substring(ix + 1); // path starts with typeinfo/... - - String oldID = archiveIdMap.get(path); + String gdtRelativePath = getGdtRelativePath(gdtFile); + String oldID = archiveIdMap.get(gdtRelativePath); if (oldID == null) { - fail("New archive added, test must be updated: " + path); + fail("New archive added, test must be updated: %s, ID: %s" + .formatted(gdtRelativePath, currentID)); } - notFound.remove(path); + notFound.remove(gdtRelativePath); - FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false); - assertEquals(ArchiveWarning.NONE, dtm.getWarning()); - try { - assertEquals("Archive UniversalID mismatch: " + path, oldID, - dtm.getUniversalID().toString()); - } - finally { - dtm.close(); - } + assertEquals("Archive UniversalID mismatch: " + gdtRelativePath, oldID, currentID); } if (!notFound.isEmpty()) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporterTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporterTest.java index 802f43b366..c5dcb13598 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporterTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFDataTypeImporterTest.java @@ -1196,6 +1196,36 @@ public class DWARFDataTypeImporterTest extends DWARFTestBase { assertEquals(10, arr.getNumElements()); } + @Test + public void testArrayWithZeroLenDataType() + throws CancelledException, IOException, DWARFException { + // Tests that an array with non-zero elements, but with zero-len data type + // becomes a zero-element array. (yuck) + DebugInfoEntry emptyStructDIE = newStruct("emptystruct", 0).create(cu); + DebugInfoEntry arrayDIE = newArrayUsingCount(cu, emptyStructDIE, 10); + + importAllDataTypes(); + + Structure emptyStruct = (Structure) dwarfDTM.getDataType(emptyStructDIE.getOffset(), null); + Array arr = (Array) dwarfDTM.getDataType(arrayDIE.getOffset(), null); + assertTrue(emptyStruct.isZeroLength()); + assertEquals(0, arr.getNumElements()); + assertTrue(arr.isZeroLength()); + } + + @Test + public void testArrayWithZeroElements() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry arrayDIE = newArrayUsingCount(cu, intDIE, 0); + + importAllDataTypes(); + + Array arr = (Array) dwarfDTM.getDataType(arrayDIE.getOffset(), null); + assertEquals(0, arr.getNumElements()); + assertTrue(arr.isZeroLength()); + } + // not implemented yet public void testSubr() { // func ptrs diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporterTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporterTest.java index c95830c0c8..3328d246bd 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporterTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFFunctionImporterTest.java @@ -25,13 +25,16 @@ import org.junit.Test; import ghidra.app.util.NamespaceUtils; import ghidra.app.util.bin.format.dwarf4.*; +import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionOpCodes; +import ghidra.program.database.function.OverlappingFunctionException; +import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.*; -import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.Namespace; -import ghidra.util.exception.CancelledException; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.exception.*; public class DWARFFunctionImporterTest extends DWARFTestBase { @@ -161,15 +164,43 @@ public class DWARFFunctionImporterTest extends DWARFTestBase { } @Test - public void testThisParam() + public void testNoReturnFlag_True() throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DIECreator func = newSubprogram("foo", intDIE, 0x410, 10); + func.addBoolean(DWARFAttribute.DW_AT_noreturn, true); + func.create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + assertNotNull(fooFunc); + assertTrue(fooFunc.hasNoReturn()); + } + + @Test + public void testNoReturnFlag_False() throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DIECreator func = newSubprogram("foo", intDIE, 0x410, 10); + func.create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + assertNotNull(fooFunc); + assertFalse(fooFunc.hasNoReturn()); + } + + @Test + public void testDetailParamLocation_Converted_to_spill() throws CancelledException, IOException, DWARFException { + // Test that a parameter's storage info is ignored if it looks invalid. + // In this case, it refers to a location in the function's local variable area, so + // it should have been converted to a spill local variable. DebugInfoEntry intDIE = addInt(cu); - DebugInfoEntry ptrDIE = addPtr(intDIE, cu); DebugInfoEntry fooDIE = newSubprogram("foo", intDIE, 0x410, 10).create(cu); - newFormalParam(fooDIE, "this", ptrDIE, - DWARFExpressionOpCodes.DW_OP_implicit_value /* requires this to be non supported location opcode to trigger formal_param_only mode*/, - 0x6c).create(cu); + newFormalParam(fooDIE, "param1", intDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x6c) // fbreg -14, func local variable area + .create(cu); importFunctions(); @@ -177,8 +208,193 @@ public class DWARFFunctionImporterTest extends DWARFTestBase { assertNotNull(fooFunc); assertEquals("foo", fooFunc.getName()); + DataType returnType = fooFunc.getReturnType(); + assertNotNull(returnType); + assertEquals("int", returnType.getName()); + Parameter[] fooParams = fooFunc.getParameters(); assertEquals(fooParams.length, 1); - assertEquals(CompilerSpec.CALLING_CONVENTION_thiscall, fooFunc.getCallingConventionName()); + assertEquals("param1", fooParams[0].getName()); + assertEquals("int", fooParams[0].getDataType().getName()); + + assertFalse(fooParams[0].isStackVariable()); // was converted to register storage via default calling convention + } + + @Test + public void testDetailParamLocation_Used() + throws CancelledException, IOException, DWARFException { + // Test that a parameter's storage info is used if it looks valid and is present at + // the start of the function. + + // TODO: need to also test location info from a debug_loc sequence that specifies a lexical offset + + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry fooDIE = newSubprogram("foo", intDIE, 0x410, 10).create(cu); + newFormalParam(fooDIE, "param1", intDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x8) // fbreg +8, caller stack area + .create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + assertNotNull(fooFunc); + + assertEquals("foo", fooFunc.getName()); + DataType returnType = fooFunc.getReturnType(); + assertNotNull(returnType); + assertEquals("int", returnType.getName()); + + Parameter[] fooParams = fooFunc.getParameters(); + assertEquals(fooParams.length, 1); + assertEquals("param1", fooParams[0].getName()); + assertEquals("int", fooParams[0].getDataType().getName()); + + assertTrue(fooParams[0].isStackVariable()); + assertEquals(8, fooParams[0].getStackOffset()); + } + + @Test + public void testThisParamDetect_NamedThis() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry floatDIE = addFloat(cu); + DebugInfoEntry struct1PtrDIE = addFwdPtr(cu, 1); + DebugInfoEntry struct1DIE = newStruct("mystruct", 100).create(cu); + DebugInfoEntry fooDIE = + newSubprogram("foo", intDIE, 0x410, 10).setParent(struct1DIE).create(cu); + newFormalParam(fooDIE, "this", struct1PtrDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x6c) + .create(cu); + + newMember(struct1DIE, "f1", intDIE, 0).create(cu); + newMember(struct1DIE, "f2", floatDIE, 10).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + + assertEquals("foo", fooFunc.getName()); + assertEquals("__thiscall", fooFunc.getCallingConventionName()); + } + + @Test + public void testThisParamDetect_ArtificalUnNamed() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry floatDIE = addFloat(cu); + DebugInfoEntry struct1PtrDIE = addFwdPtr(cu, 1); + DebugInfoEntry struct1DIE = newStruct("mystruct", 100).create(cu); + DebugInfoEntry fooDIE = + newSubprogram("foo", intDIE, 0x410, 10).setParent(struct1DIE).create(cu); + newFormalParam(fooDIE, null, struct1PtrDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x6c) + .addBoolean(DWARFAttribute.DW_AT_artificial, true) + .create(cu); + + newMember(struct1DIE, "f1", intDIE, 0).create(cu); + newMember(struct1DIE, "f2", floatDIE, 10).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + + assertEquals("foo", fooFunc.getName()); + assertEquals("__thiscall", fooFunc.getCallingConventionName()); + } + + @Test + public void testThisParamDetect_ObjectPointer() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry floatDIE = addFloat(cu); + DebugInfoEntry struct1PtrDIE = addFwdPtr(cu, 1); + DebugInfoEntry struct1DIE = newStruct("mystruct", 100).create(cu); + DebugInfoEntry fooDIE = + newSubprogram("foo", intDIE, 0x410, 10) + .addRef(DWARFAttribute.DW_AT_object_pointer, getForwardOffset(cu, 1)) + .setParent(struct1DIE) + .create(cu); + newFormalParam(fooDIE, null, struct1PtrDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x6c) + .create(cu); + + newMember(struct1DIE, "f1", intDIE, 0).create(cu); + newMember(struct1DIE, "f2", floatDIE, 10).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + + assertEquals("foo", fooFunc.getName()); + assertEquals("__thiscall", fooFunc.getCallingConventionName()); + } + + @Test + public void testThisParamDetect_Unnamed() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry floatDIE = addFloat(cu); + DebugInfoEntry struct1PtrDIE = addFwdPtr(cu, 1); + DebugInfoEntry struct1DIE = newStruct("mystruct", 100).create(cu); + DebugInfoEntry fooDIE = + newSubprogram("foo", intDIE, 0x410, 10) + .setParent(struct1DIE) + .create(cu); + newFormalParam(fooDIE, null, struct1PtrDIE, DWARFExpressionOpCodes.DW_OP_fbreg, 0x6c) + .create(cu); + + newMember(struct1DIE, "f1", intDIE, 0).create(cu); + newMember(struct1DIE, "f2", floatDIE, 10).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + + assertEquals("foo", fooFunc.getName()); + assertEquals("__thiscall", fooFunc.getCallingConventionName()); + } + + @Test + public void testParamNameConflictsWithLocalVar() + throws CancelledException, IOException, DWARFException, + InvalidInputException, OverlappingFunctionException, DuplicateNameException { + Function initialFoo = program.getListing() + .createFunction("foo", addr(0x410), new AddressSet(addr(0x410), addr(0x411)), + SourceType.DEFAULT); + + DataType intDT = IntegerDataType.getSignedDataType(4, program.getDataTypeManager()); + initialFoo.addLocalVariable(new LocalVariableImpl("testxyz", intDT, -16, program), + SourceType.USER_DEFINED); + + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry fooDIE = newSubprogram("foo", intDIE, 0x410, 10).create(cu); + newFormalParam(fooDIE, "param1", intDIE).create(cu); + newFormalParam(fooDIE, "param2", intDIE).create(cu); + newFormalParam(fooDIE, "testxyz" /* should collide with local var */, intDIE).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + assertNotNull(fooFunc); + Parameter[] parameters = fooFunc.getParameters(); + assertEquals("param1", parameters[0].getName()); + assertEquals("param2", parameters[1].getName()); + assertEquals("testxyz_1", parameters[2].getName()); + } + + @Test + public void testParamNameBadChars() + throws CancelledException, IOException, DWARFException, InvalidInputException, + OverlappingFunctionException, DuplicateNameException { + + DebugInfoEntry intDIE = addInt(cu); + DebugInfoEntry fooDIE = newSubprogram("foo", intDIE, 0x410, 10).create(cu); + newFormalParam(fooDIE, "param 1", intDIE).create(cu); + newFormalParam(fooDIE, "param\t2", intDIE).create(cu); + + importFunctions(); + + Function fooFunc = program.getListing().getFunctionAt(addr(0x410)); + + assertNotNull(fooFunc); + Parameter[] parameters = fooFunc.getParameters(); + assertEquals("param_1", parameters[0].getName()); + assertEquals("param_2", parameters[1].getName()); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFStaticVarImporterTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFStaticVarImporterTest.java index 331946e691..e7e4c34e2c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFStaticVarImporterTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFStaticVarImporterTest.java @@ -18,6 +18,9 @@ package ghidra.app.util.bin.format.dwarf4.next; import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.*; import static org.junit.Assert.*; +import java.util.HashSet; +import java.util.Set; + import java.io.IOException; import org.junit.Test; @@ -25,9 +28,11 @@ import org.junit.Test; import ghidra.app.util.bin.format.dwarf4.*; import ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag; import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionOpCodes; -import ghidra.program.model.data.IntegerDataType; +import ghidra.program.model.data.*; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Data; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolType; import ghidra.util.exception.CancelledException; public class DWARFStaticVarImporterTest extends DWARFTestBase { @@ -51,4 +56,87 @@ public class DWARFStaticVarImporterTest extends DWARFTestBase { assertTrue(((Data) cu).getDataType() instanceof IntegerDataType); } + @Test + public void testZeroLenGlobalVar() throws CancelledException, IOException, DWARFException { + DebugInfoEntry emptyStructDIE = newStruct("emptystruct", 0).create(cu); + new DIECreator(DWARFTag.DW_TAG_variable) + .addString(DW_AT_name, "static_var1") + .addRef(DW_AT_type, emptyStructDIE) + .addBlock(DW_AT_location, DWARFExpressionOpCodes.DW_OP_addr, 0x10, 0x4, 0, 0, 0, 0, + 0, 0) + .create(cu); + + importFunctions(); + + CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x410)); + assertNotNull(cu); + assertEquals("static_var1", cu.getLabel()); + assertEquals(1, cu.getLength()); + DataType dataType = ((Data) cu).getDataType(); + assertTrue(dataType instanceof Undefined || dataType instanceof DefaultDataType); + } + + @Test + public void test2ZeroLenGlobalVar() throws CancelledException, IOException, DWARFException { + DebugInfoEntry emptyStructDIE = newStruct("emptystruct", 0).create(cu); + new DIECreator(DWARFTag.DW_TAG_variable) + .addString(DW_AT_name, "static_var1") + .addRef(DW_AT_type, emptyStructDIE) + .addBlock(DW_AT_location, DWARFExpressionOpCodes.DW_OP_addr, 0x10, 0x4, 0, 0, 0, 0, + 0, 0) + .create(cu); + new DIECreator(DWARFTag.DW_TAG_variable) + .addString(DW_AT_name, "static_var2") + .addRef(DW_AT_type, emptyStructDIE) + .addBlock(DW_AT_location, DWARFExpressionOpCodes.DW_OP_addr, 0x10, 0x4, 0, 0, 0, 0, + 0, 0) + .create(cu); + + importFunctions(); + + Set labelNames = getLabelNames(program.getSymbolTable().getSymbols(addr(0x410))); + assertTrue(labelNames.contains("static_var1")); + assertTrue(labelNames.contains("static_var2")); + } + + @Test + public void testZeroLenGlobalVarOnTopOfNormalVar() + throws CancelledException, IOException, DWARFException { + DebugInfoEntry emptyStructDIE = newStruct("emptystruct", 0).create(cu); + DebugInfoEntry intDIE = addInt(cu); + new DIECreator(DWARFTag.DW_TAG_variable) + .addString(DW_AT_name, "static_var1") + .addRef(DW_AT_type, intDIE) + .addBlock(DW_AT_location, DWARFExpressionOpCodes.DW_OP_addr, 0x10, 0x4, 0, 0, 0, 0, + 0, 0) + .create(cu); + new DIECreator(DWARFTag.DW_TAG_variable) + .addString(DW_AT_name, "static_var2") + .addRef(DW_AT_type, emptyStructDIE) + .addBlock(DW_AT_location, DWARFExpressionOpCodes.DW_OP_addr, 0x10, 0x4, 0, 0, 0, 0, + 0, 0) + .create(cu); + + importFunctions(); + + Set labelNames = getLabelNames(program.getSymbolTable().getSymbols(addr(0x410))); + assertTrue(labelNames.contains("static_var1")); + assertTrue(labelNames.contains("static_var2")); + + CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x410)); + assertNotNull(cu); + assertEquals("static_var1", cu.getLabel()); + assertEquals(4, cu.getLength()); + assertTrue(((Data) cu).getDataType() instanceof IntegerDataType); + } + + private Set getLabelNames(Symbol[] symbols) { + Set result = new HashSet<>(); + for (Symbol symbol : symbols) { + if (symbol.getSymbolType() == SymbolType.LABEL) { + result.add(symbol.getName()); + } + } + return result; + } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DIETest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DIETest.java index c817aa325b..fcc4e36491 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DIETest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DIETest.java @@ -15,7 +15,7 @@ */ package ghidra.app.util.bin.format.dwarf4; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.io.IOException; @@ -39,19 +39,28 @@ public class DIETest extends AbstractGenericTest { DWARFProgram prog; DWARFAttributeFactory attribFactory; MockDWARFCompilationUnit cu; + int transactionID; + Program ghidraProgram; @Before public void setUp() throws Exception { ToyProgramBuilder builder = new ToyProgramBuilder("Test", true); - Program ghidraProgram = builder.getProgram(); + ghidraProgram = builder.getProgram(); + startTransaction(); prog = new DWARFProgram(ghidraProgram, new DWARFImportOptions(), TaskMonitor.DUMMY, new NullSectionProvider()); attribFactory = prog.getAttributeFactory(); cu = new MockDWARFCompilationUnit(prog, 0x1000, 0x2000, 0, DWARFCompilationUnit.DWARF_32, (short) 4, 0, (byte) 8, 0, DWARFSourceLanguage.DW_LANG_C); + + } + + @After + public void tearDown() throws Exception { + endTransaction(); } @Test @@ -182,4 +191,14 @@ public class DIETest extends AbstractGenericTest { } + //--------------------------------------------------------------------------------------------- + + protected void startTransaction() { + transactionID = ghidraProgram.startTransaction("Test"); + } + + protected void endTransaction() { + ghidraProgram.endTransaction(transactionID, true); + } + } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DWARFTestBase.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DWARFTestBase.java index 44c65b994c..6f8ec90133 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DWARFTestBase.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/DWARFTestBase.java @@ -65,9 +65,6 @@ public class DWARFTestBase extends AbstractGhidraHeadedIntegrationTest { protected CategoryPath uncatCP; protected CategoryPath dwarfRootCP; - /* - * @see TestCase#setUp() - */ @Before public void setUp() throws Exception { program = createDefaultProgram(testName.getMethodName(), ProgramBuilder._X64, this); @@ -87,6 +84,7 @@ public class DWARFTestBase extends AbstractGhidraHeadedIntegrationTest { importOptions = new DWARFImportOptions(); dwarfProg = new DWARFProgram(program, importOptions, TaskMonitor.DUMMY, new NullSectionProvider()); + dwarfDTM = dwarfProg.getDwarfDTM(); dwarfRootCP = dwarfProg.getRootDNI().asCategoryPath(); uncatCP = dwarfProg.getUncategorizedRootDNI().asCategoryPath(); @@ -98,14 +96,8 @@ public class DWARFTestBase extends AbstractGhidraHeadedIntegrationTest { DWARFSourceLanguage.DW_LANG_C); setMockCompilationUnits(cu, cu2); - - DWARFImportSummary importSummary = new DWARFImportSummary(); - dwarfDTM = new DWARFDataTypeManager(dwarfProg, dataMgr, builtInDTM, importSummary); } - /* - * @see TestCase#tearDown() - */ @After public void tearDown() throws Exception { endTransaction(); @@ -141,9 +133,7 @@ public class DWARFTestBase extends AbstractGhidraHeadedIntegrationTest { dwarfProg.checkPreconditions(monitor); dwarfDTM.importAllDataTypes(monitor); - DWARFImportSummary importSummary = new DWARFImportSummary(); - DWARFFunctionImporter dfi = - new DWARFFunctionImporter(dwarfProg, dwarfDTM, importOptions, importSummary, monitor); + DWARFFunctionImporter dfi = new DWARFFunctionImporter(dwarfProg, monitor); dfi.importFunctions(); } @@ -348,10 +338,14 @@ public class DWARFTestBase extends AbstractGhidraHeadedIntegrationTest { protected DIECreator newFormalParam(DebugInfoEntry subprogram, String paramName, DebugInfoEntry paramDataType, int... locationExpr) { DIECreator param = new DIECreator(DW_TAG_formal_parameter) - .addString(DW_AT_name, paramName) .addRef(DW_AT_type, paramDataType) - .addBlock(DW_AT_location, locationExpr) .setParent(subprogram); + if (locationExpr.length > 0) { + param.addBlock(DW_AT_location, locationExpr); + } + if (paramName != null) { + param.addString(DW_AT_name, paramName); + } return param; } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactoryTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactoryTest.java index 1ebed8c2e0..538d9837b5 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactoryTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf4/attribs/DWARFAttributeFactoryTest.java @@ -50,6 +50,7 @@ public class DWARFAttributeFactoryTest extends AbstractGenericTest { ToyProgramBuilder builder = new ToyProgramBuilder("Test", true); Program ghidraProgram = builder.getProgram(); + ghidraProgram.startTransaction("Test"); prog = new DWARFProgram(ghidraProgram, new DWARFImportOptions(), TaskMonitor.DUMMY, new NullSectionProvider()); stringTable = prog.getDebugStrings(); diff --git a/Ghidra/Framework/SoftwareModeling/data/languages/language_common.rxg b/Ghidra/Framework/SoftwareModeling/data/languages/language_common.rxg index 69cd59f105..6398b6d868 100644 --- a/Ghidra/Framework/SoftwareModeling/data/languages/language_common.rxg +++ b/Ghidra/Framework/SoftwareModeling/data/languages/language_common.rxg @@ -148,6 +148,21 @@ + + + + + + + + + + + + + + + diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ParameterImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ParameterImpl.java index 01786498b9..718c4dc290 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ParameterImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ParameterImpl.java @@ -233,7 +233,7 @@ public class ParameterImpl extends VariableImpl implements Parameter { * @throws InvalidInputException if dataType restrictions are violated, an invalid storage * element is specified, or error while resolving storage element for specified datatype */ - protected ParameterImpl(String name, int ordinal, DataType dataType, VariableStorage storage, + public ParameterImpl(String name, int ordinal, DataType dataType, VariableStorage storage, boolean force, Program program, SourceType sourceType) throws InvalidInputException { this(name, ordinal, dataType, storage, null, null, null, force, program, sourceType); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java index c901a0df90..1950192e8f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java @@ -948,6 +948,35 @@ public class SymbolUtilities { return symType.toString(); } + /** + * Returns the global symbol with the given name if and only if it is the only global symbol + * with that name. + * + * @param program the program to search. + * @param name the name of the global symbol to find. + * @return the global symbol with the given name if and only if it is the only one. + */ + public static Symbol getUniqueSymbol(Program program, String name) { + return getUniqueSymbol(program, name, null); + } + + /** + * Returns the symbol in the given namespace with the given name if and only if it is the only + * symbol in that namespace with that name. + * + * @param program the program to search. + * @param name the name of the symbol to find. + * @param namespace the parent namespace; may be null + * @return the symbol with the given name if and only if it is the only one in that namespace + */ + public static Symbol getUniqueSymbol(Program program, String name, Namespace namespace) { + List symbols = program.getSymbolTable().getSymbols(name, namespace); + if (symbols.size() == 1) { + return symbols.get(0); + } + return null; + } + /** * Returns the unique global label or function symbol with the given name. Also, logs if there * is not exactly one symbol with that name. diff --git a/Ghidra/Processors/ARM/certification.manifest b/Ghidra/Processors/ARM/certification.manifest index af7c971381..bf56354a99 100644 --- a/Ghidra/Processors/ARM/certification.manifest +++ b/Ghidra/Processors/ARM/certification.manifest @@ -1,5 +1,7 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| +data/languages/ARM-32-golang.cspec||GHIDRA||||END| +data/languages/ARM-32-golang.register.info||GHIDRA||||END| data/languages/ARM.cspec||GHIDRA||||END| data/languages/ARM.dwarf||GHIDRA||||END| data/languages/ARM.gdis||GHIDRA||||END| diff --git a/Ghidra/Processors/x86/certification.manifest b/Ghidra/Processors/x86/certification.manifest index 325d061fe5..3f8ddffd31 100644 --- a/Ghidra/Processors/x86/certification.manifest +++ b/Ghidra/Processors/x86/certification.manifest @@ -32,7 +32,11 @@ data/languages/x86-16-real.pspec||GHIDRA||||END| data/languages/x86-16.cspec||GHIDRA||||END| data/languages/x86-16.gdis||GHIDRA||||END| data/languages/x86-16.pspec||GHIDRA||||END| +data/languages/x86-32-golang.cspec||GHIDRA||||END| +data/languages/x86-32-golang.register.info||GHIDRA||||END| data/languages/x86-64-gcc.cspec||GHIDRA||||END| +data/languages/x86-64-golang.cspec||GHIDRA||||END| +data/languages/x86-64-golang.register.info||GHIDRA||||END| data/languages/x86-64-win.cspec||GHIDRA||||END| data/languages/x86-64.dwarf||GHIDRA||||END| data/languages/x86-64.pspec||GHIDRA||||END| diff --git a/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec b/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec new file mode 100644 index 0000000000..c50e47e186 --- /dev/null +++ b/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info b/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info new file mode 100644 index 0000000000..28c42ff062 --- /dev/null +++ b/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec new file mode 100644 index 0000000000..ab250c7b26 --- /dev/null +++ b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info b/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info new file mode 100644 index 0000000000..113ef2635c --- /dev/null +++ b/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Ghidra/Processors/x86/data/languages/x86.ldefs b/Ghidra/Processors/x86/data/languages/x86.ldefs index 482ef9e398..d643d5fc88 100644 --- a/Ghidra/Processors/x86/data/languages/x86.ldefs +++ b/Ghidra/Processors/x86/data/languages/x86.ldefs @@ -16,6 +16,7 @@ + @@ -28,6 +29,7 @@ + + + diff --git a/Ghidra/Processors/x86/data/languages/x86.opinion b/Ghidra/Processors/x86/data/languages/x86.opinion index ef5283f7c5..341758b0ca 100644 --- a/Ghidra/Processors/x86/data/languages/x86.opinion +++ b/Ghidra/Processors/x86/data/languages/x86.opinion @@ -22,6 +22,10 @@ + + + + diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsTest.java index 5cd7cbad6a..7a20eea55c 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/bin/format/dwarf4/next/DWARFRegisterMappingsTest.java @@ -24,14 +24,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; -import generic.jar.ResourceFile; import generic.test.category.NightlyCategory; import ghidra.program.model.lang.*; import ghidra.program.util.DefaultLanguageService; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.util.xml.XmlUtilities; -import utilities.util.FileResolutionResult; -import utilities.util.FileUtilities; @Category(NightlyCategory.class) public class DWARFRegisterMappingsTest extends AbstractGhidraHeadlessIntegrationTest { @@ -53,21 +50,11 @@ public class DWARFRegisterMappingsTest extends AbstractGhidraHeadlessIntegration public void testReadMappings() throws IOException { for (LanguageDescription langDesc : DefaultLanguageService.getLanguageService().getLanguageDescriptions( false)) { - - if (!DWARFRegisterMappingsManager.hasDWARFRegisterMapping(langDesc)) { - continue; - } - Language lang = DefaultLanguageService.getLanguageService().getLanguage(langDesc.getLanguageID()); - ResourceFile mappingFile = - DWARFRegisterMappingsManager.getDWARFRegisterMappingFileFor(lang); - FileResolutionResult dwarfFileFRR; - if ((dwarfFileFRR = FileUtilities.existsAndIsCaseDependent(mappingFile)) != null && - !dwarfFileFRR.isOk()) { - throw new IOException( - "DWARF register mapping filename case problem: " + dwarfFileFRR.getMessage()); + if (!DWARFRegisterMappingsManager.hasDWARFRegisterMapping(lang)) { + continue; } DWARFRegisterMappings drm = DWARFRegisterMappingsManager.readMappingForLang(lang);