From b5422faefb5f151b113358e9608bcb0e9d4aa636 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:39:26 +0000 Subject: [PATCH] GP-2425 more better duffzero / duffcopy function info --- .../core/analysis/GolangSymbolAnalyzer.java | 166 +++++++++++---- .../ProgramStartingLocationOptions.java | 2 +- .../util/bin/format/golang/GoConstants.java | 8 + .../bin/format/golang/GoRegisterInfo.java | 28 ++- .../format/golang/GoRegisterInfoManager.java | 10 +- .../format/golang/rtti/GoFunctabEntry.java | 4 + .../bin/format/golang/rtti/GoModuledata.java | 37 +++- .../bin/format/golang/rtti/GoRttiMapper.java | 125 ++++++++++-- .../util/bin/format/golang/rtti/GoSlice.java | 7 + .../format/golang/rtti/types/GoPlainType.java | 3 +- .../golang/rtti/types/GoStructType.java | 5 + .../bin/format/golang/rtti/types/GoType.java | 13 ++ .../golang/rtti/types/GoUncommonType.java | 17 +- .../ghidra/program/util/FunctionUtility.java | 2 +- .../analysis/GolangDuffFixupAnalyzer.java | 191 ++++++++++++++++++ .../x86/data/languages/x86-32-golang.cspec | 50 +++++ .../x86/data/languages/x86-64-golang.cspec | 62 ++++++ 17 files changed, 642 insertions(+), 88 deletions(-) create mode 100644 Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java 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 index ea8350ac44..6f3907884d 100644 --- 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 @@ -18,30 +18,30 @@ 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 java.util.*; import generic.jar.ResourceFile; import ghidra.app.services.*; import ghidra.app.util.MemoryBlockUtils; +import ghidra.app.util.bin.format.dwarf4.DWARFUtil; 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.GoRttiMapper; +import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.MarkupSession; 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.data.*; +import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; 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.exception.*; import ghidra.util.task.TaskMonitor; import ghidra.util.task.UnknownProgressWrappingTaskMonitor; import ghidra.xml.XmlParseException; @@ -73,34 +73,35 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { throws CancelledException { monitor.setMessage("Golang symbol analyzer"); - try (GoRttiMapper programContext = GoRttiMapper.getMapperFor(program, log)) { - if (programContext == null) { + try (GoRttiMapper goBinary = GoRttiMapper.getMapperFor(program, log)) { + if (goBinary == null) { Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper"); return false; } - programContext.discoverGoTypes(monitor); - - GoModuledata firstModule = programContext.getFirstModule(); - + goBinary.init(monitor); + goBinary.discoverGoTypes(monitor); UnknownProgressWrappingTaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor, 100); upwtm.initialize(0); upwtm.setMessage("Marking up Golang RTTI structures"); - MarkupSession markupSession = programContext.createMarkupSession(upwtm); + MarkupSession markupSession = goBinary.createMarkupSession(upwtm); + GoModuledata firstModule = goBinary.getFirstModule(); + if (firstModule != null) { + markupSession.labelStructure(firstModule, "firstmoduledata"); + markupSession.markup(firstModule, false); + } - markupSession.labelStructure(firstModule, "firstmoduledata"); - markupSession.markup(firstModule, false); - - markupMiscInfoStructs(program); - markupWellknownSymbols(programContext, markupSession); + markupWellknownSymbols(goBinary, markupSession); + setupProgramContext(goBinary, markupSession); + goBinary.recoverDataTypes(monitor); + markupGoFunctions(goBinary, markupSession); fixupNoReturnFuncs(program); - setupProgramContext(programContext, markupSession); - programContext.recoverDataTypes(monitor); + markupMiscInfoStructs(program); if (analyzerOptions.createBootstrapDatatypeArchive) { - createBootstrapGDT(programContext, program, monitor); + createBootstrapGDT(goBinary, program, monitor); } } catch (IOException e) { @@ -124,23 +125,103 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { analyzerOptions.createBootstrapDatatypeArchive); } - private void markupWellknownSymbols(GoRttiMapper programContext, MarkupSession session) + private void markupWellknownSymbols(GoRttiMapper goBinary, MarkupSession session) throws IOException { - Program program = programContext.getProgram(); - + Program program = goBinary.getProgram(); + Symbol g0 = SymbolUtilities.getUniqueSymbol(program, "runtime.g0"); - Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class); + Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class); if (g0 != null && gStruct != null) { session.markupAddressIfUndefined(g0.getAddress(), gStruct); } - + Symbol m0 = SymbolUtilities.getUniqueSymbol(program, "runtime.m0"); - Structure mStruct = programContext.getGhidraDataType("runtime.m", Structure.class); + Structure mStruct = goBinary.getGhidraDataType("runtime.m", Structure.class); if (m0 != null && mStruct != null) { session.markupAddressIfUndefined(m0.getAddress(), mStruct); } } + private void markupGoFunctions(GoRttiMapper goBinary, MarkupSession markupSession) + throws IOException { + for (GoFuncData funcdata : goBinary.getAllFunctions()) { + String funcname = SymbolUtilities.replaceInvalidChars(funcdata.getName(), true); + markupSession.createFunctionIfMissing(funcname, funcdata.getFuncAddress()); + } + try { + fixDuffFunctions(goBinary, markupSession); + } + catch (InvalidInputException | DuplicateNameException e) { + Msg.error(this, "Error configuring duff functions", e); + } + } + + /** + * Fixes the function signature of the runtime.duffzero and runtime.duffcopy functions. + *

+ * The alternate duff-ified entry points haven't been discovered yet, so the information + * set to the main function entry point will be propagated at a later time to the alternate + * entry points by the GolangDuffFixupAnalyzer. + * + * @param goBinary the golang binary + * @param session {@link MarkupSession} + * @throws InvalidInputException if error assigning the function signature + * @throws DuplicateNameException if error assigning the function signature + */ + private void fixDuffFunctions(GoRttiMapper goBinary, MarkupSession session) + throws InvalidInputException, DuplicateNameException { + Program program = goBinary.getProgram(); + GoRegisterInfo regInfo = goBinary.getRegInfo(); + DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType); + DataType uintDT = goBinary.getTypeOrDefault("uint", DataType.class, + AbstractUnsignedIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null)); + + GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero"); + Function duffzeroFunc = duffzeroFuncdata != null + ? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress()) + : null; + PrototypeModel duffzeroCC = goBinary.getDuffzeroCallingConvention(); + if (duffzeroFunc != null && duffzeroCC != null) { + // NOTE: some duffzero funcs need a zero value supplied to them via a register set + // by the caller. (depending on the arch) The duffzero calling convention defined + // by the callspec should take care of this by defining that register as the second + // storage location. Otherwise, the callspec will only have a single storage + // location defined. + boolean needZeroValueParam = regInfo.getZeroRegister() == null; + List params = new ArrayList<>(); + params.add(new ParameterImpl("dest", voidPtr, program)); + if (needZeroValueParam) { + params.add(new ParameterImpl("zeroValue", uintDT, program)); + } + + duffzeroFunc.updateFunction(duffzeroCC.getName(), + new ReturnParameterImpl(VoidDataType.dataType, program), params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, + SourceType.ANALYSIS); + + DWARFUtil.appendComment(program, duffzeroFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, + "Golang special function: ", "duffzero", "\n"); + } + + GoFuncData duffcopyFuncdata = goBinary.getFunctionByName("runtime.duffcopy"); + Function duffcopyFunc = duffcopyFuncdata != null + ? program.getFunctionManager().getFunctionAt(duffcopyFuncdata.getFuncAddress()) + : null; + PrototypeModel duffcopyCC = goBinary.getDuffcopyCallingConvention(); + if (duffcopyFuncdata != null && duffcopyCC != null) { + List params = List.of( + new ParameterImpl("dest", voidPtr, program), + new ParameterImpl("src", voidPtr, program)); + duffcopyFunc.updateFunction(duffcopyCC.getName(), + new ReturnParameterImpl(VoidDataType.dataType, program), params, + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + + DWARFUtil.appendComment(program, duffcopyFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT, + "Golang special function: ", "duffcopy", "\n"); + } + + } + private void markupMiscInfoStructs(Program program) { // this also adds "golang" info to program properties @@ -229,15 +310,14 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { return newMB.getStart(); } - private void setupProgramContext(GoRttiMapper programContext, MarkupSession session) + private void setupProgramContext(GoRttiMapper goBinary, MarkupSession session) throws IOException { - Program program = programContext.getProgram(); - GoRegisterInfo goRegInfo = GoRegisterInfoManager.getInstance() - .getRegisterInfoForLang(program.getLanguage(), - programContext.getGolangVersion()); + Program program = goBinary.getProgram(); + GoRegisterInfo goRegInfo = goBinary.getRegInfo(); MemoryBlock txtMemblock = program.getMemory().getBlock(".text"); - if (txtMemblock != null && goRegInfo.getZeroRegister() != null) { + if (txtMemblock != null && goRegInfo.getZeroRegister() != null && + !goRegInfo.isZeroRegisterIsBuiltin()) { try { program.getProgramContext() .setValue(goRegInfo.getZeroRegister(), txtMemblock.getStart(), @@ -248,9 +328,9 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { } } - int alignment = programContext.getPtrSize(); + int alignment = goBinary.getPtrSize(); long sizeNeeded = 0; - + Symbol zerobase = SymbolUtilities.getUniqueSymbol(program, "runtime.zerobase"); long zerobaseSymbol = sizeNeeded; sizeNeeded += zerobase == null @@ -258,13 +338,13 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { : 0; long gStructOffset = sizeNeeded; - Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class); + Structure gStruct = goBinary.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); + Structure mStruct = goBinary.getGhidraDataType("runtime.m", Structure.class); sizeNeeded += mStruct != null ? NumericUtilities.getUnsignedAlignedValue(mStruct.getLength(), alignment) : 0; @@ -304,16 +384,16 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { } } - private void createBootstrapGDT(GoRttiMapper programContext, Program program, + private void createBootstrapGDT(GoRttiMapper goBinary, Program program, TaskMonitor monitor) throws IOException { - GoVer goVer = programContext.getGolangVersion(); + GoVer goVer = goBinary.getGolangVersion(); String osName = GoRttiMapper.getGolangOSString(program); String gdtFilename = - GoRttiMapper.getGDTFilename(goVer, programContext.getPtrSize(), osName); + GoRttiMapper.getGDTFilename(goVer, goBinary.getPtrSize(), osName); gdtFilename = gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis())); File gdt = new File(System.getProperty("user.home"), gdtFilename); - programContext.exportTypesToGDT(gdt, monitor); + goBinary.exportTypesToGDT(gdt, monitor); Msg.info(this, "Golang bootstrap GDT created: " + gdt); } @@ -323,7 +403,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { @Override public boolean canAnalyze(Program program) { - return "golang".equals( + return GoConstants.GOLANG_CSPEC_NAME.equals( program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); } 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 7fe38e2dfe..6d01b34a4a 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.main"; + "main, WinMain, libc_start_main, WinMainStartup, main.main, start, entry"; public static enum StartLocationType { LOWEST_ADDRESS("Lowest Address"), 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 index 0757feb6b1..8b4cbd2eb0 100644 --- 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 @@ -21,8 +21,16 @@ import ghidra.program.model.data.CategoryPath; * Misc constant values for golang */ public class GoConstants { + public static final String GOLANG_CSPEC_NAME = "golang"; + /** * Category path to place golang types in */ public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang"); + + public static final String GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME = "abi-internal"; + public static final String GOLANG_ABI0_CALLINGCONVENTION_NAME = "abi0"; + public static final String GOLANG_DUFFZERO_CALLINGCONVENTION_NAME = "duffzero"; + public static final String GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME = "duffcopy"; } + 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 index 34a928e93e..631fc1471c 100644 --- 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 @@ -28,22 +28,24 @@ import ghidra.program.model.lang.Register; */ 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 + private final List intRegisters; + private final List floatRegisters; + private final int stackInitialOffset; + private final int maxAlign; // 4 or 8 + private final Register currentGoroutineRegister; // always points to g + private final Register zeroRegister; // always contains a zero value + private final boolean zeroRegisterIsBuiltin; // zero register is provided by cpu, or is manually set GoRegisterInfo(List intRegisters, List floatRegisters, int stackInitialOffset, int maxAlign, Register currentGoroutineRegister, - Register zeroRegister) { + Register zeroRegister, boolean zeroRegisterIsBuiltin) { this.intRegisters = intRegisters; this.floatRegisters = floatRegisters; this.stackInitialOffset = stackInitialOffset; this.maxAlign = maxAlign; this.currentGoroutineRegister = currentGoroutineRegister; this.zeroRegister = zeroRegister; + this.zeroRegisterIsBuiltin = zeroRegisterIsBuiltin; } public int getIntRegisterSize() { @@ -62,6 +64,10 @@ public class GoRegisterInfo { return zeroRegister; } + public boolean isZeroRegisterIsBuiltin() { + return zeroRegisterIsBuiltin; + } + public List getIntRegisters() { return intRegisters; } @@ -76,11 +82,11 @@ public class GoRegisterInfo { public int getAlignmentForType(DataType dt) { while (dt instanceof TypeDef || dt instanceof Array) { - if (dt instanceof TypeDef) { - dt = ((TypeDef) dt).getBaseDataType(); + if (dt instanceof TypeDef td) { + dt = td.getBaseDataType(); } - if (dt instanceof Array) { - dt = ((Array) dt).getDataType(); + if (dt instanceof Array a) { + dt = a.getDataType(); } } if (isIntType(dt) && isIntrinsicSize(dt.getLength())) { 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 index 078b679fba..ab2fd04905 100644 --- 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 @@ -37,7 +37,7 @@ import ghidra.util.xml.XmlUtilities; * <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"/> + * <zero_register register="XMM15" builtin="true|false"/> * </register_info> * <register_info versions="V1_2"> * ... @@ -115,7 +115,7 @@ public class GoRegisterInfoManager { } @SuppressWarnings("unchecked") - public Map readFrom(Element rootElem, Language lang) + private Map readFrom(Element rootElem, Language lang) throws IOException { Map result = new HashMap<>(); @@ -152,10 +152,12 @@ public class GoRegisterInfoManager { Register currentGoRoutineReg = parseRegStr(goRoutineElem.getAttributeValue("register"), lang); Register zeroReg = parseRegStr(zeroRegElem.getAttributeValue("register"), lang); + boolean zeroRegIsBuiltin = + XmlUtilities.parseOptionalBooleanAttr(zeroRegElem, "builtin", false); GoRegisterInfo registerInfo = new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign, - currentGoRoutineReg, zeroReg); + currentGoRoutineReg, zeroReg, zeroRegIsBuiltin); Map result = new HashMap<>(); for (GoVer goVer : validGoVersions) { result.put(goVer, registerInfo); @@ -165,7 +167,7 @@ public class GoRegisterInfoManager { private GoRegisterInfo getDefault(Language lang) { int goSize = lang.getInstructionAlignment(); - return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null); + return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null, false); } private List parseRegListStr(String s, Language lang) throws IOException { 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 index d39ba45f8c..e2d264597c 100644 --- 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 @@ -66,6 +66,10 @@ public class GoFunctabEntry { : null; } + public long getFuncoff() { + return funcoff; + } + private GoModuledata getModuledata() { return programContext.findContainingModuleByFuncData(context.getStructureStart()); } 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 index d381010b2a..f607fe2f80 100644 --- 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 @@ -128,6 +128,21 @@ public class GoModuledata implements StructureMarkup { return pclntable.isOffsetWithinData(offset, 1); } + /** + * Returns an artificial slice of the functab entries that are valid. + * + * @return artificial slice of the functab entries that are valid + */ + public GoSlice getFunctabEntriesSlice() { + // 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 + long sliceElementCount = ftab.getLen() > 0 ? ftab.getLen() - 1 : 0; + int entryLen = + programContext.getStructureMappingInfo(GoFunctabEntry.class).getStructureLength(); + GoSlice subSlice = ftab.getSubSlice(0, sliceElementCount, entryLen); + return subSlice; + } + public boolean isValid() { MemoryBlock txtBlock = programContext.getProgram().getMemory().getBlock(".text"); if (txtBlock != null && txtBlock.getStart().getOffset() != text) { @@ -153,6 +168,16 @@ public class GoModuledata implements StructureMarkup { return funcnametab; } + public List getAllFunctionData() throws IOException { + List functabentries = + getFunctabEntriesSlice().readList(GoFunctabEntry.class); + List result = new ArrayList<>(); + for (GoFunctabEntry functabEntry : functabentries) { + result.add(functabEntry.getFuncData()); + } + return result; + } + @Override public StructureContext getStructureContext() { return structureContext; @@ -168,15 +193,9 @@ public class GoModuledata implements StructureMarkup { markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session); markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session); - 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, session); - subSlice.markupArrayElements(GoFunctabEntry.class, session); - } + GoSlice subSlice = getFunctabEntriesSlice(); + subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false, session); + subSlice.markupArrayElements(GoFunctabEntry.class, session); Structure textsectDT = programContext.getGhidraDataType("runtime.textsect", Structure.class); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java index 69d5ae94f3..67a15279dd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java @@ -18,6 +18,7 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.Map.Entry; import java.util.stream.Collectors; import generic.jar.ResourceFile; @@ -34,9 +35,13 @@ 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.lang.PrototypeModel; +import ghidra.program.model.listing.Function; 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.SymbolType; import ghidra.util.Msg; import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; @@ -197,8 +202,15 @@ public class GoRttiMapper extends DataTypeMapper { private final Map typeNameIndex = new HashMap<>(); private final Map cachedRecoveredDataTypes = new HashMap<>(); private final List modules = new ArrayList<>(); + private Map funcdataByAddr = new HashMap<>(); + private Map funcdataByName = new HashMap<>(); private GoType mapGoType; private GoType chanGoType; + private GoRegisterInfo regInfo; + private PrototypeModel abiInternalCallingConvention; + private PrototypeModel abi0CallingConvention; + private PrototypeModel duffzeroCallingConvention; + private PrototypeModel duffcopyCallingConvention; /** * Creates a GoRttiMapper using the specified bootstrap information. @@ -259,6 +271,41 @@ public class GoRttiMapper extends DataTypeMapper { return goVersion; } + public GoRegisterInfo getRegInfo() { + return regInfo; + } + + public void init(TaskMonitor monitor) throws IOException { + initHiddenCompilerTypes(); + + this.regInfo = GoRegisterInfoManager.getInstance() + .getRegisterInfoForLang(program.getLanguage(), goVersion); + + this.abiInternalCallingConvention = program.getFunctionManager() + .getCallingConvention(GoConstants.GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME); + this.abi0CallingConvention = program.getFunctionManager() + .getCallingConvention(GoConstants.GOLANG_ABI0_CALLINGCONVENTION_NAME); + this.duffzeroCallingConvention = program.getFunctionManager() + .getCallingConvention(GoConstants.GOLANG_DUFFZERO_CALLINGCONVENTION_NAME); + this.duffcopyCallingConvention = program.getFunctionManager() + .getCallingConvention(GoConstants.GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME); + + GoModuledata firstModule = findFirstModuledata(monitor); + if (firstModule != null) { + addModule(firstModule); + } + initFuncdata(); + } + + private void initFuncdata() throws IOException { + for (GoModuledata module : modules) { + for (GoFuncData funcdata : module.getAllFunctionData()) { + funcdataByAddr.put(funcdata.getFuncAddress(), funcdata); + funcdataByName.put(funcdata.getName(), funcdata); + } + } + } + /** * Returns the first module data instance * @@ -277,6 +324,40 @@ public class GoRttiMapper extends DataTypeMapper { modules.add(module); } + public GoParamStorageAllocator getStorageAllocator() { + GoParamStorageAllocator storageAllocator = new GoParamStorageAllocator(program, goVersion); + return storageAllocator; + } + + public 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 PrototypeModel getAbi0CallingConvention() { + return abi0CallingConvention; + } + + public PrototypeModel getAbiInternalCallingConvention() { + return abiInternalCallingConvention; + } + + public PrototypeModel getDuffzeroCallingConvention() { + return duffzeroCallingConvention; + } + + public PrototypeModel getDuffcopyCallingConvention() { + return duffcopyCallingConvention; + } + /** * Finds the {@link GoModuledata} that contains the specified offset. *

@@ -415,6 +496,13 @@ public class GoRttiMapper extends DataTypeMapper { return getGoType(addr.getOffset()); } + public GoType getLastGoType() { + Optional> max = goTypes.entrySet() + .stream() + .max((o1, o2) -> o1.getKey().compareTo(o2.getKey())); + return max.isPresent() ? max.get().getValue() : null; + } + /** * Finds a go type by its go-type name, from the list of * {@link #discoverGoTypes(TaskMonitor) discovered} go types. @@ -662,33 +750,28 @@ public class GoRttiMapper extends DataTypeMapper { * @throws CancelledException if cancelled */ 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.checkCancelled(); - upwtm.setProgress(discoveredTypes.size()); - - GoType type = it.next(); - type.discoverGoTypes(discoveredTypes); - } typeNameIndex.clear(); + Set discoveredTypes = new HashSet<>(); + for (GoModuledata module : modules) { + for (Iterator it = module.iterateTypes(); it.hasNext();) { + upwtm.checkCancelled(); + upwtm.setProgress(discoveredTypes.size()); + + GoType type = it.next(); + type.discoverGoTypes(discoveredTypes); + } + } for (GoType goType : goTypes.values()) { String typeName = goType.getNameString(); typeNameIndex.put(typeName, goType); } Msg.info(this, "Found %d golang types".formatted(goTypes.size())); - initHiddenCompilerTypes(); } /** @@ -764,6 +847,18 @@ public class GoRttiMapper extends DataTypeMapper { return offset != 0 ? readStructure(GoName.class, offset) : null; } + public GoFuncData getFunctionData(Address funcAddr) throws IOException { + return funcdataByAddr.get(funcAddr); + } + + public GoFuncData getFunctionByName(String funcName) { + return funcdataByName.get(funcName); + } + + public List getAllFunctions() throws IOException { + return new ArrayList<>(funcdataByAddr.values()); + } + //-------------------------------------------------------------------------------------------- private void initHiddenCompilerTypes() { 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 index fb9131d7f6..680b34a8aa 100644 --- 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 @@ -94,6 +94,13 @@ public class GoSlice { return programContext.getDataAddress(array); } + public long getArrayEnd(Class elementClass) { + StructureMappingInfo elementSMI = + context.getDataTypeMapper().getStructureMappingInfo(elementClass); + int elementLength = elementSMI.getStructureLength(); + return array + len * elementLength; + } + public long getLen() { return len; } 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 index a5427f81c8..36ce223a2e 100644 --- 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 @@ -15,9 +15,8 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; -import java.util.Set; - import java.io.IOException; +import java.util.Set; import ghidra.app.util.bin.format.golang.rtti.GoString; import ghidra.app.util.bin.format.golang.structmapping.StructureMapping; 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 index 748ae1e166..f31ec7f527 100644 --- 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 @@ -56,6 +56,11 @@ public class GoStructType extends GoType { return fields.readList(GoStructField.class); } + @Override + public long getEndOfTypeInfo() throws IOException { + return fields.getArrayEnd(GoStructField.class); + } + @Override public void additionalMarkup(MarkupSession session) throws IOException { super.additionalMarkup(session); 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 index a1da8b9a73..c359a21cc4 100644 --- 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 @@ -92,6 +92,19 @@ public abstract class GoType implements StructureMarkup { : 0); } + /** + * Returns the location of where this type object, and any known associated optional + * structures ends. + * + * @return index location of end of this type object + * @throws IOException if error reading + */ + public long getEndOfTypeInfo() throws IOException { + return typ.hasUncommonType() + ? getUncommonType().getEndOfTypeInfo() + : context.getStructureEnd(); + } + @Markup public GoUncommonType getUncommonType() throws IOException { return typ.hasUncommonType() 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 index 0f8684fa5e..4e514466dc 100644 --- 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 @@ -15,9 +15,8 @@ */ package ghidra.app.util.bin.format.golang.rtti.types; -import java.util.List; - import java.io.IOException; +import java.util.List; import ghidra.app.util.bin.format.golang.rtti.*; import ghidra.app.util.bin.format.golang.structmapping.*; @@ -70,4 +69,18 @@ public class GoUncommonType { return slice.readList(GoMethod.class); } + /** + * Returns the location of where this object, and any known associated optional + * structures ends. + * + * @return index location of end of this type object + */ + public long getEndOfTypeInfo() { + if (mcount == 0) { + return context.getStructureEnd(); + } + GoSlice slice = getMethodsSlice(); + return slice.getArrayEnd(GoMethod.class); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/FunctionUtility.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/FunctionUtility.java index 23b353b7e1..b20294bdee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/FunctionUtility.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/FunctionUtility.java @@ -342,7 +342,7 @@ public class FunctionUtility { * @param function the function * @return true if the function has a default name. */ - static boolean isDefaultFunctionName(Function function) { + public static boolean isDefaultFunctionName(Function function) { String defaultFunctionName = SymbolUtilities.getDefaultFunctionName(function.getEntryPoint()); return defaultFunctionName.equals(function.getName()); diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.java new file mode 100644 index 0000000000..ea724859a6 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/analysis/GolangDuffFixupAnalyzer.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.plugin.core.analysis; + +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.app.cmd.comments.SetCommentCmd; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.decompiler.parallel.DecompilerCallback; +import ghidra.app.decompiler.parallel.ParallelDecompiler; +import ghidra.app.services.*; +import ghidra.app.util.bin.format.golang.GoConstants; +import ghidra.app.util.importer.MessageLog; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; +import ghidra.program.model.pcode.HighFunction; +import ghidra.program.model.pcode.PcodeBlockBasic; +import ghidra.program.model.symbol.*; +import ghidra.program.util.FunctionUtility; +import ghidra.util.Msg; +import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; + +public class GolangDuffFixupAnalyzer extends AbstractAnalyzer { + private final static String NAME = "Golang Duff Function Fixup"; + private final static String DESCRIPTION = """ + Propagates function signature information from the base runtime.duffcopy \ + and runtime.duffzero functions to the other entry points that were discovered \ + during analysis."""; + + private Program program; + private TaskMonitor monitor; + private MessageLog log; + + public GolangDuffFixupAnalyzer() { + super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); + setPriority(AnalysisPriority.FUNCTION_ANALYSIS.after()); + setDefaultEnablement(true); + } + + @Override + public boolean canAnalyze(Program program) { + return GoConstants.GOLANG_CSPEC_NAME.equals( + program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName()); + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + this.program = program; + this.monitor = monitor; + this.log = log; + + Symbol duffzeroSym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffzero"); + Function duffzeroFunc = duffzeroSym != null ? (Function) duffzeroSym.getObject() : null; + Symbol duffcopySym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffcopy"); + Function duffcopyFunc = duffcopySym != null ? (Function) duffcopySym.getObject() : null; + + List funcs = new ArrayList<>(); + if (duffzeroFunc != null && duffzeroFunc.getCallingConvention() != null) { + funcs.add(duffzeroFunc); + } + if (duffcopyFunc != null && duffcopyFunc.getCallingConvention() != null) { + funcs.add(duffcopyFunc); + } + + if (funcs.isEmpty()) { + return true; + } + + Map map = getFunctionActualRanges(funcs); + + if (duffzeroFunc != null) { + updateDuffFuncs(duffzeroFunc, map.get(duffzeroFunc.getEntryPoint())); + } + if (duffcopyFunc != null) { + updateDuffFuncs(duffcopyFunc, map.get(duffcopyFunc.getEntryPoint())); + } + + return true; + } + + /** + * Copy details from the base duff function to any other unnamed functions that start within + * the base duff function's range. + * + * @param duffFunc base duff function + * @param duffFuncBody the addresses the base function occupies + */ + private void updateDuffFuncs(Function duffFunc, AddressSetView duffFuncBody) { + if (duffFunc == null || duffFuncBody == null) { + return; + } + String duffComment = program.getListing() + .getCodeUnitAt(duffFunc.getEntryPoint()) + .getComment(CodeUnit.PLATE_COMMENT); + for (FunctionIterator funcIt = + program.getFunctionManager().getFunctions(duffFuncBody, true); funcIt.hasNext();) { + Function func = funcIt.next(); + if (!FunctionUtility.isDefaultFunctionName(func)) { + continue; + } + try { + func.setName(duffFunc.getName() + "_" + func.getEntryPoint(), SourceType.ANALYSIS); + func.updateFunction(duffFunc.getCallingConventionName(), duffFunc.getReturn(), + Arrays.asList(duffFunc.getParameters()), + FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + if (duffComment != null && !duffComment.isBlank()) { + new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment) + .applyTo(program); + } + } + catch (DuplicateNameException | InvalidInputException e) { + log.appendMsg("Error updating duff functions"); + log.appendException(e); + } + } + } + + private void configureDecompiler(DecompInterface decompiler) { + decompiler.toggleCCode(false); //only need syntax tree + decompiler.toggleSyntaxTree(true); // Produce syntax tree + decompiler.setSimplificationStyle("normalize"); + } + + record HighFunctionAddresses(Address functionEntry, AddressSetView functionAddresses) {} + + /** + * Returns the addresses that a function occupies (as determined by the decompiler instead of + * the disassembler). + * + * @param funcs list of functions + * @return map of function entry point and addresses for that function + */ + private Map getFunctionActualRanges(List funcs) { + DecompilerCallback callback = + new DecompilerCallback<>(program, this::configureDecompiler) { + @Override + public HighFunctionAddresses process(DecompileResults results, TaskMonitor tMonitor) + throws Exception { + tMonitor.checkCancelled(); + if (results == null) { + return null; + } + Function func = results.getFunction(); + HighFunction highFunc = results.getHighFunction(); + if (func == null || highFunc == null) { + return null; + } + AddressSet funcAddrs = new AddressSet(); + for (PcodeBlockBasic bb : highFunc.getBasicBlocks()) { + funcAddrs.add(bb.getStart(), bb.getStop()); + } + return new HighFunctionAddresses(func.getEntryPoint(), funcAddrs); + } + }; + + try { + List funcAddresses = + ParallelDecompiler.decompileFunctions(callback, funcs, monitor); + Map results = funcAddresses.stream() + .collect( + Collectors.toMap(hfa -> hfa.functionEntry, hfa -> hfa.functionAddresses)); + return results; + } + catch (Exception e) { + Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e); + return Map.of(); + } + finally { + callback.dispose(); + } + } + +} diff --git a/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec b/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec index c50e47e186..0087f87e83 100644 --- a/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec +++ b/Ghidra/Processors/x86/data/languages/x86-32-golang.cspec @@ -51,6 +51,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec index ab250c7b26..8196a9a68e 100644 --- a/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec +++ b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec @@ -178,6 +178,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +