mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-4482 Golang 1.16 + 1.15
This commit is contained in:
parent
db608a1a13
commit
560d5691a7
32 changed files with 851 additions and 374 deletions
|
@ -87,6 +87,8 @@ data/symbols/win64/mfc90u.exports||GHIDRA||||END|
|
||||||
data/symbols/win64/msvcrt.hints||GHIDRA||||END|
|
data/symbols/win64/msvcrt.hints||GHIDRA||||END|
|
||||||
data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END|
|
data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END|
|
||||||
data/typeinfo/generic/generic_clib_64.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.17_anybit_any.gdt||GHIDRA||||END|
|
||||||
data/typeinfo/golang/golang_1.18_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|
|
data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -18,11 +18,10 @@
|
||||||
// specified for the arch.
|
// specified for the arch.
|
||||||
//@category Functions
|
//@category Functions
|
||||||
//@menupath Tools.Fix Golang Function Param Storage
|
//@menupath Tools.Fix Golang Function Param Storage
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.app.util.bin.format.golang.GoFunctionFixup;
|
import ghidra.app.util.bin.format.golang.GoFunctionFixup;
|
||||||
import ghidra.app.util.bin.format.golang.GoVer;
|
import ghidra.app.util.bin.format.golang.GoVer;
|
||||||
|
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.listing.Function;
|
||||||
|
|
||||||
public class FixupGolangFuncParamStorageScript extends GhidraScript {
|
public class FixupGolangFuncParamStorageScript extends GhidraScript {
|
||||||
|
@ -34,10 +33,9 @@ public class FixupGolangFuncParamStorageScript extends GhidraScript {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GoVer goVersion = GoVer.fromProgramProperties(currentProgram);
|
GoVer goVersion = GoVer.fromProgramProperties(currentProgram);
|
||||||
if ( goVersion == GoVer.UNKNOWN ) {
|
if (goVersion == GoVer.INVALID) {
|
||||||
List<GoVer> versions = List.of(GoVer.values());
|
goVersion = askChoice("Golang Version", "What is the golang version?",
|
||||||
goVersion =
|
GoRttiMapper.getAllSupportedVersions(), GoVer.INVALID);
|
||||||
askChoice("Golang Version", "What is the golang version?", versions, GoVer.UNKNOWN);
|
|
||||||
}
|
}
|
||||||
println("Fixing param storage for function %s@%s".formatted(func.getName(),
|
println("Fixing param storage for function %s@%s".formatted(func.getName(),
|
||||||
func.getEntryPoint()));
|
func.getEntryPoint()));
|
||||||
|
|
|
@ -177,8 +177,6 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markupWellknownSymbols() throws IOException {
|
private void markupWellknownSymbols() throws IOException {
|
||||||
Program program = goBinary.getProgram();
|
|
||||||
|
|
||||||
Symbol g0 = goBinary.getGoSymbol("runtime.g0");
|
Symbol g0 = goBinary.getGoSymbol("runtime.g0");
|
||||||
Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class);
|
Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class);
|
||||||
if (g0 != null && gStruct != null) {
|
if (g0 != null && gStruct != null) {
|
||||||
|
@ -317,31 +315,25 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
|
||||||
Program program = goBinary.getProgram();
|
Program program = goBinary.getProgram();
|
||||||
GoRegisterInfo regInfo = goBinary.getRegInfo();
|
GoRegisterInfo regInfo = goBinary.getRegInfo();
|
||||||
DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType);
|
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");
|
GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero");
|
||||||
Function duffzeroFunc = duffzeroFuncdata != null
|
Function duffzeroFunc = duffzeroFuncdata != null
|
||||||
? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress())
|
? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress())
|
||||||
: null;
|
: null;
|
||||||
if (duffzeroFunc != null &&
|
List<Variable> duffzeroParams = regInfo.getDuffzeroParams(program);
|
||||||
goBinary.hasCallingConvention(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME)) {
|
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 {
|
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,
|
// NOTE: even though we are specifying custom storage for the arguments, the
|
||||||
new ReturnParameterImpl(VoidDataType.dataType, program), params,
|
// calling convention name is still important as it tells the decompiler which
|
||||||
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
|
// 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,
|
markupSession.appendComment(duffzeroFunc, null,
|
||||||
"Golang special function: duffzero");
|
"Golang special function: duffzero");
|
||||||
|
@ -527,6 +519,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
|
||||||
String duffComment = program.getListing()
|
String duffComment = program.getListing()
|
||||||
.getCodeUnitAt(duffFunc.getEntryPoint())
|
.getCodeUnitAt(duffFunc.getEntryPoint())
|
||||||
.getComment(CodeUnit.PLATE_COMMENT);
|
.getComment(CodeUnit.PLATE_COMMENT);
|
||||||
|
|
||||||
monitor.setMessage("Fixing alternate duffzero/duffcopy entry points");
|
monitor.setMessage("Fixing alternate duffzero/duffcopy entry points");
|
||||||
for (FunctionIterator funcIt =
|
for (FunctionIterator funcIt =
|
||||||
program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) {
|
program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) {
|
||||||
|
@ -538,9 +531,11 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
|
||||||
func.setName(duffFunc.getName() + "_" + func.getEntryPoint(),
|
func.setName(duffFunc.getName() + "_" + func.getEntryPoint(),
|
||||||
SourceType.ANALYSIS);
|
SourceType.ANALYSIS);
|
||||||
func.setParentNamespace(funcNS);
|
func.setParentNamespace(funcNS);
|
||||||
|
FunctionUpdateType fut = duffFunc.hasCustomVariableStorage()
|
||||||
|
? FunctionUpdateType.CUSTOM_STORAGE
|
||||||
|
: FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS;
|
||||||
func.updateFunction(ccName, duffFunc.getReturn(),
|
func.updateFunction(ccName, duffFunc.getReturn(),
|
||||||
Arrays.asList(duffFunc.getParameters()),
|
Arrays.asList(duffFunc.getParameters()), fut, true, SourceType.ANALYSIS);
|
||||||
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
|
|
||||||
if (duffComment != null && !duffComment.isBlank()) {
|
if (duffComment != null && !duffComment.isBlank()) {
|
||||||
new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment)
|
new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment)
|
||||||
.applyTo(program);
|
.applyTo(program);
|
||||||
|
|
|
@ -415,7 +415,19 @@ public class BinaryReader {
|
||||||
* @exception IOException if an I/O error occurs
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public short readNextShort() throws IOException {
|
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;
|
currentIndex += SIZEOF_SHORT;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -427,7 +439,19 @@ public class BinaryReader {
|
||||||
* @exception IOException if an I/O error occurs
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public int readNextUnsignedShort() throws IOException {
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public int readNextInt() throws IOException {
|
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;
|
currentIndex += SIZEOF_INT;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
@ -449,7 +485,19 @@ public class BinaryReader {
|
||||||
* @exception IOException if an I/O error occurs
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public long readNextUnsignedInt() throws IOException {
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public long readNextLong() throws IOException {
|
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;
|
currentIndex += SIZEOF_LONG;
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
@ -469,10 +529,22 @@ public class BinaryReader {
|
||||||
*
|
*
|
||||||
* @param len the number of bytes that the integer occupies, 1 to 8
|
* @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
|
* @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 {
|
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;
|
currentIndex += len;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -482,10 +554,22 @@ public class BinaryReader {
|
||||||
*
|
*
|
||||||
* @param len the number of bytes that the integer occupies, 1 to 8
|
* @param len the number of bytes that the integer occupies, 1 to 8
|
||||||
* @return unsigned value of requested length, in a long
|
* @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 {
|
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;
|
currentIndex += len;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -684,7 +768,25 @@ public class BinaryReader {
|
||||||
* @throws InvalidDataException if value can not be held in a java integer
|
* @throws InvalidDataException if value can not be held in a java integer
|
||||||
*/
|
*/
|
||||||
public int readNextUnsignedIntExact() throws IOException, InvalidDataException {
|
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);
|
ensureInt32u(i);
|
||||||
return (int) i;
|
return (int) i;
|
||||||
}
|
}
|
||||||
|
@ -908,8 +1010,19 @@ public class BinaryReader {
|
||||||
* @exception IOException if an I/O error occurs
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public short readShort(long index) throws IOException {
|
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);
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public int readUnsignedShort(long index) throws IOException {
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public int readInt(long index) throws IOException {
|
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);
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public long readUnsignedInt(long index) throws IOException {
|
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
|
* @exception IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
public long readLong(long index) throws IOException {
|
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);
|
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 index where the value begins
|
||||||
* @param len the number of bytes that the integer occupies, 1 to 8
|
* @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
|
* @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 {
|
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);
|
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 index where the value begins
|
||||||
* @param len the number of bytes that the integer occupies, 1 to 8
|
* @param len the number of bytes that the integer occupies, 1 to 8
|
||||||
* @return unsigned value of requested length, in a long
|
* @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 {
|
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);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1201,6 +1201,7 @@ public class DWARFProgram implements Closeable {
|
||||||
|
|
||||||
private DWARFLocationList readLocationList(DWARFNumericAttribute loclistAttr,
|
private DWARFLocationList readLocationList(DWARFNumericAttribute loclistAttr,
|
||||||
DWARFCompilationUnit cu) throws IOException {
|
DWARFCompilationUnit cu) throws IOException {
|
||||||
|
try {
|
||||||
switch (loclistAttr.getAttributeForm()) {
|
switch (loclistAttr.getAttributeForm()) {
|
||||||
case DW_FORM_sec_offset:
|
case DW_FORM_sec_offset:
|
||||||
int dwarfVer = cu.getDWARFVersion();
|
int dwarfVer = cu.getDWARFVersion();
|
||||||
|
@ -1221,6 +1222,12 @@ public class DWARFProgram implements Closeable {
|
||||||
default:
|
default:
|
||||||
break; // fallthru to throw
|
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(
|
throw new IOException(
|
||||||
"Unsupported loclist form %s".formatted(loclistAttr.getAttributeForm()));
|
"Unsupported loclist form %s".formatted(loclistAttr.getAttributeForm()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class DWARFRange implements Comparable<DWARFRange> {
|
||||||
public DWARFRange(long start, long end) {
|
public DWARFRange(long start, long end) {
|
||||||
if (Long.compareUnsigned(end, start) < 0) {
|
if (Long.compareUnsigned(end, start) < 0) {
|
||||||
throw new IllegalArgumentException(
|
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.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
|
|
|
@ -189,7 +189,7 @@ public class GoBuildInfo implements ElfInfoItem {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GoVer getVerEnum() {
|
public GoVer getGoVer() {
|
||||||
return GoVer.parse(version);
|
return GoVer.parse(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.util.bin.format.golang;
|
package ghidra.app.util.bin.format.golang;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.data.Enum;
|
import ghidra.program.model.data.Enum;
|
||||||
import ghidra.program.model.lang.Register;
|
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
|
* 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 class GoRegisterInfo {
|
||||||
|
|
||||||
|
public enum RegType { INT, FLOAT }
|
||||||
|
|
||||||
private final List<Register> intRegisters;
|
private final List<Register> intRegisters;
|
||||||
private final List<Register> floatRegisters;
|
private final List<Register> floatRegisters;
|
||||||
private final int stackInitialOffset;
|
private final int stackInitialOffset;
|
||||||
|
@ -36,9 +44,14 @@ public class GoRegisterInfo {
|
||||||
private final Register zeroRegister; // always contains a zero value
|
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 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,
|
GoRegisterInfo(List<Register> intRegisters, List<Register> floatRegisters,
|
||||||
int stackInitialOffset, int maxAlign, Register currentGoroutineRegister,
|
int stackInitialOffset, int maxAlign, Register currentGoroutineRegister,
|
||||||
Register zeroRegister, boolean zeroRegisterIsBuiltin) {
|
Register zeroRegister, boolean zeroRegisterIsBuiltin, Register duffzeroDestParam,
|
||||||
|
Register duffzeroZeroParam, RegType duffzeroZeroParamType) {
|
||||||
this.intRegisters = intRegisters;
|
this.intRegisters = intRegisters;
|
||||||
this.floatRegisters = floatRegisters;
|
this.floatRegisters = floatRegisters;
|
||||||
this.stackInitialOffset = stackInitialOffset;
|
this.stackInitialOffset = stackInitialOffset;
|
||||||
|
@ -46,6 +59,10 @@ public class GoRegisterInfo {
|
||||||
this.currentGoroutineRegister = currentGoroutineRegister;
|
this.currentGoroutineRegister = currentGoroutineRegister;
|
||||||
this.zeroRegister = zeroRegister;
|
this.zeroRegister = zeroRegister;
|
||||||
this.zeroRegisterIsBuiltin = zeroRegisterIsBuiltin;
|
this.zeroRegisterIsBuiltin = zeroRegisterIsBuiltin;
|
||||||
|
|
||||||
|
this.duffzeroDestParam = duffzeroDestParam;
|
||||||
|
this.duffzeroZeroParam = duffzeroZeroParam;
|
||||||
|
this.duffzeroZeroParamType = duffzeroZeroParamType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIntRegisterSize() {
|
public int getIntRegisterSize() {
|
||||||
|
@ -80,6 +97,45 @@ public class GoRegisterInfo {
|
||||||
return stackInitialOffset;
|
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) {
|
public int getAlignmentForType(DataType dt) {
|
||||||
while (dt instanceof TypeDef || dt instanceof Array) {
|
while (dt instanceof TypeDef || dt instanceof Array) {
|
||||||
if (dt instanceof TypeDef td) {
|
if (dt instanceof TypeDef td) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.jdom.input.SAXBuilder;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
|
import ghidra.app.util.bin.format.dwarf.DWARFUtil;
|
||||||
|
import ghidra.app.util.bin.format.golang.GoRegisterInfo.RegType;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.xml.XmlUtilities;
|
import ghidra.util.xml.XmlUtilities;
|
||||||
|
@ -32,12 +33,13 @@ import ghidra.util.xml.XmlUtilities;
|
||||||
* XML config file format:
|
* XML config file format:
|
||||||
* <pre>
|
* <pre>
|
||||||
* <golang>
|
* <golang>
|
||||||
* <register_info versions="V1_17,V1_18">
|
* <register_info versions="V1_17,V1_18,1.20,1.21"> // or "all"
|
||||||
* <int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
|
* <int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
|
||||||
* <float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
|
* <float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
|
||||||
* <stack initialoffset="8" maxalign="8"/>
|
* <stack initialoffset="8" maxalign="8"/>
|
||||||
* <current_goroutine register="R14"/>
|
* <current_goroutine register="R14"/>
|
||||||
* <zero_register register="XMM15" builtin="true|false"/>
|
* <zero_register register="XMM15" builtin="true|false"/>
|
||||||
|
* <duffzero dest="RDI" zero_arg="XMM0" zero_type="float|int"/>
|
||||||
* </register_info>
|
* </register_info>
|
||||||
* <register_info versions="V1_2">
|
* <register_info versions="V1_2">
|
||||||
* ...
|
* ...
|
||||||
|
@ -65,23 +67,37 @@ public class GoRegisterInfoManager {
|
||||||
* returned that forces all parameters to be stack allocated.
|
* returned that forces all parameters to be stack allocated.
|
||||||
*
|
*
|
||||||
* @param lang {@link Language}
|
* @param lang {@link Language}
|
||||||
* @param goVersion {@link GoVer} enum
|
* @param goVer {@link GoVer}
|
||||||
* @return {@link GoRegisterInfo}, never null
|
* @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 =
|
Map<GoVer, GoRegisterInfo> perVersionRegInfos =
|
||||||
cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang));
|
cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang));
|
||||||
GoRegisterInfo registerInfo = perVersionRegInfos.get(goVersion);
|
|
||||||
|
GoRegisterInfo registerInfo = getMatchingRegisterInfo(perVersionRegInfos, goVer);
|
||||||
if (registerInfo == null) {
|
if (registerInfo == null) {
|
||||||
registerInfo = getDefault(lang);
|
registerInfo = getDefault(lang);
|
||||||
perVersionRegInfos.put(goVersion, registerInfo);
|
perVersionRegInfos.put(goVer, registerInfo);
|
||||||
int goSize = lang.getInstructionAlignment();
|
int goSize = lang.getInstructionAlignment();
|
||||||
Msg.warn(this, "Missing Golang register info for: " + lang.getLanguageID() +
|
Msg.warn(this, "Missing Golang register info for: %s, defaulting to abi0, size=%d"
|
||||||
", defaulting to abi0, size=" + goSize);
|
.formatted(lang.getLanguageID(), goSize));
|
||||||
}
|
}
|
||||||
return registerInfo;
|
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) {
|
private Map<GoVer, GoRegisterInfo> loadRegisterInfo(Language lang) {
|
||||||
try {
|
try {
|
||||||
ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME);
|
ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME);
|
||||||
|
@ -136,8 +152,9 @@ public class GoRegisterInfoManager {
|
||||||
Element stackElem = regInfoElem.getChild("stack");
|
Element stackElem = regInfoElem.getChild("stack");
|
||||||
Element goRoutineElem = regInfoElem.getChild("current_goroutine");
|
Element goRoutineElem = regInfoElem.getChild("current_goroutine");
|
||||||
Element zeroRegElem = regInfoElem.getChild("zero_register");
|
Element zeroRegElem = regInfoElem.getChild("zero_register");
|
||||||
|
Element duffZeroElem = regInfoElem.getChild("duffzero");
|
||||||
if (intRegsElem == null || floatRegsElem == null || stackElem == null ||
|
if (intRegsElem == null || floatRegsElem == null || stackElem == null ||
|
||||||
goRoutineElem == null || zeroRegElem == null) {
|
goRoutineElem == null || zeroRegElem == null || duffZeroElem == null) {
|
||||||
throw new IOException("Bad format");
|
throw new IOException("Bad format");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,9 +172,13 @@ public class GoRegisterInfoManager {
|
||||||
boolean zeroRegIsBuiltin =
|
boolean zeroRegIsBuiltin =
|
||||||
XmlUtilities.parseOptionalBooleanAttr(zeroRegElem, "builtin", false);
|
XmlUtilities.parseOptionalBooleanAttr(zeroRegElem, "builtin", false);
|
||||||
|
|
||||||
GoRegisterInfo registerInfo =
|
Register duffzeroDest = parseRegStr(duffZeroElem.getAttributeValue("dest"), lang);
|
||||||
new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign,
|
Register duffzeroZero = parseRegStr(duffZeroElem.getAttributeValue("zero_arg"), lang);
|
||||||
currentGoRoutineReg, zeroReg, zeroRegIsBuiltin);
|
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<>();
|
Map<GoVer, GoRegisterInfo> result = new HashMap<>();
|
||||||
for (GoVer goVer : validGoVersions) {
|
for (GoVer goVer : validGoVersions) {
|
||||||
result.put(goVer, registerInfo);
|
result.put(goVer, registerInfo);
|
||||||
|
@ -167,7 +188,8 @@ public class GoRegisterInfoManager {
|
||||||
|
|
||||||
private GoRegisterInfo getDefault(Language lang) {
|
private GoRegisterInfo getDefault(Language lang) {
|
||||||
int goSize = lang.getInstructionAlignment();
|
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 {
|
private List<Register> parseRegListStr(String s, Language lang) throws IOException {
|
||||||
|
@ -196,26 +218,33 @@ public class GoRegisterInfoManager {
|
||||||
return register;
|
return register;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<GoVer> parseValidGoVersionsStr(String s) throws IOException {
|
private RegType parseRegTypeStr(String s) {
|
||||||
if (s.trim().equalsIgnoreCase("all")) {
|
return switch (Objects.requireNonNullElse(s, "int").toLowerCase()) {
|
||||||
EnumSet<GoVer> allVers = EnumSet.allOf(GoVer.class);
|
default -> RegType.INT;
|
||||||
allVers.remove(GoVer.UNKNOWN);
|
case "float" -> RegType.FLOAT;
|
||||||
return allVers;
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(",")) {
|
for (String verStr : s.split(",")) {
|
||||||
verStr = verStr.trim();
|
verStr = verStr.trim();
|
||||||
if (verStr.isEmpty()) {
|
if (verStr.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
if (verStr.startsWith("V")) {
|
||||||
GoVer ver = GoVer.valueOf(verStr);
|
verStr = verStr.substring(1).replace('_', '.'); // convert "V1_1" -> "1.1"
|
||||||
result.add(ver);
|
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
GoVer ver = GoVer.parse(verStr);
|
||||||
|
if (ver.isInvalid()) {
|
||||||
throw new IOException("Unknown go version: " + verStr);
|
throw new IOException("Unknown go version: " + verStr);
|
||||||
}
|
}
|
||||||
|
result.add(ver);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,31 +15,40 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.util.bin.format.golang;
|
package ghidra.app.util.bin.format.golang;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Golang version numbers
|
* Golang version numbers
|
||||||
*/
|
*/
|
||||||
public enum GoVer {
|
public class GoVer implements Comparable<GoVer> {
|
||||||
UNKNOWN(0, 0),
|
public static final GoVer INVALID = new GoVer(0, 0);
|
||||||
V1_2(1, 2),
|
public static final GoVer ANY = new GoVer(-1, -1);
|
||||||
V1_16(1, 16),
|
|
||||||
V1_17(1, 17),
|
// a couple of well-known versions that are re-used in a few places
|
||||||
V1_18(1, 18),
|
public static final GoVer V1_2 = new GoVer(1, 2);
|
||||||
V1_19(1, 19),
|
public static final GoVer V1_16 = new GoVer(1, 16);
|
||||||
V1_20(1, 20),
|
public static final GoVer V1_17 = new GoVer(1, 17);
|
||||||
V1_21(1, 21),
|
public static final GoVer V1_18 = new GoVer(1, 18);
|
||||||
V1_22(1, 22);
|
|
||||||
|
|
||||||
private final int major;
|
private final int major;
|
||||||
private final int minor;
|
private final int minor;
|
||||||
|
|
||||||
GoVer(int major, int minor) {
|
public GoVer(int major, int minor) {
|
||||||
this.major = major;
|
this.major = major;
|
||||||
this.minor = minor;
|
this.minor = minor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInvalid() {
|
||||||
|
return major == 0 && minor == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWildcard() {
|
||||||
|
return major == -1 && minor == -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Major value
|
* Major value
|
||||||
*
|
*
|
||||||
|
@ -58,6 +67,15 @@ public enum GoVer {
|
||||||
return minor;
|
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
|
* Compares this version to the specified other version and returns true if this version
|
||||||
* is greater than or equal to the other 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
|
* @return true if this version is gte other version
|
||||||
*/
|
*/
|
||||||
public boolean isAtLeast(GoVer otherVersion) {
|
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
|
* Returns true if this version is between the specified min and max versions (inclusive).
|
||||||
* UNKNOWN if no matching version or bad data.
|
*
|
||||||
|
* @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
|
* @param s string to parse
|
||||||
* @return GoVer enum instance, or UNKNOWN
|
* @return GoVer instance, or INVALID
|
||||||
*/
|
*/
|
||||||
public static GoVer parse(String s) {
|
public static GoVer parse(String s) {
|
||||||
String[] parts = s.split("\\.");
|
String[] parts = Objects.requireNonNullElse(s, "").split("\\.");
|
||||||
if (parts.length < 2) {
|
if (parts.length < 2) {
|
||||||
return UNKNOWN;
|
return INVALID;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int major = Integer.parseInt(parts[0]);
|
int major = Integer.parseInt(parts[0]);
|
||||||
int minor = Integer.parseInt(parts[1]);
|
int minor = Integer.parseInt(parts[1]);
|
||||||
for (GoVer ver : values()) {
|
//don't care about patch level right now
|
||||||
if (ver.major == major && ver.minor == minor) {
|
return new GoVer(major, minor);
|
||||||
return ver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
// fall thru, return unknown
|
// fall thru, return unknown
|
||||||
}
|
}
|
||||||
return UNKNOWN;
|
return INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version";
|
public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version";
|
||||||
public static GoVer fromProgramProperties(Program program) {
|
public static GoVer fromProgramProperties(Program program) {
|
||||||
Options props = program.getOptions(Program.PROGRAM_INFO);
|
Options props = program.getOptions(Program.PROGRAM_INFO);
|
||||||
String verStr = props.getString(GOLANG_VERSION_PROPERTY_NAME, null);
|
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) {
|
public static void setProgramPropertiesWithOriginalVersionString(Options props, String s) {
|
||||||
props.setString(GOLANG_VERSION_PROPERTY_NAME, 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.rtti.types.GoMethod.GoMethodInfo;
|
||||||
import ghidra.app.util.bin.format.golang.structmapping.*;
|
import ghidra.app.util.bin.format.golang.structmapping.*;
|
||||||
import ghidra.program.model.address.*;
|
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.program.model.listing.Function;
|
||||||
import ghidra.util.NumericUtilities;
|
import ghidra.util.NumericUtilities;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
@ -39,19 +40,19 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
@ContextField
|
@ContextField
|
||||||
private StructureContext<GoFuncData> context;
|
private StructureContext<GoFuncData> context;
|
||||||
|
|
||||||
@FieldMapping(optional = true, fieldName = { "entryoff", "entryOff" })
|
@FieldMapping(presentWhen = "1.18+", fieldName = { "entryoff", "entryOff" })
|
||||||
@EOLComment("getDescription")
|
@EOLComment("getDescription")
|
||||||
@MarkupReference("getFuncAddress")
|
@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")
|
@EOLComment("getDescription")
|
||||||
@MarkupReference("getFuncAddress")
|
@MarkupReference("getFuncAddress")
|
||||||
private long entry; // valid in <=1.17, location of function
|
private long entry; // absolute location of function
|
||||||
|
|
||||||
@FieldMapping(fieldName = { "nameoff", "nameOff" })
|
@FieldMapping(fieldName = { "nameoff", "nameOff" })
|
||||||
@MarkupReference("getNameAddress")
|
@MarkupReference("getNameAddress")
|
||||||
private long nameoff;
|
private long nameoff; // uint32
|
||||||
|
|
||||||
//private long args; // size of arguments
|
//private long args; // size of arguments
|
||||||
|
|
||||||
|
@ -64,14 +65,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
@FieldMapping
|
@FieldMapping
|
||||||
private int npcdata; // number of elements in varlen pcdata array
|
private int npcdata; // number of elements in varlen pcdata array
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
private long cuOffset;
|
private long cuOffset = -1;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping
|
||||||
@EOLComment("getFuncIDEnum")
|
@EOLComment("getFuncIDEnum")
|
||||||
private byte funcID; // see GoFuncID enum
|
private byte funcID; // see GoFuncID enum
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.17+")
|
||||||
@EOLComment("flags")
|
@EOLComment("flags")
|
||||||
private byte flag; // runtime.funcFlag, see GoFuncFlag enum
|
private byte flag; // runtime.funcFlag, see GoFuncFlag enum
|
||||||
|
|
||||||
|
@ -130,13 +131,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
// using the max pc value
|
// using the max pc value
|
||||||
try {
|
try {
|
||||||
long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1;
|
long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1;
|
||||||
return max > entry
|
if (max > entry) {
|
||||||
? new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max))
|
return new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max));
|
||||||
: null;
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
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>
|
* </ul>
|
||||||
* Return value information is unknown and always represented as an "undefined" data type.
|
* 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
|
* indicate the function had 2 8-byte arguments
|
||||||
* @throws IOException if error reading lookup data
|
* @throws IOException if error reading lookup data
|
||||||
*/
|
*/
|
||||||
|
@ -237,18 +239,6 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
return sig.toString();
|
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
|
* Attempts to return a {@link GoMethodInfo} for this function, based on this
|
||||||
* function's inclusion in a golang interface as a method.
|
* function's inclusion in a golang interface as a method.
|
||||||
|
@ -275,9 +265,14 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
*/
|
*/
|
||||||
public Address getNameAddress() {
|
public Address getNameAddress() {
|
||||||
GoModuledata moduledata = getModuledata();
|
GoModuledata moduledata = getModuledata();
|
||||||
return moduledata != null
|
if (moduledata != null) {
|
||||||
? moduledata.getFuncnametab().getArrayAddress().add(nameoff)
|
GoSlice slice = moduledata.getFuncnametab();
|
||||||
: null;
|
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() {
|
public String getName() {
|
||||||
GoModuledata moduledata = getModuledata();
|
GoModuledata moduledata = getModuledata();
|
||||||
try {
|
|
||||||
if (moduledata != null) {
|
if (moduledata != null) {
|
||||||
return programContext
|
try {
|
||||||
.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff)
|
GoSlice slice = moduledata.getFuncnametab();
|
||||||
.readNextUtf8String();
|
if (slice == null) {
|
||||||
|
slice = moduledata.getPclntable();
|
||||||
}
|
}
|
||||||
|
return slice.getElementReader(1, (int) nameoff).readNextUtf8String();
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
// fall thru
|
// fall thru
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return "unknown_func_%x_%s".formatted(context.getStructureStart(),
|
return "unknown_func_%x_%s".formatted(context.getStructureStart(),
|
||||||
funcAddress != null ? funcAddress : "missing_addr");
|
funcAddress != null ? funcAddress : "missing_addr");
|
||||||
}
|
}
|
||||||
|
@ -369,11 +366,25 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
|
||||||
int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry);
|
int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry);
|
||||||
int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry);
|
int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry);
|
||||||
|
|
||||||
long fileoff = fileno >= 0
|
if (fileno < 0) {
|
||||||
? moduledata.getCutab()
|
return null;
|
||||||
.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno)
|
}
|
||||||
: -1;
|
|
||||||
String fileName = fileoff != -1 ? moduledata.getFilename(fileoff) : 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;
|
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
|
|
||||||
*/
|
|
||||||
|
|
|
@ -32,13 +32,13 @@ public class GoFunctabEntry {
|
||||||
@ContextField
|
@ContextField
|
||||||
private StructureContext<GoFunctabEntry> context;
|
private StructureContext<GoFunctabEntry> context;
|
||||||
|
|
||||||
@FieldMapping(optional = true)
|
@FieldMapping(presentWhen = "1.18+")
|
||||||
@MarkupReference("getFuncAddress")
|
@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")
|
@MarkupReference("getFuncAddress")
|
||||||
private long entry; // valid in <=1.17, location of function
|
private long entry; // absolute location of function
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping
|
||||||
@MarkupReference("getFuncData")
|
@MarkupReference("getFuncData")
|
||||||
|
|
|
@ -44,9 +44,27 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
@ContextField
|
@ContextField
|
||||||
private StructureContext<GoModuledata> structureContext;
|
private StructureContext<GoModuledata> structureContext;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference
|
@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
|
@FieldMapping
|
||||||
private long data;
|
private long data;
|
||||||
|
@ -82,24 +100,6 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
@FieldMapping(fieldName = "typelinks")
|
@FieldMapping(fieldName = "typelinks")
|
||||||
private GoSlice 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
|
@FieldMapping
|
||||||
private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab)
|
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
|
* Compares the data in this structure to fields in a GoPcHeader and returns true if they
|
||||||
* match.
|
* match.
|
||||||
*
|
*
|
||||||
* @param pclntab GoPcHeader instance
|
* @param otherPcHeader GoPcHeader instance
|
||||||
* @return boolean true if match, false if no match
|
* @return boolean true if match, false if no match
|
||||||
*/
|
*/
|
||||||
public boolean matchesPclntab(GoPcHeader pclntab) {
|
public boolean matchesPcHeader(GoPcHeader otherPcHeader) {
|
||||||
return (!pclntab.hasTextStart() || pclntab.getTextStart().equals(getText())) &&
|
return (!otherPcHeader.hasTextStart() || otherPcHeader.getTextStart().equals(getText())) &&
|
||||||
pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress());
|
otherPcHeader.getFuncnameAddress().equals(funcnametab.getArrayAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Markup
|
@Markup
|
||||||
public GoPcHeader getPcHeader() throws IOException {
|
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.
|
// all these static slices should be allocated with len == cap. If not true, fail.
|
||||||
if (!typeLinks.isFull() || !filetab.isFull() || !pctab.isFull() || !pclntable.isFull() ||
|
if (!typeLinks.isFull() || !filetab.isFull() || (pctab != null && !pctab.isFull()) ||
|
||||||
!ftab.isFull()) {
|
!pclntable.isFull() || !ftab.isFull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,15 +295,8 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
return filetab;
|
return filetab;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public GoSlice getPclntable() {
|
||||||
* Returns the filename at the specified offset.
|
return pclntable;
|
||||||
*
|
|
||||||
* @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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -307,6 +308,10 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
return pctab;
|
return pctab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GoSlice getPcValueTable() {
|
||||||
|
return pctab != null ? pctab : pclntable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the controlling {@link GoRttiMapper go binary} context.
|
* 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);
|
itablinks.markupArray("moduledata.itablinks", null, GoItab.class, true, session);
|
||||||
|
|
||||||
|
if (funcnametab != null) {
|
||||||
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
|
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
|
||||||
|
}
|
||||||
markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
|
markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
|
||||||
|
|
||||||
GoSlice subSlice = getFunctabEntriesSlice();
|
GoSlice subSlice = getFunctabEntriesSlice();
|
||||||
|
@ -441,15 +448,15 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
* Searches memory for a likely GoModuledata structure.
|
* Searches memory for a likely GoModuledata structure.
|
||||||
*
|
*
|
||||||
* @param context already initialized {@link GoRttiMapper}
|
* @param context already initialized {@link GoRttiMapper}
|
||||||
* @param pclntabAddress address of an already found {@link GoPcHeader}
|
* @param pcHeaderAddress address of an already found {@link GoPcHeader}
|
||||||
* @param pclntab the {@link GoPcHeader}
|
* @param pcHeader the {@link GoPcHeader}
|
||||||
* @param range memory range to search. Will be different for different types of binaries
|
* @param range memory range to search. Will be different for different types of binaries
|
||||||
* @param monitor {@link TaskMonitor}
|
* @param monitor {@link TaskMonitor}
|
||||||
* @return new GoModuledata instance, or null if not found
|
* @return new GoModuledata instance, or null if not found
|
||||||
* @throws IOException if error reading found structure
|
* @throws IOException if error reading found structure
|
||||||
*/
|
*/
|
||||||
/* package */ static GoModuledata findFirstModule(GoRttiMapper context,
|
/* package */ static GoModuledata findFirstModule(GoRttiMapper context,
|
||||||
Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor)
|
Address pcHeaderAddress, GoPcHeader pcHeader, AddressRange range, TaskMonitor monitor)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -462,7 +469,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
|
||||||
// field of the GoModuledata structure.
|
// field of the GoModuledata structure.
|
||||||
int ptrSize = context.getPtrSize();
|
int ptrSize = context.getPtrSize();
|
||||||
byte[] searchBytes = new byte[ptrSize];
|
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(),
|
Address moduleAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
|
||||||
searchBytes, null, true, monitor);
|
searchBytes, null, true, monitor);
|
||||||
if (moduleAddr == null) {
|
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
|
// Verify that we read a good GoModuledata struct by comparing some of its values to
|
||||||
// the pclntab structure.
|
// 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()*/
|
|
||||||
|
|
|
@ -22,21 +22,26 @@ import ghidra.app.util.bin.format.golang.GoVer;
|
||||||
import ghidra.app.util.bin.format.golang.structmapping.*;
|
import ghidra.app.util.bin.format.golang.structmapping.*;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.lang.Endian;
|
import ghidra.program.model.lang.Endian;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
import ghidra.util.BigEndianDataConverter;
|
||||||
|
import ghidra.util.LittleEndianDataConverter;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A low-level structure embedded in golang binaries that contains useful bootstrapping
|
* A low-level structure embedded in golang binaries that contains useful bootstrapping
|
||||||
* information.
|
* information.
|
||||||
* <p>
|
* <p>
|
||||||
|
* Introduced in golang 1.16
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@StructureMapping(structureName = "runtime.pcHeader")
|
@StructureMapping(structureName = GoPcHeader.GO_STRUCTURE_NAME)
|
||||||
public class GoPcHeader {
|
public class GoPcHeader {
|
||||||
|
public static final String GO_STRUCTURE_NAME = "runtime.pcHeader";
|
||||||
private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab";
|
private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab";
|
||||||
public static final String GOPCLNTAB_SECTION_NAME = "gopclntab";
|
public static final String GOPCLNTAB_SECTION_NAME = "gopclntab";
|
||||||
public static final int GO_1_2_MAGIC = 0xfffffffb;
|
public static final int GO_1_2_MAGIC = 0xfffffffb;
|
||||||
|
@ -49,29 +54,27 @@ public class GoPcHeader {
|
||||||
* @param program {@link Program}
|
* @param program {@link Program}
|
||||||
* @return {@link Address} of go pclntab, or null if not present
|
* @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);
|
MemoryBlock pclntabBlock = GoRttiMapper.getGoSection(program, GOPCLNTAB_SECTION_NAME);
|
||||||
if (pclntabBlock != null) {
|
if (pclntabBlock != null) {
|
||||||
return pclntabBlock.getStart();
|
return pclntabBlock.getStart();
|
||||||
}
|
}
|
||||||
// PE binaries have a symbol instead of a named section
|
// PE binaries have a symbol instead of a named section
|
||||||
Symbol pclntabSymbol = GoRttiMapper.getGoSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME);
|
Symbol pclntabSymbol = GoRttiMapper.getGoSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME);
|
||||||
return pclntabSymbol != null
|
return pclntabSymbol != null ? pclntabSymbol.getAddress() : 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}
|
* @param program {@link Program}
|
||||||
* @return boolean true if program has a pclntab, false otherwise
|
* @return boolean true if program has a pclntab, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean hasPclntab(Program program) {
|
public static boolean hasPcHeader(Program program) {
|
||||||
Address addr = getPclntabAddress(program);
|
Address addr = getPcHeaderAddress(program);
|
||||||
if (addr != null) {
|
if (addr != null) {
|
||||||
try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) {
|
try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) {
|
||||||
return isPclntab(provider);
|
return isPcHeader(provider);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
// fall thru
|
// fall thru
|
||||||
|
@ -81,43 +84,43 @@ public class GoPcHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches (possibly slowly) for a pclntab structure in the specified memory range, which
|
* Searches (possibly slowly) for a pclntab/pcHeader structure in the specified memory range,
|
||||||
* is typically necessary in stripped PE binaries.
|
* which is typically necessary in stripped PE binaries.
|
||||||
*
|
*
|
||||||
* @param programContext {@link GoRttiMapper}
|
* @param programContext {@link GoRttiMapper}
|
||||||
* @param range memory range to search (typically .rdata or .noptrdata sections)
|
* @param range memory range to search (typically .rdata or .noptrdata sections)
|
||||||
* @param monitor {@link TaskMonitor} that will let the user cancel
|
* @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
|
* @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 {
|
TaskMonitor monitor) throws IOException {
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// search for magic signature + padding + wildcard_minLc + ptrSize
|
// 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
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // magic signature
|
||||||
0, 0, // padding
|
0, 0, // padding
|
||||||
0, // unknown minLc, masked
|
0, // unknown minLc, masked
|
||||||
(byte) programContext.getPtrSize() // ptrSize
|
(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) 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
|
(byte) 0xff, (byte) 0xff, // padding
|
||||||
0, // unknown minLc - wildcarded
|
0, // unknown minLc - wildcarded
|
||||||
(byte) 0xff // ptrSize
|
(byte) 0xff // ptrSize
|
||||||
};
|
};
|
||||||
Memory memory = programContext.getProgram().getMemory();
|
Memory memory = programContext.getProgram().getMemory();
|
||||||
Address pclntabAddr =
|
Address pcHeaderAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
|
||||||
memory.findBytes(range.getMinAddress(), range.getMaxAddress(), searchBytes, searchMask,
|
searchBytes, searchMask, true, monitor);
|
||||||
true, monitor);
|
if (pcHeaderAddr == null) {
|
||||||
if (pclntabAddr == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
MemoryByteProvider bp =
|
try (MemoryByteProvider bp =
|
||||||
new MemoryByteProvider(memory, pclntabAddr, range.getMaxAddress());
|
new MemoryByteProvider(memory, pcHeaderAddr, range.getMaxAddress())) {
|
||||||
return isPclntab(bp) ? pclntabAddr : null;
|
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
|
* @return boolean true if the byte provider has the magic signature of a pclntab
|
||||||
* @throws IOException if error reading
|
* @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);
|
byte[] header = provider.readBytes(0, 8);
|
||||||
// logic from pclntab.go parsePclnTab()
|
// logic from pclntab.go parsePclnTab()
|
||||||
if (provider.length() < 16 ||
|
if (provider.length() < 16 || header[4] != 0 || header[5] != 0 || // pad bytes == 0
|
||||||
header[4] != 0 || header[5] != 0 || // pad bytes == 0
|
|
||||||
(header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4
|
(header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4
|
||||||
(header[7] != 4 && header[7] != 8) // ptrSize == 4,8
|
(header[7] != 4 && header[7] != 8) // ptrSize == 4,8
|
||||||
) {
|
) {
|
||||||
|
@ -140,6 +142,23 @@ public class GoPcHeader {
|
||||||
return readMagic(provider) != null;
|
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
|
@ContextField
|
||||||
private GoRttiMapper programContext;
|
private GoRttiMapper programContext;
|
||||||
|
|
||||||
|
@ -156,27 +175,27 @@ public class GoPcHeader {
|
||||||
@FieldMapping
|
@FieldMapping
|
||||||
private byte ptrSize;
|
private byte ptrSize;
|
||||||
|
|
||||||
@FieldMapping(optional = true) // present >= 1.18
|
@FieldMapping(presentWhen = "1.18+")
|
||||||
@MarkupReference
|
@MarkupReference
|
||||||
private long textStart; // should be same as offset of ".text"
|
private long textStart; // should be same as offset of ".text"
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference("getFuncnameAddress")
|
@MarkupReference("getFuncnameAddress")
|
||||||
private long funcnameOffset;
|
private long funcnameOffset;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference("getCuAddress")
|
@MarkupReference("getCuAddress")
|
||||||
private long cuOffset;
|
private long cuOffset;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference("getFiletabAddress")
|
@MarkupReference("getFiletabAddress")
|
||||||
private long filetabOffset;
|
private long filetabOffset;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference("getPctabAddress")
|
@MarkupReference("getPctabAddress")
|
||||||
private long pctabOffset;
|
private long pctabOffset;
|
||||||
|
|
||||||
@FieldMapping
|
@FieldMapping(presentWhen = "1.16+")
|
||||||
@MarkupReference("getPclnAddress")
|
@MarkupReference("getPclnAddress")
|
||||||
private long pclnOffset;
|
private long pclnOffset;
|
||||||
|
|
||||||
|
@ -187,7 +206,7 @@ public class GoPcHeader {
|
||||||
case GO_1_2_MAGIC -> GoVer.V1_2;
|
case GO_1_2_MAGIC -> GoVer.V1_2;
|
||||||
case GO_1_16_MAGIC -> GoVer.V1_16;
|
case GO_1_16_MAGIC -> GoVer.V1_16;
|
||||||
case GO_1_18_MAGIC -> GoVer.V1_18;
|
case GO_1_18_MAGIC -> GoVer.V1_18;
|
||||||
default -> GoVer.UNKNOWN;
|
default -> GoVer.INVALID;
|
||||||
};
|
};
|
||||||
return ver;
|
return ver;
|
||||||
}
|
}
|
||||||
|
@ -214,7 +233,9 @@ public class GoPcHeader {
|
||||||
* @return address of func name slice
|
* @return address of func name slice
|
||||||
*/
|
*/
|
||||||
public Address getFuncnameAddress() {
|
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
|
* @return address of the cu tab slice
|
||||||
*/
|
*/
|
||||||
public Address getCuAddress() {
|
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
|
* @return address of the filetab slice
|
||||||
*/
|
*/
|
||||||
public Address getFiletabAddress() {
|
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
|
* @return address of the pctab slice
|
||||||
*/
|
*/
|
||||||
public Address getPctabAddress() {
|
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
|
* @return address of the pcln slice
|
||||||
*/
|
*/
|
||||||
public Address getPclnAddress() {
|
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 {
|
private static GoVerEndian readMagic(ByteProvider provider) throws IOException {
|
||||||
int leMagic = new BinaryReader(provider, true /* little endian */).readInt(0);
|
BinaryReader reader = new BinaryReader(provider, true);
|
||||||
int beMagic = new BinaryReader(provider, false /* big endian */).readInt(0);
|
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) {
|
if (leMagic == GO_1_2_MAGIC || beMagic == GO_1_2_MAGIC) {
|
||||||
return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC);
|
return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC);
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class GoPcValueEvaluator {
|
||||||
GoModuledata moduledata = func.getModuledata();
|
GoModuledata moduledata = func.getModuledata();
|
||||||
|
|
||||||
this.pcquantum = moduledata.getGoBinary().getMinLC();
|
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.funcEntry = func.getFuncAddress().getOffset();
|
||||||
this.pc = funcEntry;
|
this.pc = funcEntry;
|
||||||
|
|
|
@ -78,7 +78,13 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
|
||||||
* </ul>
|
* </ul>
|
||||||
* </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";
|
private static final String FAILED_FLAG = "FAILED TO FIND GOLANG BINARY";
|
||||||
|
|
||||||
|
@ -163,20 +169,24 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
GoVer goVer = buildInfo.getVerEnum();
|
GoVer goVer = buildInfo.getGoVer();
|
||||||
if (goVer == GoVer.UNKNOWN) {
|
if (goVer.isInvalid()) {
|
||||||
throw new BootstrapInfoException(
|
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 =
|
ResourceFile gdtFile =
|
||||||
findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program));
|
findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program));
|
||||||
if (gdtFile == null) {
|
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(),
|
return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), goVer,
|
||||||
buildInfo.getVerEnum(), gdtFile);
|
gdtFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,10 +277,6 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
return false;
|
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.
|
* Returns a matching symbol from the specified program, using golang specific logic.
|
||||||
*
|
*
|
||||||
|
@ -328,6 +334,16 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
return zerobaseAddr;
|
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 =
|
public final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME =
|
||||||
"ARTIFICIAL.runtime.zerobase";
|
"ARTIFICIAL.runtime.zerobase";
|
||||||
|
|
||||||
|
@ -415,7 +431,7 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
this.stringDT = getTypeOrDefault("string", Structure.class, null);
|
this.stringDT = getTypeOrDefault("string", Structure.class, null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerStructures(GOLANG_STRUCTMAPPED_CLASSES);
|
registerStructures(GOLANG_STRUCTMAPPED_CLASSES, this);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
if (archiveGDT == null) {
|
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
|
* Returns the golang version
|
||||||
* @return {@link GoVer}
|
* @return {@link GoVer}
|
||||||
|
@ -462,11 +492,13 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
GoModuledata firstModule = findFirstModuledata(monitor);
|
GoModuledata firstModule = findFirstModuledata(monitor);
|
||||||
if (firstModule != null) {
|
if (firstModule != null) {
|
||||||
GoPcHeader pcHeader = firstModule.getPcHeader();
|
GoPcHeader pcHeader = firstModule.getPcHeader();
|
||||||
|
if (pcHeader != null) {
|
||||||
this.minLC = pcHeader.getMinLC();
|
this.minLC = pcHeader.getMinLC();
|
||||||
if (pcHeader.getPtrSize() != ptrSize) {
|
if (pcHeader.getPtrSize() != ptrSize) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize));
|
"Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
addModule(firstModule);
|
addModule(firstModule);
|
||||||
}
|
}
|
||||||
initFuncdata();
|
initFuncdata();
|
||||||
|
@ -1401,22 +1433,22 @@ public class GoRttiMapper extends DataTypeMapper {
|
||||||
|
|
||||||
private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException {
|
private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException {
|
||||||
GoModuledata result = GoModuledata.getFirstModuledata(this);
|
GoModuledata result = GoModuledata.getFirstModuledata(this);
|
||||||
if (result == null) {
|
Address pcHeaderAddress =
|
||||||
monitor.setMessage("Searching for Golang pclntab");
|
result != null ? result.getPcHeaderAddress() : GoPcHeader.getPcHeaderAddress(program);
|
||||||
monitor.initialize(0);
|
if (pcHeaderAddress == null) {
|
||||||
Address pclntabAddress = GoPcHeader.getPclntabAddress(program);
|
monitor.initialize(0, "Searching for Golang pclntab");
|
||||||
if (pclntabAddress == null) {
|
pcHeaderAddress =
|
||||||
pclntabAddress =
|
GoPcHeader.findPcHeaderAddress(this, getPclntabSearchRange(), monitor);
|
||||||
GoPcHeader.findPclntabAddress(this, getPclntabSearchRange(), monitor);
|
|
||||||
}
|
}
|
||||||
if (pclntabAddress != null) {
|
if (result == null && pcHeaderAddress != null) {
|
||||||
monitor.setMessage("Searching for Golang firstmoduledata");
|
// find the moduledata struct by searching for a pointer to the pclntab/pcHeader,
|
||||||
monitor.initialize(0);
|
// which should be the first field in the moduledata struct.
|
||||||
GoPcHeader pclntab = readStructure(GoPcHeader.class, pclntabAddress);
|
monitor.initialize(0, "Searching for Golang firstmoduledata");
|
||||||
result = GoModuledata.findFirstModule(this, pclntabAddress, pclntab,
|
GoPcHeader pcHeader = readStructure(GoPcHeader.class, pcHeaderAddress);
|
||||||
|
result = GoModuledata.findFirstModule(this, pcHeaderAddress, pcHeader,
|
||||||
getModuledataSearchRange(), monitor);
|
getModuledataSearchRange(), monitor);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (result != null && !result.isValid()) {
|
if (result != null && !result.isValid()) {
|
||||||
throw new IOException("Invalid Golang moduledata at %s"
|
throw new IOException("Invalid Golang moduledata at %s"
|
||||||
.formatted(result.getStructureContext().getStructureAddress()));
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,14 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import ghidra.app.util.bin.BinaryReader;
|
import ghidra.app.util.bin.BinaryReader;
|
||||||
|
import ghidra.app.util.bin.format.golang.GoVer;
|
||||||
import ghidra.app.util.bin.format.golang.structmapping.*;
|
import ghidra.app.util.bin.format.golang.structmapping.*;
|
||||||
import ghidra.program.model.data.*;
|
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>
|
* <p>
|
||||||
* Used mainly in lower-level RTTI structures, this class is a ghidra'ism used to parse the
|
* 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.
|
* 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());
|
readFrom(context.getReader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean useLEB128() {
|
||||||
|
return ((GoRttiMapper) context.getDataTypeMapper()).getGolangVersion()
|
||||||
|
.isAtLeast(GoVer.V1_17);
|
||||||
|
}
|
||||||
|
|
||||||
private void readFrom(BinaryReader reader) throws IOException {
|
private void readFrom(BinaryReader reader) throws IOException {
|
||||||
long startPos = reader.getPointerIndex();
|
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.strlenLen = (int) (reader.getPointerIndex() - startPos);
|
||||||
this.bytes = reader.readNextByteArray(strLen);
|
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() {
|
public int getStrlenLen() {
|
||||||
return strlenLen;
|
return strlenLen;
|
||||||
|
@ -100,8 +110,11 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
|
||||||
* @return data type needed to hold the string length field
|
* @return data type needed to hold the string length field
|
||||||
*/
|
*/
|
||||||
public DataTypeInstance getStrlenDataType() {
|
public DataTypeInstance getStrlenDataType() {
|
||||||
return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen,
|
DataType dt = useLEB128()
|
||||||
false);
|
? 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]",
|
return String.format("GoVarlenString [context=%s, strlenLen=%s, bytes=%s, getString()=%s]",
|
||||||
context, strlenLen, Arrays.toString(bytes), getString());
|
context, strlenLen, Arrays.toString(bytes), getString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import ghidra.app.util.bin.format.golang.structmapping.*;
|
import ghidra.app.util.bin.format.golang.structmapping.*;
|
||||||
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
|
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
|
||||||
import ghidra.program.model.data.ArrayDataType;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.data.DataType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link GoType} structure that defines an array.
|
* {@link GoType} structure that defines an array.
|
||||||
|
@ -72,7 +71,15 @@ public class GoArrayType extends GoType {
|
||||||
if (self != null) {
|
if (self != null) {
|
||||||
return self;
|
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
|
@Override
|
||||||
|
|
|
@ -42,10 +42,10 @@ public class GoStructField {
|
||||||
@MarkupReference("getType")
|
@MarkupReference("getType")
|
||||||
private long typ; // direct ptr to GoType
|
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
|
private long offsetAnon; // offsetAnon >> 1 == actual offset, bit 0 = embedded flag
|
||||||
|
|
||||||
@FieldMapping(optional = true) //>=1.19
|
@FieldMapping(presentWhen = "1.19+")
|
||||||
private long offset;
|
private long offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
/**
|
/**
|
||||||
* Information about {@link StructureMapping} classes and their metadata.
|
* Information about {@link StructureMapping} classes and their metadata.
|
||||||
* <p>
|
* <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™, a DataTypeMapper must be created. It
|
||||||
* must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find}
|
* must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find}
|
||||||
* ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data
|
* ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data
|
||||||
* types being used, and it must {@link #registerStructure(Class) know} about all classes that are
|
* 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 <T> structure mapped class type
|
||||||
* @param clazz class that represents a structure, marked with {@link StructureMapping}
|
* @param clazz class that represents a structure, marked with {@link StructureMapping}
|
||||||
* annotation
|
* annotation
|
||||||
|
* @param context {@link DataTypeMapperContext}
|
||||||
* @throws IOException if the class's Ghidra structure data type could not be found
|
* @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);
|
StructureMapping sma = clazz.getAnnotation(StructureMapping.class);
|
||||||
List<String> structNames = sma != null ? Arrays.asList(sma.structureName()) : List.of();
|
List<String> structNames = sma != null ? Arrays.asList(sma.structureName()) : List.of();
|
||||||
Structure structDT = getType(structNames, Structure.class);
|
Structure structDT = getType(structNames, Structure.class);
|
||||||
|
@ -187,7 +189,7 @@ public class DataTypeMapper implements AutoCloseable {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StructureMappingInfo<T> structMappingInfo =
|
StructureMappingInfo<T> structMappingInfo =
|
||||||
StructureMappingInfo.fromClass(clazz, structDT);
|
StructureMappingInfo.fromClass(clazz, structDT, context);
|
||||||
mappingInfo.put(clazz, structMappingInfo);
|
mappingInfo.put(clazz, structMappingInfo);
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
|
@ -199,11 +201,13 @@ public class DataTypeMapper implements AutoCloseable {
|
||||||
* Registers the specified {@link StructureMapping structure mapping} classes.
|
* Registers the specified {@link StructureMapping structure mapping} classes.
|
||||||
*
|
*
|
||||||
* @param classes list of classes to register
|
* @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
|
* @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) {
|
for (Class<?> clazz : classes) {
|
||||||
registerStructure(clazz);
|
registerStructure(clazz, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,5 +548,4 @@ public class DataTypeMapper implements AutoCloseable {
|
||||||
}
|
}
|
||||||
return new StructureContext<>(this, smi, null);
|
return new StructureContext<>(this, smi, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -62,6 +62,22 @@ public @interface FieldMapping {
|
||||||
*/
|
*/
|
||||||
boolean optional() default false;
|
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
|
* Specifies the name of a setter method that will be used to assign the deserialized value
|
||||||
* to the java field.
|
* to the java field.
|
||||||
|
|
|
@ -39,18 +39,19 @@ public class StructureMappingInfo<T> {
|
||||||
* @param targetClass structure mapped class
|
* @param targetClass structure mapped class
|
||||||
* @param structDataType Ghidra {@link DataType} that defines the binary layout of the mapped
|
* @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
|
* 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
|
* @return new {@link StructureMappingInfo} for the specified class
|
||||||
* @throws IllegalArgumentException if targetClass isn't tagged as a structure mapped class
|
* @throws IllegalArgumentException if targetClass isn't tagged as a structure mapped class
|
||||||
*/
|
*/
|
||||||
public static <T> StructureMappingInfo<T> fromClass(Class<T> targetClass,
|
public static <T> StructureMappingInfo<T> fromClass(Class<T> targetClass,
|
||||||
Structure structDataType) {
|
Structure structDataType, DataTypeMapperContext context) {
|
||||||
StructureMapping sma = targetClass.getAnnotation(StructureMapping.class);
|
StructureMapping sma = targetClass.getAnnotation(StructureMapping.class);
|
||||||
if (sma == null) {
|
if (sma == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Missing @StructureMapping annotation on " + targetClass.getSimpleName());
|
"Missing @StructureMapping annotation on " + targetClass.getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new StructureMappingInfo<>(targetClass, structDataType, sma);
|
return new StructureMappingInfo<>(targetClass, structDataType, sma, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Class<T> targetClass;
|
private final Class<T> targetClass;
|
||||||
|
@ -69,17 +70,17 @@ public class StructureMappingInfo<T> {
|
||||||
private Field structureContextField;
|
private Field structureContextField;
|
||||||
|
|
||||||
private StructureMappingInfo(Class<T> targetClass, Structure structDataType,
|
private StructureMappingInfo(Class<T> targetClass, Structure structDataType,
|
||||||
StructureMapping sma) {
|
StructureMapping sma, DataTypeMapperContext context) {
|
||||||
this.targetClass = targetClass;
|
this.targetClass = targetClass;
|
||||||
this.structureDataType = structDataType;
|
this.structureDataType = structDataType;
|
||||||
this.structureName = structureDataType != null
|
this.structureName = structureDataType != null
|
||||||
? structureDataType.getName()
|
? structureDataType.getName()
|
||||||
: sma.structureName()[0];
|
: sma.structureName()[0];
|
||||||
this.fieldNameLookup = indexStructFields(structDataType);
|
this.fieldNameLookup = indexStructFields();
|
||||||
this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass);
|
this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass);
|
||||||
this.instanceCreator = findInstanceCreator();
|
this.instanceCreator = findInstanceCreator();
|
||||||
|
|
||||||
readFieldInfo(targetClass);
|
readFieldInfo(targetClass, context);
|
||||||
Collections.sort(outputFields,
|
Collections.sort(outputFields,
|
||||||
(foi1, foi2) -> Integer.compare(foi1.getOrdinal(), foi2.getOrdinal()));
|
(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();
|
Class<?> superclass = clazz.getSuperclass();
|
||||||
if (superclass != null) {
|
if (superclass != null) {
|
||||||
readFieldInfo(superclass);
|
readFieldInfo(superclass, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Field field : clazz.getDeclaredFields()) {
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
@ -260,7 +261,7 @@ public class StructureMappingInfo<T> {
|
||||||
FieldMapping fma = field.getAnnotation(FieldMapping.class);
|
FieldMapping fma = field.getAnnotation(FieldMapping.class);
|
||||||
FieldOutput foa = field.getAnnotation(FieldOutput.class);
|
FieldOutput foa = field.getAnnotation(FieldOutput.class);
|
||||||
if (fma != null || foa != null) {
|
if (fma != null || foa != null) {
|
||||||
FieldMappingInfo<T> fmi = readFieldMappingInfo(field, fma);
|
FieldMappingInfo<T> fmi = readFieldMappingInfo(field, fma, context);
|
||||||
if (fmi == null) {
|
if (fmi == null) {
|
||||||
// was marked optional field, just skip
|
// was marked optional field, just skip
|
||||||
continue;
|
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);
|
String[] fieldNames = getFieldNamesToSearchFor(field, fma);
|
||||||
DataTypeComponent dtc = getFirstMatchingField(fieldNames);
|
DataTypeComponent dtc = getFirstMatchingField(fieldNames);
|
||||||
if (useFieldMappingInfo && dtc == null) {
|
if (useFieldMappingInfo && dtc == null) {
|
||||||
|
@ -379,12 +387,12 @@ public class StructureMappingInfo<T> {
|
||||||
return struct.isZeroLength() ? 0 : struct.getLength();
|
return struct.isZeroLength() ? 0 : struct.getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, DataTypeComponent> indexStructFields(Structure struct) {
|
private Map<String, DataTypeComponent> indexStructFields() {
|
||||||
if (struct == null) {
|
if (structureDataType == null) {
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
Map<String, DataTypeComponent> result = new HashMap<>();
|
Map<String, DataTypeComponent> result = new HashMap<>();
|
||||||
for (DataTypeComponent dtc : struct.getDefinedComponents()) {
|
for (DataTypeComponent dtc : structureDataType.getDefinedComponents()) {
|
||||||
String fieldName = dtc.getFieldName();
|
String fieldName = dtc.getFieldName();
|
||||||
if (fieldName != null) {
|
if (fieldName != null) {
|
||||||
result.put(fieldName.toLowerCase(), dtc);
|
result.put(fieldName.toLowerCase(), dtc);
|
||||||
|
|
|
@ -45,6 +45,8 @@ public class DataTypeArchiveIDTest extends AbstractGenericTest {
|
||||||
Map.entry(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"),
|
Map.entry(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"),
|
||||||
Map.entry(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"),
|
Map.entry(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"),
|
||||||
Map.entry(MAC_OS_10_9_GDT_PATH, "2650667045259492112"),
|
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.17_anybit_any.gdt", "3533627828569507753"),
|
||||||
Map.entry("typeinfo/golang/golang_1.18_anybit_any.gdt", "3528902399865061936"),
|
Map.entry("typeinfo/golang/golang_1.18_anybit_any.gdt", "3528902399865061936"),
|
||||||
Map.entry("typeinfo/golang/golang_1.19_anybit_any.gdt", "3533812166493410774"),
|
Map.entry("typeinfo/golang/golang_1.19_anybit_any.gdt", "3533812166493410774"),
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<compiler name="golang" spec="AARCH64_golang.cspec" id="golang"/>
|
<compiler name="golang" spec="AARCH64_golang.cspec" id="golang"/>
|
||||||
<external_name tool="gnu" name="aarch64"/>
|
<external_name tool="gnu" name="aarch64"/>
|
||||||
<external_name tool="DWARF.register.mapping.file" name="AARCH64.dwarf"/>
|
<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"/>
|
<external_name tool="qemu" name="qemu-aarch64"/>
|
||||||
</language>
|
</language>
|
||||||
<language processor="AARCH64"
|
<language processor="AARCH64"
|
||||||
|
|
|
@ -281,8 +281,26 @@
|
||||||
<killedbycall>
|
<killedbycall>
|
||||||
<register name="x21"/>
|
<register name="x21"/>
|
||||||
<register name="x20"/>
|
<register name="x20"/>
|
||||||
|
<register name="x26"/>
|
||||||
|
<register name="x27"/>
|
||||||
</killedbycall>
|
</killedbycall>
|
||||||
<unaffected>
|
<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="x16"/>
|
||||||
<register name="x17"/>
|
<register name="x17"/>
|
||||||
</unaffected>
|
</unaffected>
|
||||||
|
|
|
@ -6,5 +6,14 @@
|
||||||
<stack initialoffset="8" maxalign="8"/>
|
<stack initialoffset="8" maxalign="8"/>
|
||||||
<current_goroutine register="x28"/>
|
<current_goroutine register="x28"/>
|
||||||
<zero_register register="xzr" builtin="true"/>
|
<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>
|
</register_info>
|
||||||
</golang>
|
</golang>
|
|
@ -5,5 +5,6 @@
|
||||||
<stack initialoffset="4" maxalign="4"/>
|
<stack initialoffset="4" maxalign="4"/>
|
||||||
<current_goroutine register=""/>
|
<current_goroutine register=""/>
|
||||||
<zero_register register=""/>
|
<zero_register register=""/>
|
||||||
|
<duffzero dest="EDI" zero_arg="EAX" zero_type="int"/>
|
||||||
</register_info>
|
</register_info>
|
||||||
</golang>
|
</golang>
|
|
@ -195,6 +195,14 @@
|
||||||
<register name="RDI"/>
|
<register name="RDI"/>
|
||||||
</killedbycall>
|
</killedbycall>
|
||||||
<unaffected>
|
<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="RSP"/>
|
||||||
<register name="RBP"/>
|
<register name="RBP"/>
|
||||||
<register name="R14"/>
|
<register name="R14"/>
|
||||||
|
@ -228,8 +236,6 @@
|
||||||
<register name="RAX"/>
|
<register name="RAX"/>
|
||||||
<register name="RBX"/>
|
<register name="RBX"/>
|
||||||
<register name="RCX"/>
|
<register name="RCX"/>
|
||||||
<register name="RDI"/>
|
|
||||||
<register name="RSI"/>
|
|
||||||
<register name="R8"/>
|
<register name="R8"/>
|
||||||
<register name="R9"/>
|
<register name="R9"/>
|
||||||
<register name="R10"/>
|
<register name="R10"/>
|
||||||
|
|
|
@ -6,5 +6,14 @@
|
||||||
<stack initialoffset="8" maxalign="8"/>
|
<stack initialoffset="8" maxalign="8"/>
|
||||||
<current_goroutine register="R14"/>
|
<current_goroutine register="R14"/>
|
||||||
<zero_register register="XMM15"/>
|
<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>
|
</register_info>
|
||||||
</golang>
|
</golang>
|
Loading…
Add table
Add a link
Reference in a new issue