GP-4482 Golang 1.16 + 1.15

This commit is contained in:
dev747368 2023-08-30 15:44:37 +00:00
parent db608a1a13
commit 560d5691a7
32 changed files with 851 additions and 374 deletions

View file

@ -87,6 +87,8 @@ data/symbols/win64/mfc90u.exports||GHIDRA||||END|
data/symbols/win64/msvcrt.hints||GHIDRA||||END|
data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END|
data/typeinfo/generic/generic_clib_64.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.15_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.16_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.17_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END|

View file

@ -18,11 +18,10 @@
// specified for the arch.
//@category Functions
//@menupath Tools.Fix Golang Function Param Storage
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.format.golang.GoFunctionFixup;
import ghidra.app.util.bin.format.golang.GoVer;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.program.model.listing.Function;
public class FixupGolangFuncParamStorageScript extends GhidraScript {
@ -34,10 +33,9 @@ public class FixupGolangFuncParamStorageScript extends GhidraScript {
return;
}
GoVer goVersion = GoVer.fromProgramProperties(currentProgram);
if ( goVersion == GoVer.UNKNOWN ) {
List<GoVer> versions = List.of(GoVer.values());
goVersion =
askChoice("Golang Version", "What is the golang version?", versions, GoVer.UNKNOWN);
if (goVersion == GoVer.INVALID) {
goVersion = askChoice("Golang Version", "What is the golang version?",
GoRttiMapper.getAllSupportedVersions(), GoVer.INVALID);
}
println("Fixing param storage for function %s@%s".formatted(func.getName(),
func.getEntryPoint()));

View file

@ -177,8 +177,6 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
}
private void markupWellknownSymbols() throws IOException {
Program program = goBinary.getProgram();
Symbol g0 = goBinary.getGoSymbol("runtime.g0");
Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class);
if (g0 != null && gStruct != null) {
@ -317,31 +315,25 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
Program program = goBinary.getProgram();
GoRegisterInfo regInfo = goBinary.getRegInfo();
DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType);
DataType uintDT = goBinary.getTypeOrDefault("uint", DataType.class,
AbstractIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null));
GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero");
Function duffzeroFunc = duffzeroFuncdata != null
? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress())
: null;
if (duffzeroFunc != null &&
goBinary.hasCallingConvention(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME)) {
List<Variable> duffzeroParams = regInfo.getDuffzeroParams(program);
if (duffzeroFunc != null && !duffzeroParams.isEmpty()) {
// NOTE: some go archs don't create duffzero functions. See
// cmd/compile/internal/ssa/config.go and look for flag noDuffDevice in each arch.
try {
// 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(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME,
new ReturnParameterImpl(VoidDataType.dataType, program), params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
// NOTE: even though we are specifying custom storage for the arguments, the
// calling convention name is still important as it tells the decompiler which
// registers are unaffected vs killed-by-call
ReturnParameterImpl voidRet = new ReturnParameterImpl(VoidDataType.dataType,
VariableStorage.VOID_STORAGE, program);
duffzeroFunc.updateFunction(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME, voidRet,
duffzeroParams, FunctionUpdateType.CUSTOM_STORAGE, true, SourceType.ANALYSIS);
markupSession.appendComment(duffzeroFunc, null,
"Golang special function: duffzero");
@ -527,6 +519,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
String duffComment = program.getListing()
.getCodeUnitAt(duffFunc.getEntryPoint())
.getComment(CodeUnit.PLATE_COMMENT);
monitor.setMessage("Fixing alternate duffzero/duffcopy entry points");
for (FunctionIterator funcIt =
program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) {
@ -538,9 +531,11 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
func.setName(duffFunc.getName() + "_" + func.getEntryPoint(),
SourceType.ANALYSIS);
func.setParentNamespace(funcNS);
FunctionUpdateType fut = duffFunc.hasCustomVariableStorage()
? FunctionUpdateType.CUSTOM_STORAGE
: FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS;
func.updateFunction(ccName, duffFunc.getReturn(),
Arrays.asList(duffFunc.getParameters()),
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
Arrays.asList(duffFunc.getParameters()), fut, true, SourceType.ANALYSIS);
if (duffComment != null && !duffComment.isBlank()) {
new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment)
.applyTo(program);

View file

@ -415,7 +415,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public short readNextShort() throws IOException {
short s = readShort(currentIndex);
return readNextShort(converter);
}
/**
* Reads the short at the current index and then increments the current
* index by <code>SIZEOF_SHORT</code>.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the short at the current index
* @exception IOException if an I/O error occurs
*/
public short readNextShort(DataConverter dc) throws IOException {
short s = readShort(dc, currentIndex);
currentIndex += SIZEOF_SHORT;
return s;
}
@ -427,7 +439,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public int readNextUnsignedShort() throws IOException {
return Short.toUnsignedInt(readNextShort());
return Short.toUnsignedInt(readNextShort(converter));
}
/**
* Reads the unsigned short at the current index and then increments the current
* index by <code>SIZEOF_SHORT</code>.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the unsigned short at the current index, as an int
* @exception IOException if an I/O error occurs
*/
public int readNextUnsignedShort(DataConverter dc) throws IOException {
return Short.toUnsignedInt(readNextShort(dc));
}
/**
@ -437,7 +461,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public int readNextInt() throws IOException {
int i = readInt(currentIndex);
return readNextInt(converter);
}
/**
* Reads the integer at the current index and then increments the current
* index by <code>SIZEOF_INT</code>.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the integer at the current index
* @exception IOException if an I/O error occurs
*/
public int readNextInt(DataConverter dc) throws IOException {
int i = readInt(dc, currentIndex);
currentIndex += SIZEOF_INT;
return i;
}
@ -449,7 +485,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public long readNextUnsignedInt() throws IOException {
return Integer.toUnsignedLong(readNextInt());
return Integer.toUnsignedLong(readNextInt(converter));
}
/**
* Reads the unsigned integer at the current index and then increments the current
* index by <code>SIZEOF_INT</code>.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the unsigned integer at the current index, as a long
* @exception IOException if an I/O error occurs
*/
public long readNextUnsignedInt(DataConverter dc) throws IOException {
return Integer.toUnsignedLong(readNextInt(dc));
}
/**
@ -459,7 +507,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public long readNextLong() throws IOException {
long l = readLong(currentIndex);
return readNextLong(converter);
}
/**
* Reads the long at the current index and then increments the current
* index by <code>SIZEOF_LONG</code>.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the long at the current index
* @exception IOException if an I/O error occurs
*/
public long readNextLong(DataConverter dc) throws IOException {
long l = readLong(dc, currentIndex);
currentIndex += SIZEOF_LONG;
return l;
}
@ -469,10 +529,22 @@ public class BinaryReader {
*
* @param len the number of bytes that the integer occupies, 1 to 8
* @return value of requested length, with sign bit extended, in a long
* @throws IOException
* @exception IOException if an I/O error occurs
*/
public long readNextValue(int len) throws IOException {
long result = readValue(currentIndex, len);
return readNextValue(converter, len);
}
/**
* Returns the signed value of the integer (of the specified length) at the current index.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param len the number of bytes that the integer occupies, 1 to 8
* @return value of requested length, with sign bit extended, in a long
* @exception IOException if an I/O error occurs
*/
public long readNextValue(DataConverter dc, int len) throws IOException {
long result = readValue(dc, currentIndex, len);
currentIndex += len;
return result;
}
@ -482,10 +554,22 @@ public class BinaryReader {
*
* @param len the number of bytes that the integer occupies, 1 to 8
* @return unsigned value of requested length, in a long
* @throws IOException
* @exception IOException if an I/O error occurs
*/
public long readNextUnsignedValue(int len) throws IOException {
long result = readUnsignedValue(currentIndex, len);
return readNextUnsignedValue(converter, len);
}
/**
* Returns the unsigned value of the integer (of the specified length) at the current index.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param len the number of bytes that the integer occupies, 1 to 8
* @return unsigned value of requested length, in a long
* @exception IOException if an I/O error occurs
*/
public long readNextUnsignedValue(DataConverter dc, int len) throws IOException {
long result = readUnsignedValue(dc, currentIndex, len);
currentIndex += len;
return result;
}
@ -684,7 +768,25 @@ public class BinaryReader {
* @throws InvalidDataException if value can not be held in a java integer
*/
public int readNextUnsignedIntExact() throws IOException, InvalidDataException {
long i = readNextUnsignedInt();
return readNextUnsignedIntExact(converter);
}
/**
* Reads an unsigned int32 value, and returns it as a java int (instead of a java long).
* <p>
* If the value is outside the range of 0..Integer.MAX_VALUE, an InvalidDataException is thrown.
* <p>
* Useful for reading uint32 values that are going to be used in java to allocate arrays or
* other similar cases where the value must be a java integer.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @return the uint32 value read from the stream, if it fits into the range [0..MAX_VALUE]
* of a java integer
* @throws IOException if there was an error reading
* @throws InvalidDataException if value can not be held in a java integer
*/
public int readNextUnsignedIntExact(DataConverter dc) throws IOException, InvalidDataException {
long i = readNextUnsignedInt(dc);
ensureInt32u(i);
return (int) i;
}
@ -908,8 +1010,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public short readShort(long index) throws IOException {
return readShort(converter, index);
}
/**
* Returns the signed SHORT at <code>index</code>.
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index the index where the SHORT begins
* @return the signed SHORT
* @exception IOException if an I/O error occurs
*/
public short readShort(DataConverter dc, long index) throws IOException {
byte[] bytes = provider.readBytes(index, SIZEOF_SHORT);
return converter.getShort(bytes);
return dc.getShort(bytes);
}
/**
@ -919,7 +1032,18 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public int readUnsignedShort(long index) throws IOException {
return Short.toUnsignedInt(readShort(index));
return Short.toUnsignedInt(readShort(converter, index));
}
/**
* Returns the unsigned SHORT at <code>index</code>.
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index the index where the SHORT begins
* @return the unsigned SHORT as an int
* @exception IOException if an I/O error occurs
*/
public int readUnsignedShort(DataConverter dc, long index) throws IOException {
return Short.toUnsignedInt(readShort(dc, index));
}
/**
@ -929,8 +1053,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public int readInt(long index) throws IOException {
return readInt(converter, index);
}
/**
* Returns the signed INTEGER at <code>index</code>.
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index the index where the INTEGER begins
* @return the signed INTEGER
* @exception IOException if an I/O error occurs
*/
public int readInt(DataConverter dc, long index) throws IOException {
byte[] bytes = provider.readBytes(index, SIZEOF_INT);
return converter.getInt(bytes);
return dc.getInt(bytes);
}
/**
@ -940,7 +1075,18 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public long readUnsignedInt(long index) throws IOException {
return Integer.toUnsignedLong(readInt(index));
return Integer.toUnsignedLong(readInt(converter, index));
}
/**
* Returns the unsigned INTEGER at <code>index</code>.
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index the index where the INTEGER begins
* @return the unsigned INTEGER as a long
* @exception IOException if an I/O error occurs
*/
public long readUnsignedInt(DataConverter dc, long index) throws IOException {
return Integer.toUnsignedLong(readInt(dc, index));
}
/**
@ -950,8 +1096,19 @@ public class BinaryReader {
* @exception IOException if an I/O error occurs
*/
public long readLong(long index) throws IOException {
return readLong(converter, index);
}
/**
* Returns the signed LONG at <code>index</code>.
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index the index where the LONG begins
* @return the LONG
* @exception IOException if an I/O error occurs
*/
public long readLong(DataConverter dc, long index) throws IOException {
byte[] bytes = provider.readBytes(index, SIZEOF_LONG);
return converter.getLong(bytes);
return dc.getLong(bytes);
}
/**
@ -960,11 +1117,24 @@ public class BinaryReader {
* @param index where the value begins
* @param len the number of bytes that the integer occupies, 1 to 8
* @return value of requested length, with sign bit extended, in a long
* @throws IOException
* @exception IOException if an I/O error occurs
*/
public long readValue(long index, int len) throws IOException {
return readValue(converter, index, len);
}
/**
* Returns the signed value of the integer (of the specified length) at the specified offset.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index where the value begins
* @param len the number of bytes that the integer occupies, 1 to 8
* @return value of requested length, with sign bit extended, in a long
* @exception IOException if an I/O error occurs
*/
public long readValue(DataConverter dc, long index, int len) throws IOException {
byte[] bytes = provider.readBytes(index, len);
return converter.getSignedValue(bytes, len);
return dc.getSignedValue(bytes, len);
}
/**
@ -973,11 +1143,24 @@ public class BinaryReader {
* @param index where the value begins
* @param len the number of bytes that the integer occupies, 1 to 8
* @return unsigned value of requested length, in a long
* @throws IOException
* @exception IOException if an I/O error occurs
*/
public long readUnsignedValue(long index, int len) throws IOException {
return readUnsignedValue(converter, index, len);
}
/**
* Returns the unsigned value of the integer (of the specified length) at the specified offset.
*
* @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE}
* @param index where the value begins
* @param len the number of bytes that the integer occupies, 1 to 8
* @return unsigned value of requested length, in a long
* @exception IOException if an I/O error occurs
*/
public long readUnsignedValue(DataConverter dc, long index, int len) throws IOException {
byte[] bytes = provider.readBytes(index, len);
return converter.getValue(bytes, len);
return dc.getValue(bytes, len); // NOTE: getValue() is unsigned so this is all good
}
/**

View file

@ -1201,6 +1201,7 @@ public class DWARFProgram implements Closeable {
private DWARFLocationList readLocationList(DWARFNumericAttribute loclistAttr,
DWARFCompilationUnit cu) throws IOException {
try {
switch (loclistAttr.getAttributeForm()) {
case DW_FORM_sec_offset:
int dwarfVer = cu.getDWARFVersion();
@ -1221,6 +1222,12 @@ public class DWARFProgram implements Closeable {
default:
break; // fallthru to throw
}
}
catch (IOException | IllegalArgumentException e) {
throw new IOException(
"Failed to read location list specified by %s".formatted(loclistAttr.toString()),
e);
}
throw new IOException(
"Unsupported loclist form %s".formatted(loclistAttr.getAttributeForm()));
}

View file

@ -43,7 +43,7 @@ public class DWARFRange implements Comparable<DWARFRange> {
public DWARFRange(long start, long end) {
if (Long.compareUnsigned(end, start) < 0) {
throw new IllegalArgumentException(
"Range max (%d) cannot be less than min (%d).".formatted(end, start));
"Range max (%x) cannot be less than min (%x).".formatted(end, start));
}
this.start = start;
this.end = end;

View file

@ -189,7 +189,7 @@ public class GoBuildInfo implements ElfInfoItem {
return version;
}
public GoVer getVerEnum() {
public GoVer getGoVer() {
return GoVer.parse(version);
}

View file

@ -15,11 +15,17 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.InvalidInputException;
/**
* Immutable information about registers, alignment sizes, etc needed to allocate storage
@ -28,6 +34,8 @@ import ghidra.program.model.lang.Register;
*/
public class GoRegisterInfo {
public enum RegType { INT, FLOAT }
private final List<Register> intRegisters;
private final List<Register> floatRegisters;
private final int stackInitialOffset;
@ -36,9 +44,14 @@ public class GoRegisterInfo {
private final Register zeroRegister; // always contains a zero value
private final boolean zeroRegisterIsBuiltin; // zero register is provided by cpu, or is manually set
private final Register duffzeroDestParam;
private final Register duffzeroZeroParam; // if duffzero has 2nd param
private final RegType duffzeroZeroParamType;
GoRegisterInfo(List<Register> intRegisters, List<Register> floatRegisters,
int stackInitialOffset, int maxAlign, Register currentGoroutineRegister,
Register zeroRegister, boolean zeroRegisterIsBuiltin) {
Register zeroRegister, boolean zeroRegisterIsBuiltin, Register duffzeroDestParam,
Register duffzeroZeroParam, RegType duffzeroZeroParamType) {
this.intRegisters = intRegisters;
this.floatRegisters = floatRegisters;
this.stackInitialOffset = stackInitialOffset;
@ -46,6 +59,10 @@ public class GoRegisterInfo {
this.currentGoroutineRegister = currentGoroutineRegister;
this.zeroRegister = zeroRegister;
this.zeroRegisterIsBuiltin = zeroRegisterIsBuiltin;
this.duffzeroDestParam = duffzeroDestParam;
this.duffzeroZeroParam = duffzeroZeroParam;
this.duffzeroZeroParamType = duffzeroZeroParamType;
}
public int getIntRegisterSize() {
@ -80,6 +97,45 @@ public class GoRegisterInfo {
return stackInitialOffset;
}
public List<Variable> getDuffzeroParams(Program program) {
if (duffzeroDestParam == null) {
return List.of();
}
try {
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
DataType voidPtr = dtm.getPointer(VoidDataType.dataType);
List<Variable> params = new ArrayList<>();
params.add(new ParameterImpl("dest", Parameter.UNASSIGNED_ORDINAL, voidPtr,
getStorageForReg(program, duffzeroDestParam, voidPtr.getLength()), true, program,
SourceType.ANALYSIS));
if (duffzeroZeroParam != null && duffzeroZeroParamType != null) {
int regSize = duffzeroZeroParam.getMinimumByteSize();
DataType dt = switch (duffzeroZeroParamType) {
case FLOAT -> AbstractFloatDataType.getFloatDataType(regSize, dtm);
case INT -> AbstractIntegerDataType.getUnsignedDataType(regSize, dtm);
};
params.add(new ParameterImpl("zeroValue", Parameter.UNASSIGNED_ORDINAL, dt,
getStorageForReg(program, duffzeroZeroParam, regSize), true, program,
SourceType.ANALYSIS));
}
return params;
}
catch (InvalidInputException e) {
return List.of();
}
}
private VariableStorage getStorageForReg(Program program, Register reg, int len)
throws InvalidInputException {
return new VariableStorage(program,
DWARFUtil.convertRegisterListToVarnodeStorage(List.of(reg), len)
.toArray(Varnode[]::new));
}
public int getAlignmentForType(DataType dt) {
while (dt instanceof TypeDef || dt instanceof Array) {
if (dt instanceof TypeDef td) {

View file

@ -24,6 +24,7 @@ import org.jdom.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
import ghidra.app.util.bin.format.golang.GoRegisterInfo.RegType;
import ghidra.program.model.lang.*;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
@ -32,12 +33,13 @@ import ghidra.util.xml.XmlUtilities;
* XML config file format:
* <pre>
* &lt;golang>
* &lt;register_info versions="V1_17,V1_18">
* &lt;register_info versions="V1_17,V1_18,1.20,1.21"> // or "all"
* &lt;int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
* &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" builtin="true|false"/>
* &lt;duffzero dest="RDI" zero_arg="XMM0" zero_type="float|int"/>
* &lt;/register_info>
* &lt;register_info versions="V1_2">
* ...
@ -65,23 +67,37 @@ public class GoRegisterInfoManager {
* returned that forces all parameters to be stack allocated.
*
* @param lang {@link Language}
* @param goVersion {@link GoVer} enum
* @param goVer {@link GoVer}
* @return {@link GoRegisterInfo}, never null
*/
public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVersion) {
public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVer) {
Map<GoVer, GoRegisterInfo> perVersionRegInfos =
cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang));
GoRegisterInfo registerInfo = perVersionRegInfos.get(goVersion);
GoRegisterInfo registerInfo = getMatchingRegisterInfo(perVersionRegInfos, goVer);
if (registerInfo == null) {
registerInfo = getDefault(lang);
perVersionRegInfos.put(goVersion, registerInfo);
perVersionRegInfos.put(goVer, registerInfo);
int goSize = lang.getInstructionAlignment();
Msg.warn(this, "Missing Golang register info for: " + lang.getLanguageID() +
", defaulting to abi0, size=" + goSize);
Msg.warn(this, "Missing Golang register info for: %s, defaulting to abi0, size=%d"
.formatted(lang.getLanguageID(), goSize));
}
return registerInfo;
}
private GoRegisterInfo getMatchingRegisterInfo(Map<GoVer, GoRegisterInfo> mappedRegInfo, GoVer goVer) {
GoRegisterInfo result = mappedRegInfo.get(goVer);
if ( result == null ) {
result = mappedRegInfo.entrySet()
.stream()
.filter(e -> e.getKey().isWildcard())
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
return result;
}
private Map<GoVer, GoRegisterInfo> loadRegisterInfo(Language lang) {
try {
ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME);
@ -136,8 +152,9 @@ public class GoRegisterInfoManager {
Element stackElem = regInfoElem.getChild("stack");
Element goRoutineElem = regInfoElem.getChild("current_goroutine");
Element zeroRegElem = regInfoElem.getChild("zero_register");
Element duffZeroElem = regInfoElem.getChild("duffzero");
if (intRegsElem == null || floatRegsElem == null || stackElem == null ||
goRoutineElem == null || zeroRegElem == null) {
goRoutineElem == null || zeroRegElem == null || duffZeroElem == null) {
throw new IOException("Bad format");
}
@ -155,9 +172,13 @@ public class GoRegisterInfoManager {
boolean zeroRegIsBuiltin =
XmlUtilities.parseOptionalBooleanAttr(zeroRegElem, "builtin", false);
GoRegisterInfo registerInfo =
new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign,
currentGoRoutineReg, zeroReg, zeroRegIsBuiltin);
Register duffzeroDest = parseRegStr(duffZeroElem.getAttributeValue("dest"), lang);
Register duffzeroZero = parseRegStr(duffZeroElem.getAttributeValue("zero_arg"), lang);
RegType duffzeroZeroType = parseRegTypeStr(duffZeroElem.getAttributeValue("zero_type"));
GoRegisterInfo registerInfo = new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset,
maxAlign, currentGoRoutineReg, zeroReg, zeroRegIsBuiltin, duffzeroDest, duffzeroZero,
duffzeroZeroType);
Map<GoVer, GoRegisterInfo> result = new HashMap<>();
for (GoVer goVer : validGoVersions) {
result.put(goVer, registerInfo);
@ -167,7 +188,8 @@ public class GoRegisterInfoManager {
private GoRegisterInfo getDefault(Language lang) {
int goSize = lang.getInstructionAlignment();
return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null, false);
return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null, false, null,
null, RegType.INT);
}
private List<Register> parseRegListStr(String s, Language lang) throws IOException {
@ -196,26 +218,33 @@ public class GoRegisterInfoManager {
return register;
}
private Set<GoVer> parseValidGoVersionsStr(String s) throws IOException {
if (s.trim().equalsIgnoreCase("all")) {
EnumSet<GoVer> allVers = EnumSet.allOf(GoVer.class);
allVers.remove(GoVer.UNKNOWN);
return allVers;
private RegType parseRegTypeStr(String s) {
return switch (Objects.requireNonNullElse(s, "int").toLowerCase()) {
default -> RegType.INT;
case "float" -> RegType.FLOAT;
};
}
EnumSet<GoVer> result = EnumSet.noneOf(GoVer.class);
private Set<GoVer> parseValidGoVersionsStr(String s) throws IOException {
if (s.trim().equalsIgnoreCase("all")) {
return Set.of(GoVer.ANY);
}
Set<GoVer> result = new HashSet<>();
for (String verStr : s.split(",")) {
verStr = verStr.trim();
if (verStr.isEmpty()) {
continue;
}
try {
GoVer ver = GoVer.valueOf(verStr);
result.add(ver);
if (verStr.startsWith("V")) {
verStr = verStr.substring(1).replace('_', '.'); // convert "V1_1" -> "1.1"
}
catch (IllegalArgumentException e) {
GoVer ver = GoVer.parse(verStr);
if (ver.isInvalid()) {
throw new IOException("Unknown go version: " + verStr);
}
result.add(ver);
}
return result;
}

View file

@ -15,31 +15,40 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.Objects;
import ghidra.framework.options.Options;
import ghidra.program.model.listing.Program;
/**
* Golang version numbers
*/
public enum GoVer {
UNKNOWN(0, 0),
V1_2(1, 2),
V1_16(1, 16),
V1_17(1, 17),
V1_18(1, 18),
V1_19(1, 19),
V1_20(1, 20),
V1_21(1, 21),
V1_22(1, 22);
public class GoVer implements Comparable<GoVer> {
public static final GoVer INVALID = new GoVer(0, 0);
public static final GoVer ANY = new GoVer(-1, -1);
// a couple of well-known versions that are re-used in a few places
public static final GoVer V1_2 = new GoVer(1, 2);
public static final GoVer V1_16 = new GoVer(1, 16);
public static final GoVer V1_17 = new GoVer(1, 17);
public static final GoVer V1_18 = new GoVer(1, 18);
private final int major;
private final int minor;
GoVer(int major, int minor) {
public GoVer(int major, int minor) {
this.major = major;
this.minor = minor;
}
public boolean isInvalid() {
return major == 0 && minor == 0;
}
public boolean isWildcard() {
return major == -1 && minor == -1;
}
/**
* Major value
*
@ -58,6 +67,15 @@ public enum GoVer {
return minor;
}
@Override
public int compareTo(GoVer o) {
int result = Integer.compare(major, o.major);
if (result == 0) {
result = Integer.compare(minor, o.minor);
}
return result;
}
/**
* Compares this version to the specified other version and returns true if this version
* is greater than or equal to the other version.
@ -66,45 +84,75 @@ public enum GoVer {
* @return true if this version is gte other version
*/
public boolean isAtLeast(GoVer otherVersion) {
return this.ordinal() >= otherVersion.ordinal();
return compareTo(otherVersion) >= 0;
}
/**
* Parses a version string ("1.2") and returns the matching GoVer enum instance, or
* UNKNOWN if no matching version or bad data.
* Returns true if this version is between the specified min and max versions (inclusive).
*
* @param min minimum version to allow (inclusive)
* @param max maximum version to allow (inclusive)
* @return boolean true if this version is between the specified min and max versions
*/
public boolean inRange(GoVer min, GoVer max) {
return min.compareTo(this) <= 0 && this.compareTo(max) <= 0;
}
/**
* Parses a version string ("1.2") and returns a GoVer instance, or
* INVALID if no matching version or bad data.
*
* @param s string to parse
* @return GoVer enum instance, or UNKNOWN
* @return GoVer instance, or INVALID
*/
public static GoVer parse(String s) {
String[] parts = s.split("\\.");
String[] parts = Objects.requireNonNullElse(s, "").split("\\.");
if (parts.length < 2) {
return UNKNOWN;
return INVALID;
}
try {
int major = Integer.parseInt(parts[0]);
int minor = Integer.parseInt(parts[1]);
for (GoVer ver : values()) {
if (ver.major == major && ver.minor == minor) {
return ver;
}
}
//don't care about patch level right now
return new GoVer(major, minor);
}
catch (NumberFormatException e) {
// fall thru, return unknown
}
return UNKNOWN;
return INVALID;
}
public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version";
public static GoVer fromProgramProperties(Program program) {
Options props = program.getOptions(Program.PROGRAM_INFO);
String verStr = props.getString(GOLANG_VERSION_PROPERTY_NAME, null);
return verStr != null ? parse(verStr) : UNKNOWN;
return parse(verStr);
}
public static void setProgramPropertiesWithOriginalVersionString(Options props, String s) {
props.setString(GOLANG_VERSION_PROPERTY_NAME, s);
}
@Override
public int hashCode() {
return Objects.hash(major, minor);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GoVer)) {
return false;
}
GoVer other = (GoVer) obj;
return major == other.major && minor == other.minor;
}
@Override
public String toString() {
return "%d.%d".formatted(major, minor);
}
}

View file

@ -22,7 +22,8 @@ import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
@ -39,19 +40,19 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
@ContextField
private StructureContext<GoFuncData> context;
@FieldMapping(optional = true, fieldName = { "entryoff", "entryOff" })
@FieldMapping(presentWhen = "1.18+", fieldName = { "entryoff", "entryOff" })
@EOLComment("getDescription")
@MarkupReference("getFuncAddress")
private long entryoff; // valid in >=1.18, relative offset of function
private long entryoff; // relative offset of function
@FieldMapping(optional = true)
@FieldMapping(presentWhen = "-1.17")
@EOLComment("getDescription")
@MarkupReference("getFuncAddress")
private long entry; // valid in <=1.17, location of function
private long entry; // absolute location of function
@FieldMapping(fieldName = { "nameoff", "nameOff" })
@MarkupReference("getNameAddress")
private long nameoff;
private long nameoff; // uint32
//private long args; // size of arguments
@ -64,14 +65,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
@FieldMapping
private int npcdata; // number of elements in varlen pcdata array
@FieldMapping
private long cuOffset;
@FieldMapping(presentWhen = "1.16+")
private long cuOffset = -1;
@FieldMapping
@EOLComment("getFuncIDEnum")
private byte funcID; // see GoFuncID enum
@FieldMapping
@FieldMapping(presentWhen = "1.17+")
@EOLComment("flags")
private byte flag; // runtime.funcFlag, see GoFuncFlag enum
@ -130,13 +131,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
// using the max pc value
try {
long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1;
return max > entry
? new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max))
: null;
if (max > entry) {
return new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max));
}
}
catch (IOException e) {
return new AddressRangeImpl(getFuncAddress(), getFuncAddress());
// fall thru, return 1-byte range
}
return new AddressRangeImpl(funcAddress, funcAddress);
}
/**
@ -228,7 +230,7 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
* </ul>
* Return value information is unknown and always represented as an "undefined" data type.
*
* @return pseduo-function signature string, such as "undefined foo( 8, 8 )" which would
* @return pseudo-function signature string, such as "undefined foo( 8, 8 )" which would
* indicate the function had 2 8-byte arguments
* @throws IOException if error reading lookup data
*/
@ -237,18 +239,6 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
return sig.toString();
}
/**
* Attempts to return a {@link FunctionDefinition} for this function, based on this
* function's inclusion in a golang interface as a method.
*
* @return {@link FunctionDefinition}
* @throws IOException if error
*/
public FunctionDefinition findMethodSignature() throws IOException {
MethodInfo methodInfo = findMethodInfo();
return methodInfo != null ? methodInfo.getSignature() : null;
}
/**
* Attempts to return a {@link GoMethodInfo} for this function, based on this
* function's inclusion in a golang interface as a method.
@ -275,9 +265,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
*/
public Address getNameAddress() {
GoModuledata moduledata = getModuledata();
return moduledata != null
? moduledata.getFuncnametab().getArrayAddress().add(nameoff)
: null;
if (moduledata != null) {
GoSlice slice = moduledata.getFuncnametab();
if (slice == null) {
slice = moduledata.getPclntable();
}
return slice.getArrayAddress().add(nameoff);
}
return null;
}
/**
@ -287,16 +282,18 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
*/
public String getName() {
GoModuledata moduledata = getModuledata();
try {
if (moduledata != null) {
return programContext
.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff)
.readNextUtf8String();
try {
GoSlice slice = moduledata.getFuncnametab();
if (slice == null) {
slice = moduledata.getPclntable();
}
return slice.getElementReader(1, (int) nameoff).readNextUtf8String();
}
catch (IOException e) {
// fall thru
}
}
return "unknown_func_%x_%s".formatted(context.getStructureStart(),
funcAddress != null ? funcAddress : "missing_addr");
}
@ -369,11 +366,25 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry);
int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry);
long fileoff = fileno >= 0
? moduledata.getCutab()
.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno)
: -1;
String fileName = fileoff != -1 ? moduledata.getFilename(fileoff) : null;
if (fileno < 0) {
return null;
}
long fileoff;
GoSlice cutab = moduledata.getCutab();
GoSlice filetab = moduledata.getFiletab();
GoSlice nameSlice;
if (cutab == null) { // when <= 1.15
fileoff = filetab.readUIntElement(4 /*sizeof(uint32*/, fileno);
nameSlice = moduledata.getPclntable();
}
else { // when >= 1.16
fileoff = cutab.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno);
nameSlice = filetab;
}
String fileName = fileoff >= 0 // -1 == no value
? nameSlice.getElementReader(1, (int) fileoff).readNextUtf8String()
: null;
return fileName != null ? new GoSourceFileInfo(fileName, lineNum) : null;
}
@ -579,25 +590,3 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
}
}
/*
struct runtime._func
Length: 40 Alignment: 4
{
uint32 entryoff
int32 nameoff
int32 args
uint32 deferreturn
uint32 pcsp
uint32 pcfile
uint32 pcln
uint32 npcdata
uint32 cuOffset
runtime.funcID funcID
runtime.funcFlag flag
uint8[1] _
uint8 nfuncdata
} pack()
int32[] pcdata
int32[] funcdata
*/

View file

@ -32,13 +32,13 @@ public class GoFunctabEntry {
@ContextField
private StructureContext<GoFunctabEntry> context;
@FieldMapping(optional = true)
@FieldMapping(presentWhen = "1.18+")
@MarkupReference("getFuncAddress")
private long entryoff; // valid in >=1.18, relative offset of function
private long entryoff; // relative offset of function
@FieldMapping(optional = true)
@FieldMapping(presentWhen = "-1.17")
@MarkupReference("getFuncAddress")
private long entry; // valid in <=1.17, location of function
private long entry; // absolute location of function
@FieldMapping
@MarkupReference("getFuncData")

View file

@ -44,9 +44,27 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
@ContextField
private StructureContext<GoModuledata> structureContext;
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference
private long pcHeader; // pointer to the GoPcHeader instance, useful for bootstrapping
private long pcHeader; // pointer to the GoPcHeader instance, useful for bootstrapping. when ver >= 1.16, this is first field
@FieldMapping(presentWhen = "1.16+")
private GoSlice funcnametab; // []uint8 blob of null term strings
@FieldMapping(presentWhen = "1.16+")
private GoSlice cutab; // []uint32
@FieldMapping
private GoSlice filetab; // []uint32 when ver <=1.15, []uint8 blob of null term strings when ver >= 1.16
@FieldMapping(presentWhen = "1.16+")
private GoSlice pctab; // []uint8
@FieldMapping
private GoSlice pclntable; // []uint8, shares footprint with ftab. when ver <= 1.15, this is first field and happens to have a GoPcHeader
@FieldMapping
private GoSlice ftab; // []runtime.functab, shares footprint with pclntable
@FieldMapping
private long data;
@ -82,24 +100,6 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
@FieldMapping(fieldName = "typelinks")
private GoSlice typeLinks;
@FieldMapping
private GoSlice funcnametab; // []uint8 blob of null term strings
@FieldMapping
private GoSlice cutab; // []uint32
@FieldMapping
private GoSlice filetab; // []uint8 blob of null term strings
@FieldMapping
private GoSlice pctab; // []uint8
@FieldMapping
private GoSlice pclntable; // []uint8, shares footprint with ftab
@FieldMapping
private GoSlice ftab; // []runtime.functab, shares footprint with pclntable
@FieldMapping
private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab)
@ -114,17 +114,25 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
* Compares the data in this structure to fields in a GoPcHeader and returns true if they
* match.
*
* @param pclntab GoPcHeader instance
* @param otherPcHeader GoPcHeader instance
* @return boolean true if match, false if no match
*/
public boolean matchesPclntab(GoPcHeader pclntab) {
return (!pclntab.hasTextStart() || pclntab.getTextStart().equals(getText())) &&
pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress());
public boolean matchesPcHeader(GoPcHeader otherPcHeader) {
return (!otherPcHeader.hasTextStart() || otherPcHeader.getTextStart().equals(getText())) &&
otherPcHeader.getFuncnameAddress().equals(funcnametab.getArrayAddress());
}
@Markup
public GoPcHeader getPcHeader() throws IOException {
return programContext.readStructure(GoPcHeader.class, pcHeader);
return pcHeader != 0 // when ver >= 1.16
? programContext.readStructure(GoPcHeader.class, pcHeader)
: programContext.readStructure(GoPcHeader.class, pclntable.getArrayAddress());
}
public Address getPcHeaderAddress() {
return pcHeader != 0
? programContext.getDataAddress(pcHeader)
: pclntable.getArrayAddress();
}
/**
@ -236,8 +244,8 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
}
// all these static slices should be allocated with len == cap. If not true, fail.
if (!typeLinks.isFull() || !filetab.isFull() || !pctab.isFull() || !pclntable.isFull() ||
!ftab.isFull()) {
if (!typeLinks.isFull() || !filetab.isFull() || (pctab != null && !pctab.isFull()) ||
!pclntable.isFull() || !ftab.isFull()) {
return false;
}
@ -287,15 +295,8 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return filetab;
}
/**
* Returns the filename at the specified offset.
*
* @param fileoff offset in the filetab of the filename
* @return filename
* @throws IOException if error reading
*/
public String getFilename(long fileoff) throws IOException {
return programContext.getReader(filetab.getElementOffset(1, fileoff)).readNextUtf8String();
public GoSlice getPclntable() {
return pclntable;
}
/**
@ -307,6 +308,10 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return pctab;
}
public GoSlice getPcValueTable() {
return pctab != null ? pctab : pclntable;
}
/**
* Returns a reference to the controlling {@link GoRttiMapper go binary} context.
*
@ -329,7 +334,9 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
itablinks.markupArray("moduledata.itablinks", null, GoItab.class, true, session);
if (funcnametab != null) {
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
}
markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
GoSlice subSlice = getFunctabEntriesSlice();
@ -441,15 +448,15 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
* Searches memory for a likely GoModuledata structure.
*
* @param context already initialized {@link GoRttiMapper}
* @param pclntabAddress address of an already found {@link GoPcHeader}
* @param pclntab the {@link GoPcHeader}
* @param pcHeaderAddress address of an already found {@link GoPcHeader}
* @param pcHeader the {@link GoPcHeader}
* @param range memory range to search. Will be different for different types of binaries
* @param monitor {@link TaskMonitor}
* @return new GoModuledata instance, or null if not found
* @throws IOException if error reading found structure
*/
/* package */ static GoModuledata findFirstModule(GoRttiMapper context,
Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor)
Address pcHeaderAddress, GoPcHeader pcHeader, AddressRange range, TaskMonitor monitor)
throws IOException {
if (range == null) {
return null;
@ -462,7 +469,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
// field of the GoModuledata structure.
int ptrSize = context.getPtrSize();
byte[] searchBytes = new byte[ptrSize];
context.getDataConverter().putValue(pclntabAddress.getOffset(), ptrSize, searchBytes, 0);
context.getDataConverter().putValue(pcHeaderAddress.getOffset(), ptrSize, searchBytes, 0);
Address moduleAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
searchBytes, null, true, monitor);
if (moduleAddr == null) {
@ -473,50 +480,6 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
// Verify that we read a good GoModuledata struct by comparing some of its values to
// the pclntab structure.
return moduleData.matchesPclntab(pclntab) ? moduleData : null;
return moduleData.matchesPcHeader(pcHeader) ? moduleData : null;
}
}
/*
struct runtime.moduledata Length:276 Alignment:4{
runtime.pcHeader*pcHeader
[]uint8 funcnametab
[]uint32 cutab
[]uint8 filetab
[]uint8 pctab
[]uint8 pclntable
[]runtime.functab ftab
uintptr findfunctab
uintptr minpc
uintptr maxpc
uintptr text
uintptr etext
uintptr noptrdata
uintptr enoptrdata
uintptr data
uintptr edata
uintptr bss
uintptr ebss
uintptr noptrbss
uintptr enoptrbss
uintptr end
uintptr gcdata
uintptr gcbss
uintptr types
uintptr etypes
uintptr rodata
uintptr gofunc
[]runtime.textsect textsectmap
[]int32 typelinks
[]*runtime.itab itablinks
[]
runtime.ptabEntry ptab
string pluginpath
[]runtime.modulehash pkghashes
string modulename
[]runtime.modulehash modulehashes
uint8 hasmain
runtime.bitvector gcdatamask
runtime.bitvector gcbssmask map[runtime.typeOff]*runtime._type typemap
bool bad runtime.moduledata*next
}pack()*/

View file

@ -22,21 +22,26 @@ import ghidra.app.util.bin.format.golang.GoVer;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.BigEndianDataConverter;
import ghidra.util.LittleEndianDataConverter;
import ghidra.util.task.TaskMonitor;
/**
* A low-level structure embedded in golang binaries that contains useful bootstrapping
* information.
* <p>
* Introduced in golang 1.16
*
*/
@StructureMapping(structureName = "runtime.pcHeader")
@StructureMapping(structureName = GoPcHeader.GO_STRUCTURE_NAME)
public class GoPcHeader {
public static final String GO_STRUCTURE_NAME = "runtime.pcHeader";
private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab";
public static final String GOPCLNTAB_SECTION_NAME = "gopclntab";
public static final int GO_1_2_MAGIC = 0xfffffffb;
@ -49,29 +54,27 @@ public class GoPcHeader {
* @param program {@link Program}
* @return {@link Address} of go pclntab, or null if not present
*/
public static Address getPclntabAddress(Program program) {
public static Address getPcHeaderAddress(Program program) {
MemoryBlock pclntabBlock = GoRttiMapper.getGoSection(program, GOPCLNTAB_SECTION_NAME);
if (pclntabBlock != null) {
return pclntabBlock.getStart();
}
// PE binaries have a symbol instead of a named section
Symbol pclntabSymbol = GoRttiMapper.getGoSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME);
return pclntabSymbol != null
? pclntabSymbol.getAddress()
: null;
return pclntabSymbol != null ? pclntabSymbol.getAddress() : null;
}
/**
* Returns true if the specified program has an easily found pclntab
* Returns true if the specified program has an easily found pclntab w/pcHeader
*
* @param program {@link Program}
* @return boolean true if program has a pclntab, false otherwise
*/
public static boolean hasPclntab(Program program) {
Address addr = getPclntabAddress(program);
public static boolean hasPcHeader(Program program) {
Address addr = getPcHeaderAddress(program);
if (addr != null) {
try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) {
return isPclntab(provider);
return isPcHeader(provider);
}
catch (IOException e) {
// fall thru
@ -81,43 +84,43 @@ public class GoPcHeader {
}
/**
* Searches (possibly slowly) for a pclntab structure in the specified memory range, which
* is typically necessary in stripped PE binaries.
* Searches (possibly slowly) for a pclntab/pcHeader structure in the specified memory range,
* which is typically necessary in stripped PE binaries.
*
* @param programContext {@link GoRttiMapper}
* @param range memory range to search (typically .rdata or .noptrdata sections)
* @param monitor {@link TaskMonitor} that will let the user cancel
* @return {@link Address} of the found pclntab structure, or null if not found
* @return {@link Address} of the found pcHeader structure, or null if not found
* @throws IOException if error reading
*/
public static Address findPclntabAddress(GoRttiMapper programContext, AddressRange range,
public static Address findPcHeaderAddress(GoRttiMapper programContext, AddressRange range,
TaskMonitor monitor) throws IOException {
if (range == null) {
return null;
}
// search for magic signature + padding + wildcard_minLc + ptrSize
byte[] searchBytes = new byte[/*4 + 2 + 1 + 1*/] {
byte[] searchBytes = new byte[] { // 4 + 2 + 1 + 1
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // magic signature
0, 0, // padding
0, // unknown minLc, masked
(byte) programContext.getPtrSize() // ptrSize
};
byte[] searchMask = new byte[] {
byte[] searchMask = new byte[] { // also 4 + 2 + 1 + 1
(byte) 0xf0, (byte) 0xff, (byte) 0xff, (byte) 0xf0, // magic, first byte nibble and last byte nibble is wildcard to handle either endian matching
(byte) 0xff, (byte) 0xff, // padding
0, // unknown minLc - wildcarded
(byte) 0xff // ptrSize
};
Memory memory = programContext.getProgram().getMemory();
Address pclntabAddr =
memory.findBytes(range.getMinAddress(), range.getMaxAddress(), searchBytes, searchMask,
true, monitor);
if (pclntabAddr == null) {
Address pcHeaderAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
searchBytes, searchMask, true, monitor);
if (pcHeaderAddr == null) {
return null;
}
MemoryByteProvider bp =
new MemoryByteProvider(memory, pclntabAddr, range.getMaxAddress());
return isPclntab(bp) ? pclntabAddr : null;
try (MemoryByteProvider bp =
new MemoryByteProvider(memory, pcHeaderAddr, range.getMaxAddress())) {
return isPcHeader(bp) ? pcHeaderAddr : null;
}
}
/**
@ -127,11 +130,10 @@ public class GoPcHeader {
* @return boolean true if the byte provider has the magic signature of a pclntab
* @throws IOException if error reading
*/
public static boolean isPclntab(ByteProvider provider) throws IOException {
public static boolean isPcHeader(ByteProvider provider) throws IOException {
byte[] header = provider.readBytes(0, 8);
// logic from pclntab.go parsePclnTab()
if (provider.length() < 16 ||
header[4] != 0 || header[5] != 0 || // pad bytes == 0
if (provider.length() < 16 || header[4] != 0 || header[5] != 0 || // pad bytes == 0
(header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4
(header[7] != 4 && header[7] != 8) // ptrSize == 4,8
) {
@ -140,6 +142,23 @@ public class GoPcHeader {
return readMagic(provider) != null;
}
public static Structure createArtificialGoPcHeaderStructure(CategoryPath cp,
DataTypeManager dtm) {
// this manually creates a minimal struct that matches the header of a <=1.15 pclntab
// section
StructureDataType struct = new StructureDataType(cp, GO_STRUCTURE_NAME, 0, dtm);
struct.setDescription(
"Artificial structure created by Ghidra to represent the header of the pclntable");
struct.setPackingEnabled(true);
struct.add(AbstractIntegerDataType.getUnsignedDataType(4, null), "magic", null);
struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "pad1", null);
struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "pad2", null);
struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "minLC", null);
struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "ptrSize", null);
return struct;
}
@ContextField
private GoRttiMapper programContext;
@ -156,27 +175,27 @@ public class GoPcHeader {
@FieldMapping
private byte ptrSize;
@FieldMapping(optional = true) // present >= 1.18
@FieldMapping(presentWhen = "1.18+")
@MarkupReference
private long textStart; // should be same as offset of ".text"
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference("getFuncnameAddress")
private long funcnameOffset;
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference("getCuAddress")
private long cuOffset;
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference("getFiletabAddress")
private long filetabOffset;
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference("getPctabAddress")
private long pctabOffset;
@FieldMapping
@FieldMapping(presentWhen = "1.16+")
@MarkupReference("getPclnAddress")
private long pclnOffset;
@ -187,7 +206,7 @@ public class GoPcHeader {
case GO_1_2_MAGIC -> GoVer.V1_2;
case GO_1_16_MAGIC -> GoVer.V1_16;
case GO_1_18_MAGIC -> GoVer.V1_18;
default -> GoVer.UNKNOWN;
default -> GoVer.INVALID;
};
return ver;
}
@ -214,7 +233,9 @@ public class GoPcHeader {
* @return address of func name slice
*/
public Address getFuncnameAddress() {
return programContext.getDataAddress(context.getStructureStart() + funcnameOffset);
return funcnameOffset != 0
? programContext.getDataAddress(context.getStructureStart() + funcnameOffset)
: null;
}
/**
@ -222,7 +243,9 @@ public class GoPcHeader {
* @return address of the cu tab slice
*/
public Address getCuAddress() {
return programContext.getDataAddress(context.getStructureStart() + cuOffset);
return cuOffset != 0
? programContext.getDataAddress(context.getStructureStart() + cuOffset)
: null;
}
/**
@ -230,7 +253,9 @@ public class GoPcHeader {
* @return address of the filetab slice
*/
public Address getFiletabAddress() {
return programContext.getDataAddress(context.getStructureStart() + filetabOffset);
return filetabOffset != 0
? programContext.getDataAddress(context.getStructureStart() + filetabOffset)
: null;
}
/**
@ -238,7 +263,9 @@ public class GoPcHeader {
* @return address of the pctab slice
*/
public Address getPctabAddress() {
return programContext.getDataAddress(context.getStructureStart() + pctabOffset);
return pctabOffset != 0
? programContext.getDataAddress(context.getStructureStart() + pctabOffset)
: null;
}
/**
@ -246,7 +273,9 @@ public class GoPcHeader {
* @return address of the pcln slice
*/
public Address getPclnAddress() {
return programContext.getDataAddress(context.getStructureStart() + pclnOffset);
return pclnOffset != 0
? programContext.getDataAddress(context.getStructureStart() + pclnOffset)
: null;
}
/**
@ -273,8 +302,9 @@ public class GoPcHeader {
}
private static GoVerEndian readMagic(ByteProvider provider) throws IOException {
int leMagic = new BinaryReader(provider, true /* little endian */).readInt(0);
int beMagic = new BinaryReader(provider, false /* big endian */).readInt(0);
BinaryReader reader = new BinaryReader(provider, true);
int leMagic = reader.readInt(LittleEndianDataConverter.INSTANCE, 0);
int beMagic = reader.readInt(BigEndianDataConverter.INSTANCE, 0);
if (leMagic == GO_1_2_MAGIC || beMagic == GO_1_2_MAGIC) {
return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC);

View file

@ -46,7 +46,7 @@ public class GoPcValueEvaluator {
GoModuledata moduledata = func.getModuledata();
this.pcquantum = moduledata.getGoBinary().getMinLC();
this.reader = moduledata.getPctab().getElementReader(1, (int) offset);
this.reader = moduledata.getPcValueTable().getElementReader(1, (int) offset);
this.funcEntry = func.getFuncAddress().getOffset();
this.pc = funcEntry;

View file

@ -78,7 +78,13 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
* </ul>
* </ul>
*/
public class GoRttiMapper extends DataTypeMapper {
public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContext {
public static final GoVer SUPPORTED_MIN_VER = new GoVer(1, 15);
public static final GoVer SUPPORTED_MAX_VER = new GoVer(1, 22);
private static final List<String> SYMBOL_SEARCH_PREFIXES = List.of("", "_" /* macho symbols */);
private static final List<String> SECTION_PREFIXES =
List.of("." /* ELF */, "__" /* macho sections */);
private static final String FAILED_FLAG = "FAILED TO FIND GOLANG BINARY";
@ -163,20 +169,24 @@ public class GoRttiMapper extends DataTypeMapper {
return null;
}
GoVer goVer = buildInfo.getVerEnum();
if (goVer == GoVer.UNKNOWN) {
GoVer goVer = buildInfo.getGoVer();
if (goVer.isInvalid()) {
throw new BootstrapInfoException(
"Unsupported Golang version, version info: '%s'".formatted(buildInfo.getVersion()));
"Invalid Golang version string [%s]".formatted(buildInfo.getVersion()));
}
if (!goVer.inRange(SUPPORTED_MIN_VER, SUPPORTED_MAX_VER)) {
Msg.error(GoRttiMapper.class, "Untested golang version [%s]".formatted(goVer));
}
ResourceFile gdtFile =
findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program));
if (gdtFile == null) {
Msg.error(GoRttiMapper.class, "Missing golang gdt archive for " + goVer);
Msg.error(GoRttiMapper.class,
"Missing golang gdt archive for golang version [%s]".formatted(goVer));
}
return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(),
buildInfo.getVerEnum(), gdtFile);
return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), goVer,
gdtFile);
}
/**
@ -267,10 +277,6 @@ public class GoRttiMapper extends DataTypeMapper {
return false;
}
private static final List<String> SYMBOL_SEARCH_PREFIXES = List.of("", "_" /* macho symbols */);
private static final List<String> SECTION_PREFIXES =
List.of("." /* ELF */, "__" /* macho sections */);
/**
* Returns a matching symbol from the specified program, using golang specific logic.
*
@ -328,6 +334,16 @@ public class GoRttiMapper extends DataTypeMapper {
return zerobaseAddr;
}
public static List<GoVer> getAllSupportedVersions() {
List<GoVer> result = new ArrayList<>();
for (int minor = SUPPORTED_MIN_VER.getMinor(); minor <= SUPPORTED_MAX_VER
.getMinor(); minor++) {
// TODO: a bit of a hack only supporting 1.x version number instances
result.add(new GoVer(1, minor));
}
return result;
}
public final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME =
"ARTIFICIAL.runtime.zerobase";
@ -415,7 +431,7 @@ public class GoRttiMapper extends DataTypeMapper {
this.stringDT = getTypeOrDefault("string", Structure.class, null);
try {
registerStructures(GOLANG_STRUCTMAPPED_CLASSES);
registerStructures(GOLANG_STRUCTMAPPED_CLASSES, this);
}
catch (IOException e) {
if (archiveGDT == null) {
@ -433,6 +449,20 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
@Override
public <T extends DataType> T getType(String name, Class<T> clazz) {
T result = super.getType(name, clazz);
if (result == null && GoPcHeader.GO_STRUCTURE_NAME.equals(name) &&
Structure.class.isAssignableFrom(clazz)) {
// create an artificial runtime.pcHeader structure for <=1.15 to enable GoModuledata
// to have references to a GoPcHeader
result = clazz.cast(GoPcHeader.createArtificialGoPcHeaderStructure(GOLANG_CP,
program.getDataTypeManager()));
}
return result;
}
/**
* Returns the golang version
* @return {@link GoVer}
@ -462,11 +492,13 @@ public class GoRttiMapper extends DataTypeMapper {
GoModuledata firstModule = findFirstModuledata(monitor);
if (firstModule != null) {
GoPcHeader pcHeader = firstModule.getPcHeader();
if (pcHeader != null) {
this.minLC = pcHeader.getMinLC();
if (pcHeader.getPtrSize() != ptrSize) {
throw new IOException(
"Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize));
}
}
addModule(firstModule);
}
initFuncdata();
@ -1401,22 +1433,22 @@ public class GoRttiMapper extends DataTypeMapper {
private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException {
GoModuledata result = GoModuledata.getFirstModuledata(this);
if (result == null) {
monitor.setMessage("Searching for Golang pclntab");
monitor.initialize(0);
Address pclntabAddress = GoPcHeader.getPclntabAddress(program);
if (pclntabAddress == null) {
pclntabAddress =
GoPcHeader.findPclntabAddress(this, getPclntabSearchRange(), monitor);
Address pcHeaderAddress =
result != null ? result.getPcHeaderAddress() : GoPcHeader.getPcHeaderAddress(program);
if (pcHeaderAddress == null) {
monitor.initialize(0, "Searching for Golang pclntab");
pcHeaderAddress =
GoPcHeader.findPcHeaderAddress(this, getPclntabSearchRange(), monitor);
}
if (pclntabAddress != null) {
monitor.setMessage("Searching for Golang firstmoduledata");
monitor.initialize(0);
GoPcHeader pclntab = readStructure(GoPcHeader.class, pclntabAddress);
result = GoModuledata.findFirstModule(this, pclntabAddress, pclntab,
if (result == null && pcHeaderAddress != null) {
// find the moduledata struct by searching for a pointer to the pclntab/pcHeader,
// which should be the first field in the moduledata struct.
monitor.initialize(0, "Searching for Golang firstmoduledata");
GoPcHeader pcHeader = readStructure(GoPcHeader.class, pcHeaderAddress);
result = GoModuledata.findFirstModule(this, pcHeaderAddress, pcHeader,
getModuledataSearchRange(), monitor);
}
}
if (result != null && !result.isValid()) {
throw new IOException("Invalid Golang moduledata at %s"
.formatted(result.getStructureContext().getStructureAddress()));
@ -1554,4 +1586,25 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
@Override
public boolean isFieldPresent(String presentWhen) {
presentWhen = presentWhen.strip();
if (presentWhen.isEmpty()) {
return true;
}
String[] verNums = presentWhen.split("[+-]", -1); // "1.2-1.5" or "1.2+" or "-1.2"
if (verNums.length != 2) {
throw new IllegalArgumentException(
"Invalid 'presentWhen' value [%s]".formatted(presentWhen));
}
GoVer startVer = verNums[0].isBlank() ? new GoVer(1, 0) : GoVer.parse(verNums[0]);
GoVer endVer = verNums[1].isBlank() ? new GoVer(99, 99) : GoVer.parse(verNums[1]);
if (startVer.isInvalid() || endVer.isInvalid()) {
throw new IllegalArgumentException(
"Invalid 'presentWhen' value [%s]".formatted(presentWhen));
}
return startVer.compareTo(goVersion) <= 0 && goVersion.compareTo(endVer) <= 0;
}
}

View file

@ -20,11 +20,14 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.GoVer;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.BigEndianDataConverter;
/**
* A pascal-ish string, using a LEB128 value as the length of the following bytes.
* A pascal-ish string, using a LEB128 (or a uint16 in pre-1.16) value as the length of the
* following bytes.
* <p>
* Used mainly in lower-level RTTI structures, this class is a ghidra'ism used to parse the
* golang rtti data and does not have a counterpart in the golang src.
@ -51,9 +54,16 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
readFrom(context.getReader());
}
private boolean useLEB128() {
return ((GoRttiMapper) context.getDataTypeMapper()).getGolangVersion()
.isAtLeast(GoVer.V1_17);
}
private void readFrom(BinaryReader reader) throws IOException {
long startPos = reader.getPointerIndex();
int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
int strLen = useLEB128()
? reader.readNextUnsignedVarIntExact(LEB128::unsigned)
: reader.readNextUnsignedShort(BigEndianDataConverter.INSTANCE);
this.strlenLen = (int) (reader.getPointerIndex() - startPos);
this.bytes = reader.readNextByteArray(strLen);
}
@ -68,9 +78,9 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
}
/**
* Returns the string length's length (length of the leb128 number)
* Returns the size of the string length field.
*
* @return string length's length
* @return size of the string length field
*/
public int getStrlenLen() {
return strlenLen;
@ -100,8 +110,11 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
* @return data type needed to hold the string length field
*/
public DataTypeInstance getStrlenDataType() {
return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen,
false);
DataType dt = useLEB128()
? UnsignedLeb128DataType.dataType
: AbstractIntegerDataType.getUnsignedDataType(2, null);
return DataTypeInstance.getDataTypeInstance(dt, strlenLen, false);
}
/**
@ -119,5 +132,4 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
return String.format("GoVarlenString [context=%s, strlenLen=%s, bytes=%s, getString()=%s]",
context, strlenLen, Arrays.toString(bytes), getString());
}
}

View file

@ -20,8 +20,7 @@ import java.util.Set;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.*;
/**
* {@link GoType} structure that defines an array.
@ -72,7 +71,15 @@ public class GoArrayType extends GoType {
if (self != null) {
return self;
}
return new ArrayDataType(elementDt, (int) len, -1);
return isValidLength()
? new ArrayDataType(elementDt, (int) len, -1)
: new TypedefDataType(elementDt.getCategoryPath(),
".invalid_arraysize_%d_%s".formatted(len, elementDt.getName()),
new ArrayDataType(elementDt, 1, -1), elementDt.getDataTypeManager());
}
private boolean isValidLength() {
return 0 <= len && len <= Integer.MAX_VALUE;
}
@Override

View file

@ -42,10 +42,10 @@ public class GoStructField {
@MarkupReference("getType")
private long typ; // direct ptr to GoType
@FieldMapping(optional = true) //<=1.18
@FieldMapping(presentWhen = "-1.18")
private long offsetAnon; // offsetAnon >> 1 == actual offset, bit 0 = embedded flag
@FieldMapping(optional = true) //>=1.19
@FieldMapping(presentWhen = "1.19+")
private long offset;
/**

View file

@ -31,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
/**
* Information about {@link StructureMapping} classes and their metadata.
* <p>
* To use the full might and majesty of StructureMapping(tm), a DataTypeMapper must be created. It
* To use the full might and majesty of StructureMapping&trade;, a DataTypeMapper must be created. It
* must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find}
* ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data
* types being used, and it must {@link #registerStructure(Class) know} about all classes that are
@ -166,9 +166,11 @@ public class DataTypeMapper implements AutoCloseable {
* @param <T> structure mapped class type
* @param clazz class that represents a structure, marked with {@link StructureMapping}
* annotation
* @param context {@link DataTypeMapperContext}
* @throws IOException if the class's Ghidra structure data type could not be found
*/
public <T> void registerStructure(Class<T> clazz) throws IOException {
public <T> void registerStructure(Class<T> clazz, DataTypeMapperContext context)
throws IOException {
StructureMapping sma = clazz.getAnnotation(StructureMapping.class);
List<String> structNames = sma != null ? Arrays.asList(sma.structureName()) : List.of();
Structure structDT = getType(structNames, Structure.class);
@ -187,7 +189,7 @@ public class DataTypeMapper implements AutoCloseable {
try {
StructureMappingInfo<T> structMappingInfo =
StructureMappingInfo.fromClass(clazz, structDT);
StructureMappingInfo.fromClass(clazz, structDT, context);
mappingInfo.put(clazz, structMappingInfo);
}
catch (IllegalArgumentException e) {
@ -199,11 +201,13 @@ public class DataTypeMapper implements AutoCloseable {
* Registers the specified {@link StructureMapping structure mapping} classes.
*
* @param classes list of classes to register
* @param context {@link DataTypeMapperContext}
* @throws IOException if a class's Ghidra structure data type could not be found
*/
public void registerStructures(List<Class<?>> classes) throws IOException {
public void registerStructures(List<Class<?>> classes, DataTypeMapperContext context)
throws IOException {
for (Class<?> clazz : classes) {
registerStructure(clazz);
registerStructure(clazz, context);
}
}
@ -544,5 +548,4 @@ public class DataTypeMapper implements AutoCloseable {
}
return new StructureContext<>(this, smi, null);
}
}

View file

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.structmapping;
/**
* Context passed to StructureMapping logic when binding a structure's fields to a java class's
* fields.
*/
public interface DataTypeMapperContext {
/**
* Tests if a field should be included when creating bindings between a structure and a class.
*
* @param presentWhen free-form string that is interpreted by each {@link DataTypeMapper}
* @return boolean true if field should be bound, false if field should not be bound
*/
boolean isFieldPresent(String presentWhen);
}

View file

@ -62,6 +62,22 @@ public @interface FieldMapping {
*/
boolean optional() default false;
/**
* Marks this field as only present in certain context configurations.
* <p>
* The specified string is interpreted by the specific {@link DataTypeMapper} and its
* {@link DataTypeMapperContext context}.
* <p>
* For example, a version number could be used to allow some optional fields to be skipped
* depending on the the concrete {@link DataTypeMapper}'s information during structure
* mapping registration.
* <p>
* Similar to {@link #optional()}
*
* @return String
*/
String presentWhen() default "";
/**
* Specifies the name of a setter method that will be used to assign the deserialized value
* to the java field.

View file

@ -39,18 +39,19 @@ public class StructureMappingInfo<T> {
* @param targetClass structure mapped class
* @param structDataType Ghidra {@link DataType} that defines the binary layout of the mapped
* fields of the class, or null if this is a self-reading {@link StructureReader} class
* @param context {@link DataTypeMapperContext}
* @return new {@link StructureMappingInfo} for the specified class
* @throws IllegalArgumentException if targetClass isn't tagged as a structure mapped class
*/
public static <T> StructureMappingInfo<T> fromClass(Class<T> targetClass,
Structure structDataType) {
Structure structDataType, DataTypeMapperContext context) {
StructureMapping sma = targetClass.getAnnotation(StructureMapping.class);
if (sma == null) {
throw new IllegalArgumentException(
"Missing @StructureMapping annotation on " + targetClass.getSimpleName());
}
return new StructureMappingInfo<>(targetClass, structDataType, sma);
return new StructureMappingInfo<>(targetClass, structDataType, sma, context);
}
private final Class<T> targetClass;
@ -69,17 +70,17 @@ public class StructureMappingInfo<T> {
private Field structureContextField;
private StructureMappingInfo(Class<T> targetClass, Structure structDataType,
StructureMapping sma) {
StructureMapping sma, DataTypeMapperContext context) {
this.targetClass = targetClass;
this.structureDataType = structDataType;
this.structureName = structureDataType != null
? structureDataType.getName()
: sma.structureName()[0];
this.fieldNameLookup = indexStructFields(structDataType);
this.fieldNameLookup = indexStructFields();
this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass);
this.instanceCreator = findInstanceCreator();
readFieldInfo(targetClass);
readFieldInfo(targetClass, context);
Collections.sort(outputFields,
(foi1, foi2) -> Integer.compare(foi1.getOrdinal(), foi2.getOrdinal()));
@ -249,10 +250,10 @@ public class StructureMappingInfo<T> {
}
}
private void readFieldInfo(Class<?> clazz) {
private void readFieldInfo(Class<?> clazz, DataTypeMapperContext context) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
readFieldInfo(superclass);
readFieldInfo(superclass, context);
}
for (Field field : clazz.getDeclaredFields()) {
@ -260,7 +261,7 @@ public class StructureMappingInfo<T> {
FieldMapping fma = field.getAnnotation(FieldMapping.class);
FieldOutput foa = field.getAnnotation(FieldOutput.class);
if (fma != null || foa != null) {
FieldMappingInfo<T> fmi = readFieldMappingInfo(field, fma);
FieldMappingInfo<T> fmi = readFieldMappingInfo(field, fma, context);
if (fmi == null) {
// was marked optional field, just skip
continue;
@ -287,7 +288,14 @@ public class StructureMappingInfo<T> {
}
}
private FieldMappingInfo<T> readFieldMappingInfo(Field field, FieldMapping fma) {
private FieldMappingInfo<T> readFieldMappingInfo(Field field, FieldMapping fma,
DataTypeMapperContext context) {
if (fma != null && !context.isFieldPresent(fma.presentWhen())) {
// skip if this field was marked as not present
return null;
}
String[] fieldNames = getFieldNamesToSearchFor(field, fma);
DataTypeComponent dtc = getFirstMatchingField(fieldNames);
if (useFieldMappingInfo && dtc == null) {
@ -379,12 +387,12 @@ public class StructureMappingInfo<T> {
return struct.isZeroLength() ? 0 : struct.getLength();
}
private static Map<String, DataTypeComponent> indexStructFields(Structure struct) {
if (struct == null) {
private Map<String, DataTypeComponent> indexStructFields() {
if (structureDataType == null) {
return Map.of();
}
Map<String, DataTypeComponent> result = new HashMap<>();
for (DataTypeComponent dtc : struct.getDefinedComponents()) {
for (DataTypeComponent dtc : structureDataType.getDefinedComponents()) {
String fieldName = dtc.getFieldName();
if (fieldName != null) {
result.put(fieldName.toLowerCase(), dtc);

View file

@ -45,6 +45,8 @@ public class DataTypeArchiveIDTest extends AbstractGenericTest {
Map.entry(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"),
Map.entry(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"),
Map.entry(MAC_OS_10_9_GDT_PATH, "2650667045259492112"),
Map.entry("typeinfo/golang/golang_1.15_anybit_any.gdt", "3600806988729184131"),
Map.entry("typeinfo/golang/golang_1.16_anybit_any.gdt", "3597021567582750001"),
Map.entry("typeinfo/golang/golang_1.17_anybit_any.gdt", "3533627828569507753"),
Map.entry("typeinfo/golang/golang_1.18_anybit_any.gdt", "3528902399865061936"),
Map.entry("typeinfo/golang/golang_1.19_anybit_any.gdt", "3533812166493410774"),

View file

@ -15,6 +15,7 @@
<compiler name="golang" spec="AARCH64_golang.cspec" id="golang"/>
<external_name tool="gnu" name="aarch64"/>
<external_name tool="DWARF.register.mapping.file" name="AARCH64.dwarf"/>
<external_name tool="Golang.register.info.file" name="AARCH64_golang.register.info"/>
<external_name tool="qemu" name="qemu-aarch64"/>
</language>
<language processor="AARCH64"

View file

@ -281,8 +281,26 @@
<killedbycall>
<register name="x21"/>
<register name="x20"/>
<register name="x26"/>
<register name="x27"/>
</killedbycall>
<unaffected>
<register name="x0"/>
<register name="x1"/>
<register name="x2"/>
<register name="x3"/>
<register name="x4"/>
<register name="x5"/>
<register name="x6"/>
<register name="x7"/>
<register name="x8"/>
<register name="x9"/>
<register name="x10"/>
<register name="x11"/>
<register name="x12"/>
<register name="x13"/>
<register name="x14"/>
<register name="x15"/>
<register name="x16"/>
<register name="x17"/>
</unaffected>

View file

@ -6,5 +6,14 @@
<stack initialoffset="8" maxalign="8"/>
<current_goroutine register="x28"/>
<zero_register register="xzr" builtin="true"/>
<duffzero dest="x20" zero_arg="" zero_type=""/>
</register_info>
<register_info versions="V1_16">
<int_registers list=""/>
<float_registers list=""/>
<stack initialoffset="8" maxalign="8"/>
<current_goroutine register="x28"/>
<zero_register register="xzr" builtin="true"/>
<duffzero dest="x20" zero_arg="" zero_type=""/>
</register_info>
</golang>

View file

@ -5,5 +5,6 @@
<stack initialoffset="4" maxalign="4"/>
<current_goroutine register=""/>
<zero_register register=""/>
<duffzero dest="EDI" zero_arg="EAX" zero_type="int"/>
</register_info>
</golang>

View file

@ -195,6 +195,14 @@
<register name="RDI"/>
</killedbycall>
<unaffected>
<register name="RAX"/>
<register name="RBX"/>
<register name="RCX"/>
<register name="RSI"/>
<register name="R8"/>
<register name="R9"/>
<register name="R10"/>
<register name="R11"/>
<register name="RSP"/>
<register name="RBP"/>
<register name="R14"/>
@ -228,8 +236,6 @@
<register name="RAX"/>
<register name="RBX"/>
<register name="RCX"/>
<register name="RDI"/>
<register name="RSI"/>
<register name="R8"/>
<register name="R9"/>
<register name="R10"/>

View file

@ -6,5 +6,14 @@
<stack initialoffset="8" maxalign="8"/>
<current_goroutine register="R14"/>
<zero_register register="XMM15"/>
<duffzero dest="RDI" />
</register_info>
<register_info versions="V1_15,V1_16">
<int_registers list=""/>
<float_registers list=""/>
<stack initialoffset="8" maxalign="8"/>
<current_goroutine register=""/>
<zero_register register=""/>
<duffzero dest="RDI" zero_arg="XMM0" zero_type="float"/>
</register_info>
</golang>