From aea65727feff723e13484cd7bed1bd6510b4a6bb Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 19 Mar 2025 18:22:50 +0000 Subject: [PATCH] GP-5373 markup golang's runtime.writeBarrier flag --- .../core/analysis/GolangSymbolAnalyzer.java | 208 ++++++++++++++++-- .../bin/format/golang/rtti/GoModuledata.java | 37 +++- .../bin/format/golang/rtti/GoRttiMapper.java | 22 ++ 3 files changed, 245 insertions(+), 22 deletions(-) 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 c5f80772b5..4b941a2e56 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 @@ -26,6 +26,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.regex.Pattern; import generic.jar.ResourceFile; import ghidra.app.cmd.comments.SetCommentCmd; @@ -42,15 +43,18 @@ import ghidra.app.util.importer.MessageLog; import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.options.Options; +import ghidra.framework.store.LockException; import ghidra.program.model.address.*; import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities.ClearDataMode; import ghidra.program.model.lang.Language; 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.mem.*; import ghidra.program.model.pcode.HighFunctionDBUtil; import ghidra.program.model.symbol.*; +import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.program.util.*; import ghidra.program.util.SymbolicPropogator.Value; import ghidra.util.Msg; @@ -73,6 +77,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { AnalysisPriority.REFERENCE_ANALYSIS.after(); private static final AnalysisPriority FIX_CLOSURES_PRIORITY = PROP_RTTI_PRIORITY.after(); static final AnalysisPriority STRINGS_PRIORITY = FIX_CLOSURES_PRIORITY.after(); + private static final AnalysisPriority FIX_GCWRITEBARRIER_PRIORITY = STRINGS_PRIORITY.after(); private final static String NAME = "Golang Symbols"; private final static String DESCRIPTION = """ @@ -152,20 +157,25 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { if (analyzerOptions.fixupDuffFunctions) { fixDuffFunctions(); } - if (analyzerOptions.fixupGcWriteBarierFunctions) { + if (analyzerOptions.fixupGcWriteBarrierFunctions) { fixGcWriteBarrierFunctions(); } if (analyzerOptions.propagateRtti) { Msg.info(this, "Golang symbol analyzer: scheduling RTTI propagation after reference analysis"); - aam.schedule(new PropagateRttiBackgroundCommand(goBinary), + aam.schedule(new PropagateRttiBackgroundCommand(goBinary, markupSession), PROP_RTTI_PRIORITY.priority()); Msg.info(this, "Golang symbol analyzer: scheduling closure function fixup"); aam.schedule(new FixClosureFuncArgsBackgroundCommand(goBinary), FIX_CLOSURES_PRIORITY.priority()); } + if (analyzerOptions.fixupGcWriteBarrierFlag) { + Msg.info(this, "Golang symbol analyzer: scheduling gcWriteBarrier flag fixup"); + aam.schedule(new FixGcWriteBarrierFlagBackgroundCommand(goBinary, markupSession), + FIX_GCWRITEBARRIER_PRIORITY.priority()); + } if (analyzerOptions.createBootstrapDatatypeArchive) { createBootstrapGDT(monitor); @@ -231,10 +241,6 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { int noreturnFuncCount = 0; int functionSignatureFromBootstrap = 0; int functionSignatureFromMethod = 0; - String abiIntCCName = - goBinary.hasCallingConvention(GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME) - ? GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME - : null; List funcs = goBinary.getAllFunctions(); @@ -348,6 +354,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { .formatted(functionSignatureFromMethod)); } + private void fixGcWriteBarrierFunctions() { if (GoConstants.GCWRITE_BUFFERED_VERS.contains(goBinary.getGoVer())) { fixGcWriteBarrierBufferedFunctions(); @@ -637,6 +644,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { public FixupDuffAlternateEntryPointsBackgroundCommand(GoFuncData funcData, Function duffFunc) { + super("Golang duffzero/duffcopy fixups (deferred)", true, true, false); this.funcData = funcData; this.duffFunc = duffFunc; } @@ -685,6 +693,164 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { } //-------------------------------------------------------------------------------------------- + /* + * Sets the bytes occupied by the runtime.writeBarrier enabled flag to 0 so the decompiler + * can eliminate some boilerplate code. + * + * The runtime.writeBarrier flag is embedded inside an anon go struct defined something like + * struct { enabled bool; 3 bytes padding, etc. }, and is located in a uninitialized data + * section. + */ + private static class FixGcWriteBarrierFlagBackgroundCommand extends BackgroundCommand { + private GoRttiMapper goBinary; + private Program program; + private ReferenceManager refMgr; + private MarkupSession markupSession; + private AddressSetView searchAddrs; + + public FixGcWriteBarrierFlagBackgroundCommand(GoRttiMapper goBinary, + MarkupSession markupSession) { + super("Golang writeBarrier flag fixup (deferred)", true, true, false); + this.goBinary = goBinary; + this.markupSession = markupSession; + this.program = goBinary.getProgram(); + this.refMgr = program.getReferenceManager(); + this.searchAddrs = goBinary.getUninitializedNoPtrDataRange(); + } + + @Override + public boolean applyTo(Program obj, TaskMonitor monitor) { + Memory memory = program.getMemory(); + + Address flagAddr = getGcWriteBarrierFlagAddr(); + if (flagAddr == null) { + Msg.warn(this, "Could not find runtime.writeBarrier, unable to fixup"); + return true; + } + MemoryBlock memBlk = memory.getBlock(flagAddr); + if (!memBlk.isInitialized()) { + // split the uninitialized memory block into 3 pieces (leading, 4 byte flag, trailing) + try { + memory.split(memBlk, flagAddr); + memBlk = memory.getBlock(flagAddr); + Address afterFlag = flagAddr.add(4 /* sizeof(bool + padding) */); + memory.split(memBlk, afterFlag); + memory.convertToInitialized(memBlk, (byte) 0); + memBlk.setName(memBlk.getName().replaceFirst("\\.split$", ".writeBarrierFlag")); + memBlk = memory.getBlock(afterFlag); + memBlk.setName(memBlk.getName().replaceFirst("(\\.split)+$", ".part2")); + } + catch (MemoryBlockException | LockException | NotFoundException e) { + Msg.error(this, "Failed to fixup runtime.writeBarrier flag", e); + } + } + else { + // this codepath probably shouldn't be possible, the runtime.writeBarrier value + // typically will be in an uninit'd mem block + try { + memory.setBytes(flagAddr, new byte[4]); + } + catch (MemoryAccessException e) { + Msg.error(this, "Failed to set runtime.writeBarrier flag bytes to 0"); + } + } + // mark the 4 bytes of the flag with a data type and set it to constant mutability. + try { + Data flagData = DataUtilities.createData(program, flagAddr, + AbstractIntegerDataType.getUnsignedDataType(4, null), 4, + ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); + MutabilitySettingsDefinition.DEF.setChoice(flagData, + MutabilitySettingsDefinition.CONSTANT); + markupSession.labelAddress(flagAddr, "runtime.writeBarrier.discovered"); + } + catch (CodeUnitInsertionException | IOException e) { + Msg.error(this, "Failed to markup runtime.writeBarrier flag", e); + } + return true; + } + + private Address getGcWriteBarrierFlagAddr() { + Symbol writeBarrierSym = goBinary.getGoSymbol("runtime.writeBarrier"); + if (writeBarrierSym != null) { + return writeBarrierSym.getAddress(); + } + + // for each runtime.gcWriteBarrier*() func + // for each call ref to wb func + // iterate refs to func backwards starting at callsite (max 50'ish bytes) + // looking for data ref to global uninit addr + // followed by conditional jmp + Map candidateFlagAddrs = new HashMap<>(); + + for (Address wbFunc : getGcWriteBarrierFuncAddrs()) { + for (Reference callSiteRef : refMgr.getReferencesTo(wbFunc)) { + if (callSiteRef.getReferenceType().isCall()) { + getGcWriteBarrierFlagAddrGuesses(callSiteRef.getFromAddress(), + candidateFlagAddrs); + } + } + } + if (candidateFlagAddrs.size() > 1) { + Msg.debug(this, "runtime.writeBarrier flag candidates: " + candidateFlagAddrs); + } + + Optional> mostHits = candidateFlagAddrs.entrySet() + .stream() + .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())) + .findFirst(); + + return mostHits.isPresent() ? mostHits.get().getKey() : null; + } + + // max number of bytes to search backwards from callsites to gcWriteBarrier + private static final int MAX_REFSEARCH_BYTES = 50; + + private void getGcWriteBarrierFlagAddrGuesses(Address wbFuncCallSite, + Map candidateFlagAddrs) { + Address minAddr = wbFuncCallSite.subtract(MAX_REFSEARCH_BYTES); + + for (Address refAddr : refMgr.getReferenceSourceIterator(wbFuncCallSite, false)) { + if (refAddr.compareTo(minAddr) < 0) { + break; + } + for (Reference ref : refMgr.getReferencesFrom(refAddr)) { + if (ref.getReferenceType().isData() && ref.getReferenceType().isRead() && + ref.isMemoryReference() && searchAddrs.contains(ref.getToAddress())) { + if (hasTrailingConditionalRef(refAddr, wbFuncCallSite)) { + candidateFlagAddrs.compute(ref.getToAddress(), + (k, v) -> v == null ? 1 : v + 1); + } + } + } + } + } + + private boolean hasTrailingConditionalRef(Address addr, Address maxAddr) { + for (Address refAddr : refMgr.getReferenceSourceIterator(addr, true)) { + if (refAddr.compareTo(maxAddr) > 0) { + break; + } + for (Reference ref : refMgr.getReferencesFrom(refAddr)) { + if (ref.getReferenceType().isConditional() && ref.getReferenceType().isJump()) { + return true; + } + } + } + return false; + } + + private static final Pattern GCWRITEBARRIER_FUNCNAMES = + Pattern.compile("runtime\\.gcWriteBarrier.*"); + + private List
getGcWriteBarrierFuncAddrs() { + return goBinary.getFunctionsByNamePattern(GCWRITEBARRIER_FUNCNAMES) + .stream() + .map(GoFuncData::getFuncAddress) + .toList(); + } + + } + /** * Partially fixup closure func signatures by matching a closure func (*.func1) with a * closure struct ( struct { F uintptr; X0 blah... } ), and giving the func a context param @@ -701,6 +867,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { private int methodWrapperFuncsFixed; public FixClosureFuncArgsBackgroundCommand(GoRttiMapper goBinary) { + super("Golang closure func arg (deferred)", true, true, false); this.goBinary = goBinary; this.goTypes = goBinary.getGoTypes(); this.program = goBinary.getProgram(); @@ -920,10 +1087,11 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { int unfixedCallsiteCount; int callingFunctionCount; - public PropagateRttiBackgroundCommand(GoRttiMapper goBinary) { + public PropagateRttiBackgroundCommand(GoRttiMapper goBinary, MarkupSession markupSession) { super("Golang RTTI Propagation (deferred)", true, true, false); this.goBinary = goBinary; this.program = goBinary.getProgram(); + this.markupSession = markupSession; } @Override @@ -934,7 +1102,6 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { return true; } try { - this.markupSession = goBinary.createMarkupSession(monitor); Set>> callsiteInfo = getInformationAboutCallsites(monitor); @@ -1141,7 +1308,15 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { Fixup gcWriteBarrier functions \ (requires gcwrite calling convention defined for the program's arch)"""; - boolean fixupGcWriteBarierFunctions = true; + boolean fixupGcWriteBarrierFunctions = true; + + static final String FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME = "Fixup gcWriteBarrier Flag"; + static final String FIXUP_GCWRITEBARRIER_FLAG_DESC = """ + Fixup global writeBarrier flag so decompiler can eliminate some code paths. + The un-initialized memory block the flag is located in will be split to enable + initializing the flag byte to 0."""; + + public boolean fixupGcWriteBarrierFlag = true; void registerOptions(Options options, Program program) { options.registerOption(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME, @@ -1154,8 +1329,11 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { options.registerOption(GolangAnalyzerOptions.PROP_RTTI_OPTIONNAME, propagateRtti, null, GolangAnalyzerOptions.PROP_RTTI_DESC); options.registerOption(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_OPTIONNAME, - fixupGcWriteBarierFunctions, null, + fixupGcWriteBarrierFunctions, null, GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FUNCS_DESC); + options.registerOption(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME, + fixupGcWriteBarrierFlag, null, + GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_DESC); } void optionsChanged(Options options, Program program) { @@ -1169,8 +1347,12 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_OPTIONNAME, fixupDuffFunctions); propagateRtti = options.getBoolean(GolangAnalyzerOptions.PROP_RTTI_OPTIONNAME, propagateRtti); - fixupGcWriteBarierFunctions = options.getBoolean( - GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_OPTIONNAME, fixupGcWriteBarierFunctions); + fixupGcWriteBarrierFunctions = + options.getBoolean(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_OPTIONNAME, + fixupGcWriteBarrierFunctions); + fixupGcWriteBarrierFlag = + options.getBoolean(GolangAnalyzerOptions.FIXUP_GCWRITEBARRIER_FLAG_OPTIONNAME, + fixupGcWriteBarrierFlag); } } 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 b06b41dba3..afa45271f7 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 @@ -16,18 +16,12 @@ package ghidra.app.util.bin.format.golang.rtti; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.format.golang.structmapping.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; -import ghidra.program.model.address.AddressRangeImpl; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.StringUTF8DataType; -import ghidra.program.model.data.Structure; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; @@ -77,6 +71,18 @@ public class GoModuledata implements StructureMarkup { @FieldMapping private long edata; + @FieldMapping + private long bss; + + @FieldMapping + private long ebss; + + @FieldMapping + private long noptrbss; + + @FieldMapping + private long enoptrbss; + @FieldMapping @MarkupReference private long text; @@ -167,6 +173,18 @@ public class GoModuledata implements StructureMarkup { return new AddressRangeImpl(dataStart, dataEnd); } + public AddressRange getUninitializedDataRange() { + Address rangeStart = programContext.getCodeAddress(bss); + Address rangeEnd = programContext.getCodeAddress(ebss); + return new AddressRangeImpl(rangeStart, rangeEnd); + } + + public AddressRange getUninitializedNoPtrDataRange() { + Address rangeStart = programContext.getCodeAddress(noptrbss); + Address rangeEnd = programContext.getCodeAddress(enoptrbss); + return new AddressRangeImpl(rangeStart, rangeEnd); + } + /** * Returns the starting offset of type info * @@ -466,4 +484,5 @@ public class GoModuledata implements StructureMarkup { // the pclntab structure. return moduleData.matchesPcHeader(pcHeader) ? moduleData : null; } + } 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 29cb5be03b..d65999d9fb 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 @@ -21,6 +21,8 @@ import static ghidra.app.util.bin.format.golang.rtti.GoRttiMapper.FuncDefFlags.* import java.io.File; import java.io.IOException; import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; import generic.jar.ResourceFile; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; @@ -1371,6 +1373,18 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return funcdataByName.get(funcName); } + public List getFunctionsByNamePattern(Pattern pattern) { + List results = new ArrayList<>(); + for (Entry entry : funcdataByName.entrySet()) { + String name = entry.getKey(); + GoFuncData func = entry.getValue(); + if (pattern.matcher(name).matches()) { + results.add(func); + } + } + return results; + } + /** * Return a list of all functions * @@ -1468,6 +1482,14 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return result; } + public AddressSetView getUninitializedNoPtrDataRange() { + AddressSet result = new AddressSet(); + for (GoModuledata moduledata : modules) { + result.add(moduledata.getUninitializedNoPtrDataRange()); + } + return result; + } + public Symbol getGoSymbol(String symbolName) { return getGoSymbol(program, symbolName); }