GP-2425 more better duffzero / duffcopy function info

This commit is contained in:
dev747368 2023-06-06 16:39:26 +00:00
parent 176bdea28a
commit b5422faefb
17 changed files with 642 additions and 88 deletions

View file

@ -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.
* <p>
* 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<Variable> 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<Variable> 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());
}

View file

@ -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"),

View file

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

View file

@ -28,22 +28,24 @@ import ghidra.program.model.lang.Register;
*/
public class GoRegisterInfo {
private List<Register> intRegisters;
private List<Register> 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<Register> intRegisters;
private final List<Register> 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<Register> intRegisters, List<Register> 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<Register> 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())) {

View file

@ -37,7 +37,7 @@ import ghidra.util.xml.XmlUtilities;
* &lt;float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
* &lt;stack initialoffset="8" maxalign="8"/>
* &lt;current_goroutine register="R14"/>
* &lt;zero_register register="XMM15"/>
* &lt;zero_register register="XMM15" builtin="true|false"/>
* &lt;/register_info>
* &lt;register_info versions="V1_2">
* ...
@ -115,7 +115,7 @@ public class GoRegisterInfoManager {
}
@SuppressWarnings("unchecked")
public Map<GoVer, GoRegisterInfo> readFrom(Element rootElem, Language lang)
private Map<GoVer, GoRegisterInfo> readFrom(Element rootElem, Language lang)
throws IOException {
Map<GoVer, GoRegisterInfo> 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<GoVer, GoRegisterInfo> 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<Register> parseRegListStr(String s, Language lang) throws IOException {

View file

@ -66,6 +66,10 @@ public class GoFunctabEntry {
: null;
}
public long getFuncoff() {
return funcoff;
}
private GoModuledata getModuledata() {
return programContext.findContainingModuleByFuncData(context.getStructureStart());
}

View file

@ -128,6 +128,21 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
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<GoModuledata> {
return funcnametab;
}
public List<GoFuncData> getAllFunctionData() throws IOException {
List<GoFunctabEntry> functabentries =
getFunctabEntriesSlice().readList(GoFunctabEntry.class);
List<GoFuncData> result = new ArrayList<>();
for (GoFunctabEntry functabEntry : functabentries) {
result.add(functabEntry.getFuncData());
}
return result;
}
@Override
public StructureContext<GoModuledata> getStructureContext() {
return structureContext;
@ -168,15 +193,9 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
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);

View file

@ -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<String, GoType> typeNameIndex = new HashMap<>();
private final Map<Long, DataType> cachedRecoveredDataTypes = new HashMap<>();
private final List<GoModuledata> modules = new ArrayList<>();
private Map<Address, GoFuncData> funcdataByAddr = new HashMap<>();
private Map<String, GoFuncData> 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.
* <p>
@ -415,6 +496,13 @@ public class GoRttiMapper extends DataTypeMapper {
return getGoType(addr.getOffset());
}
public GoType getLastGoType() {
Optional<Entry<Long, GoType>> 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<Long> discoveredTypes = new HashSet<>();
for (Iterator<GoType> it = firstModule.iterateTypes(); it.hasNext();) {
upwtm.checkCancelled();
upwtm.setProgress(discoveredTypes.size());
GoType type = it.next();
type.discoverGoTypes(discoveredTypes);
}
typeNameIndex.clear();
Set<Long> discoveredTypes = new HashSet<>();
for (GoModuledata module : modules) {
for (Iterator<GoType> 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<GoFuncData> getAllFunctions() throws IOException {
return new ArrayList<>(funcdataByAddr.values());
}
//--------------------------------------------------------------------------------------------
private void initHiddenCompilerTypes() {

View file

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

View file

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

View file

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

View file

@ -92,6 +92,19 @@ public abstract class GoType implements StructureMarkup<GoType> {
: 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()

View file

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

View file

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

View file

@ -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<Function> 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<Address, AddressSetView> 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<Address, AddressSetView> getFunctionActualRanges(List<Function> funcs) {
DecompilerCallback<HighFunctionAddresses> 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<HighFunctionAddresses> funcAddresses =
ParallelDecompiler.decompileFunctions(callback, funcs, monitor);
Map<Address, AddressSetView> 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();
}
}
}

View file

@ -51,6 +51,56 @@
</unaffected>
</prototype>
</default_proto>
<prototype name="duffzero" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="4">
<register name="EDI"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
</input>
<output>
<pentry minsize="1" maxsize="4">
<register name="EDI"/>
</pentry>
</output>
<killedbycall>
<register name="EDI"/>
</killedbycall>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
</unaffected>
</prototype>
<prototype name="duffcopy" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="4">
<register name="EDI"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="ESI"/>
</pentry>
</input>
<output>
</output>
<killedbycall>
<register name="EDI"/>
<register name="ESI"/>
<register name="ECX"/>
</killedbycall>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
</unaffected>
</prototype>
<prototype name="__cdeclf" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="500" align="4">

View file

@ -178,6 +178,68 @@
</unaffected>
</prototype>
<prototype name="duffzero" extrapop="8" stackshift="8">
<input>
<pentry minsize="1" maxsize="8">
<register name="RDI"/>
</pentry>
</input>
<output>
<pentry minsize="1" maxsize="8">
<register name="RDI"/>
</pentry>
</output>
<killedbycall>
<register name="RDI"/>
</killedbycall>
<unaffected>
<register name="RSP"/>
<register name="RBP"/>
<register name="R14"/>
</unaffected>
</prototype>
<prototype name="duffcopy" extrapop="8" stackshift="8">
<input>
<pentry minsize="1" maxsize="8">
<register name="RDI"/>
</pentry>
<pentry minsize="1" maxsize="8">
<register name="RSI"/>
</pentry>
</input>
<output>
<pentry minsize="1" maxsize="8">
<register name="RDI"/>
</pentry>
<pentry minsize="9" maxsize="16">
<addr space="join" piece2="RDI" piece1="RSI"/>
</pentry>
</output>
<killedbycall>
<register name="RDI"/>
<register name="RSI"/>
</killedbycall>
<unaffected>
<register name="RAX"/>
<register name="RBX"/>
<register name="RCX"/>
<register name="RDI"/>
<register name="RSI"/>
<register name="R8"/>
<register name="R9"/>
<register name="R10"/>
<register name="R11"/>
<register name="RSP"/>
<register name="RBP"/>
<register name="R14"/>
</unaffected>
</prototype>
<prototype name="__stdcall" extrapop="8" stackshift="8">
<!-- Derived from "System V Application Binary Interface AMD64 Architecture Processor Supplement" April 2016 -->
<input>