From 560d5691a7af00b4c886f2ccbe8a01c4347223d3 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:44:37 +0000 Subject: [PATCH] GP-4482 Golang 1.16 + 1.15 --- Ghidra/Features/Base/certification.manifest | 2 + .../golang/golang_1.15_anybit_any.gdt | Bin 0 -> 10790 bytes .../golang/golang_1.16_anybit_any.gdt | Bin 0 -> 11177 bytes .../FixupGolangFuncParamStorageScript.java | 10 +- .../core/analysis/GolangSymbolAnalyzer.java | 39 ++-- .../ghidra/app/util/bin/BinaryReader.java | 221 ++++++++++++++++-- .../util/bin/format/dwarf/DWARFProgram.java | 43 ++-- .../app/util/bin/format/dwarf/DWARFRange.java | 2 +- .../util/bin/format/golang/GoBuildInfo.java | 2 +- .../bin/format/golang/GoRegisterInfo.java | 58 ++++- .../format/golang/GoRegisterInfoManager.java | 69 ++++-- .../app/util/bin/format/golang/GoVer.java | 96 ++++++-- .../bin/format/golang/rtti/GoFuncData.java | 117 +++++----- .../format/golang/rtti/GoFunctabEntry.java | 8 +- .../bin/format/golang/rtti/GoModuledata.java | 135 ++++------- .../bin/format/golang/rtti/GoPcHeader.java | 108 +++++---- .../golang/rtti/GoPcValueEvaluator.java | 2 +- .../bin/format/golang/rtti/GoRttiMapper.java | 115 ++++++--- .../format/golang/rtti/GoVarlenString.java | 26 ++- .../format/golang/rtti/types/GoArrayType.java | 13 +- .../golang/rtti/types/GoStructField.java | 4 +- .../golang/structmapping/DataTypeMapper.java | 15 +- .../structmapping/DataTypeMapperContext.java | 32 +++ .../golang/structmapping/FieldMapping.java | 16 ++ .../structmapping/StructureMappingInfo.java | 32 ++- .../core/datamgr/DataTypeArchiveIDTest.java | 2 + .../AARCH64/data/languages/AARCH64.ldefs | 1 + .../data/languages/AARCH64_golang.cspec | 18 ++ .../languages/AARCH64_golang.register.info | 9 + .../languages/x86-32-golang.register.info | 1 + .../x86/data/languages/x86-64-golang.cspec | 20 +- .../languages/x86-64-golang.register.info | 9 + 32 files changed, 851 insertions(+), 374 deletions(-) create mode 100644 Ghidra/Features/Base/data/typeinfo/golang/golang_1.15_anybit_any.gdt create mode 100644 Ghidra/Features/Base/data/typeinfo/golang/golang_1.16_anybit_any.gdt create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapperContext.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 35177a917d..8f6c07bb61 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -87,6 +87,8 @@ data/symbols/win64/mfc90u.exports||GHIDRA||||END| data/symbols/win64/msvcrt.hints||GHIDRA||||END| data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END| data/typeinfo/generic/generic_clib_64.gdt||GHIDRA||||END| +data/typeinfo/golang/golang_1.15_anybit_any.gdt||GHIDRA||||END| +data/typeinfo/golang/golang_1.16_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.17_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END| data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END| diff --git a/Ghidra/Features/Base/data/typeinfo/golang/golang_1.15_anybit_any.gdt b/Ghidra/Features/Base/data/typeinfo/golang/golang_1.15_anybit_any.gdt new file mode 100644 index 0000000000000000000000000000000000000000..a7de3bdab2d6e5374a98ecfc147373349e50bc0b GIT binary patch literal 10790 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/data/typeinfo/golang/golang_1.16_anybit_any.gdt b/Ghidra/Features/Base/data/typeinfo/golang/golang_1.16_anybit_any.gdt new file mode 100644 index 0000000000000000000000000000000000000000..045d020eb6135d416a204509c2cdb1210a335490 GIT binary patch literal 11177 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

Rd3N9 zTy9M2qMYpw521dMC34*9aLaYM_WDy5Aq2cm)!-Y(h*P>?f$^+3c8=U4yLxw4Jbq4v z0Mkuhb+kW=Y)C+c&LE9u*RgY#2`)P6ZI1S*knDJ*=M11fTY#ODBWOUG!qqKLFzfbQ%-GTWm2u8@d4(*k9s7??)Lx5;Jeo@>%YP@` zfxc?OE<w=7Xnhsp`;!8*b5qS1f>ZCO3hnfbHm^_Qed%=B)-5 zP8pRaB$UcSzC#aX>vB&mvc}c7^+w19hBLPgTHM0)nxdl_&(Lr(qi7>|2uHC4cs7_s z97Y?c{u%7H!F1gSb{z=TJ};9|%EJAw7RYE?9lEy;O{qf@>(Ci>+(m~m1K=A>vjL{3 zZDKXo_tXu6KdH(x+()QNpOeFo7!T;&h-`+AVT>kn%Yo_bow055*mlU+w%XYC&geFI zbUS2pTWxfEXJnf^vK=zAtv0f~GrUb6-VPbwRvX^l8KP2(XvAV#Q86vAn3lZ+UFl6* zAb;?|5ltJ+fNUy;uA0hS#P0MwIa*%572vQUj8q?b!@5Twl)>Z$5!e2pZ%Jc_3?3Zk z9soCIa|Y8oci7Hz2nres2=>s~J7QNgKA6aIG{pZ$*R9Vts3u+Xi|4?on0eAwX(`Ij zQ(GEeuKkY#{?+m1p7oeU(z2aWl9=H}o>eO0_x~;|li;?lUhFM2iS versions = List.of(GoVer.values()); - goVersion = - askChoice("Golang Version", "What is the golang version?", versions, GoVer.UNKNOWN); + if (goVersion == GoVer.INVALID) { + goVersion = askChoice("Golang Version", "What is the golang version?", + GoRttiMapper.getAllSupportedVersions(), GoVer.INVALID); } println("Fixing param storage for function %s@%s".formatted(func.getName(), func.getEntryPoint())); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java index 702c2c0f62..ace8e52179 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/GolangSymbolAnalyzer.java @@ -177,8 +177,6 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { } private void markupWellknownSymbols() throws IOException { - Program program = goBinary.getProgram(); - Symbol g0 = goBinary.getGoSymbol("runtime.g0"); Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class); if (g0 != null && gStruct != null) { @@ -317,31 +315,25 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { Program program = goBinary.getProgram(); GoRegisterInfo regInfo = goBinary.getRegInfo(); DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType); - DataType uintDT = goBinary.getTypeOrDefault("uint", DataType.class, - AbstractIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null)); GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero"); Function duffzeroFunc = duffzeroFuncdata != null ? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress()) : null; - if (duffzeroFunc != null && - goBinary.hasCallingConvention(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME)) { + List duffzeroParams = regInfo.getDuffzeroParams(program); + if (duffzeroFunc != null && !duffzeroParams.isEmpty()) { + // NOTE: some go archs don't create duffzero functions. See + // cmd/compile/internal/ssa/config.go and look for flag noDuffDevice in each arch. try { - // NOTE: some duffzero funcs need a zero value supplied to them via a register set - // by the caller. (depending on the arch) The duffzero calling convention defined - // by the callspec should take care of this by defining that register as the second - // storage location. Otherwise, the callspec will only have a single storage - // location defined. - boolean needZeroValueParam = regInfo.getZeroRegister() == null; - List params = new ArrayList<>(); - params.add(new ParameterImpl("dest", voidPtr, program)); - if (needZeroValueParam) { - params.add(new ParameterImpl("zeroValue", uintDT, program)); - } - duffzeroFunc.updateFunction(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME, - new ReturnParameterImpl(VoidDataType.dataType, program), params, - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + // NOTE: even though we are specifying custom storage for the arguments, the + // calling convention name is still important as it tells the decompiler which + // registers are unaffected vs killed-by-call + + ReturnParameterImpl voidRet = new ReturnParameterImpl(VoidDataType.dataType, + VariableStorage.VOID_STORAGE, program); + duffzeroFunc.updateFunction(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME, voidRet, + duffzeroParams, FunctionUpdateType.CUSTOM_STORAGE, true, SourceType.ANALYSIS); markupSession.appendComment(duffzeroFunc, null, "Golang special function: duffzero"); @@ -527,6 +519,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { String duffComment = program.getListing() .getCodeUnitAt(duffFunc.getEntryPoint()) .getComment(CodeUnit.PLATE_COMMENT); + monitor.setMessage("Fixing alternate duffzero/duffcopy entry points"); for (FunctionIterator funcIt = program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) { @@ -538,9 +531,11 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer { func.setName(duffFunc.getName() + "_" + func.getEntryPoint(), SourceType.ANALYSIS); func.setParentNamespace(funcNS); + FunctionUpdateType fut = duffFunc.hasCustomVariableStorage() + ? FunctionUpdateType.CUSTOM_STORAGE + : FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS; func.updateFunction(ccName, duffFunc.getReturn(), - Arrays.asList(duffFunc.getParameters()), - FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS); + Arrays.asList(duffFunc.getParameters()), fut, true, SourceType.ANALYSIS); if (duffComment != null && !duffComment.isBlank()) { new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment) .applyTo(program); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java index 3bf7b5ce62..66e3983ff4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/BinaryReader.java @@ -415,7 +415,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public short readNextShort() throws IOException { - short s = readShort(currentIndex); + return readNextShort(converter); + } + + /** + * Reads the short at the current index and then increments the current + * index by SIZEOF_SHORT. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the short at the current index + * @exception IOException if an I/O error occurs + */ + public short readNextShort(DataConverter dc) throws IOException { + short s = readShort(dc, currentIndex); currentIndex += SIZEOF_SHORT; return s; } @@ -427,7 +439,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public int readNextUnsignedShort() throws IOException { - return Short.toUnsignedInt(readNextShort()); + return Short.toUnsignedInt(readNextShort(converter)); + } + + /** + * Reads the unsigned short at the current index and then increments the current + * index by SIZEOF_SHORT. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the unsigned short at the current index, as an int + * @exception IOException if an I/O error occurs + */ + public int readNextUnsignedShort(DataConverter dc) throws IOException { + return Short.toUnsignedInt(readNextShort(dc)); } /** @@ -437,7 +461,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public int readNextInt() throws IOException { - int i = readInt(currentIndex); + return readNextInt(converter); + } + + /** + * Reads the integer at the current index and then increments the current + * index by SIZEOF_INT. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the integer at the current index + * @exception IOException if an I/O error occurs + */ + public int readNextInt(DataConverter dc) throws IOException { + int i = readInt(dc, currentIndex); currentIndex += SIZEOF_INT; return i; } @@ -449,7 +485,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public long readNextUnsignedInt() throws IOException { - return Integer.toUnsignedLong(readNextInt()); + return Integer.toUnsignedLong(readNextInt(converter)); + } + + /** + * Reads the unsigned integer at the current index and then increments the current + * index by SIZEOF_INT. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the unsigned integer at the current index, as a long + * @exception IOException if an I/O error occurs + */ + public long readNextUnsignedInt(DataConverter dc) throws IOException { + return Integer.toUnsignedLong(readNextInt(dc)); } /** @@ -459,7 +507,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public long readNextLong() throws IOException { - long l = readLong(currentIndex); + return readNextLong(converter); + } + + /** + * Reads the long at the current index and then increments the current + * index by SIZEOF_LONG. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the long at the current index + * @exception IOException if an I/O error occurs + */ + public long readNextLong(DataConverter dc) throws IOException { + long l = readLong(dc, currentIndex); currentIndex += SIZEOF_LONG; return l; } @@ -469,10 +529,22 @@ public class BinaryReader { * * @param len the number of bytes that the integer occupies, 1 to 8 * @return value of requested length, with sign bit extended, in a long - * @throws IOException + * @exception IOException if an I/O error occurs */ public long readNextValue(int len) throws IOException { - long result = readValue(currentIndex, len); + return readNextValue(converter, len); + } + + /** + * Returns the signed value of the integer (of the specified length) at the current index. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param len the number of bytes that the integer occupies, 1 to 8 + * @return value of requested length, with sign bit extended, in a long + * @exception IOException if an I/O error occurs + */ + public long readNextValue(DataConverter dc, int len) throws IOException { + long result = readValue(dc, currentIndex, len); currentIndex += len; return result; } @@ -482,10 +554,22 @@ public class BinaryReader { * * @param len the number of bytes that the integer occupies, 1 to 8 * @return unsigned value of requested length, in a long - * @throws IOException + * @exception IOException if an I/O error occurs */ public long readNextUnsignedValue(int len) throws IOException { - long result = readUnsignedValue(currentIndex, len); + return readNextUnsignedValue(converter, len); + } + + /** + * Returns the unsigned value of the integer (of the specified length) at the current index. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param len the number of bytes that the integer occupies, 1 to 8 + * @return unsigned value of requested length, in a long + * @exception IOException if an I/O error occurs + */ + public long readNextUnsignedValue(DataConverter dc, int len) throws IOException { + long result = readUnsignedValue(dc, currentIndex, len); currentIndex += len; return result; } @@ -684,7 +768,25 @@ public class BinaryReader { * @throws InvalidDataException if value can not be held in a java integer */ public int readNextUnsignedIntExact() throws IOException, InvalidDataException { - long i = readNextUnsignedInt(); + return readNextUnsignedIntExact(converter); + } + + /** + * Reads an unsigned int32 value, and returns it as a java int (instead of a java long). + *

+ * If the value is outside the range of 0..Integer.MAX_VALUE, an InvalidDataException is thrown. + *

+ * Useful for reading uint32 values that are going to be used in java to allocate arrays or + * other similar cases where the value must be a java integer. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @return the uint32 value read from the stream, if it fits into the range [0..MAX_VALUE] + * of a java integer + * @throws IOException if there was an error reading + * @throws InvalidDataException if value can not be held in a java integer + */ + public int readNextUnsignedIntExact(DataConverter dc) throws IOException, InvalidDataException { + long i = readNextUnsignedInt(dc); ensureInt32u(i); return (int) i; } @@ -908,8 +1010,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public short readShort(long index) throws IOException { + return readShort(converter, index); + } + + /** + * Returns the signed SHORT at index. + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index the index where the SHORT begins + * @return the signed SHORT + * @exception IOException if an I/O error occurs + */ + public short readShort(DataConverter dc, long index) throws IOException { byte[] bytes = provider.readBytes(index, SIZEOF_SHORT); - return converter.getShort(bytes); + return dc.getShort(bytes); } /** @@ -919,7 +1032,18 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public int readUnsignedShort(long index) throws IOException { - return Short.toUnsignedInt(readShort(index)); + return Short.toUnsignedInt(readShort(converter, index)); + } + + /** + * Returns the unsigned SHORT at index. + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index the index where the SHORT begins + * @return the unsigned SHORT as an int + * @exception IOException if an I/O error occurs + */ + public int readUnsignedShort(DataConverter dc, long index) throws IOException { + return Short.toUnsignedInt(readShort(dc, index)); } /** @@ -929,8 +1053,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public int readInt(long index) throws IOException { + return readInt(converter, index); + } + + /** + * Returns the signed INTEGER at index. + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index the index where the INTEGER begins + * @return the signed INTEGER + * @exception IOException if an I/O error occurs + */ + public int readInt(DataConverter dc, long index) throws IOException { byte[] bytes = provider.readBytes(index, SIZEOF_INT); - return converter.getInt(bytes); + return dc.getInt(bytes); } /** @@ -940,7 +1075,18 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public long readUnsignedInt(long index) throws IOException { - return Integer.toUnsignedLong(readInt(index)); + return Integer.toUnsignedLong(readInt(converter, index)); + } + + /** + * Returns the unsigned INTEGER at index. + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index the index where the INTEGER begins + * @return the unsigned INTEGER as a long + * @exception IOException if an I/O error occurs + */ + public long readUnsignedInt(DataConverter dc, long index) throws IOException { + return Integer.toUnsignedLong(readInt(dc, index)); } /** @@ -950,8 +1096,19 @@ public class BinaryReader { * @exception IOException if an I/O error occurs */ public long readLong(long index) throws IOException { + return readLong(converter, index); + } + + /** + * Returns the signed LONG at index. + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index the index where the LONG begins + * @return the LONG + * @exception IOException if an I/O error occurs + */ + public long readLong(DataConverter dc, long index) throws IOException { byte[] bytes = provider.readBytes(index, SIZEOF_LONG); - return converter.getLong(bytes); + return dc.getLong(bytes); } /** @@ -960,11 +1117,24 @@ public class BinaryReader { * @param index where the value begins * @param len the number of bytes that the integer occupies, 1 to 8 * @return value of requested length, with sign bit extended, in a long - * @throws IOException + * @exception IOException if an I/O error occurs */ public long readValue(long index, int len) throws IOException { + return readValue(converter, index, len); + } + + /** + * Returns the signed value of the integer (of the specified length) at the specified offset. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index where the value begins + * @param len the number of bytes that the integer occupies, 1 to 8 + * @return value of requested length, with sign bit extended, in a long + * @exception IOException if an I/O error occurs + */ + public long readValue(DataConverter dc, long index, int len) throws IOException { byte[] bytes = provider.readBytes(index, len); - return converter.getSignedValue(bytes, len); + return dc.getSignedValue(bytes, len); } /** @@ -973,11 +1143,24 @@ public class BinaryReader { * @param index where the value begins * @param len the number of bytes that the integer occupies, 1 to 8 * @return unsigned value of requested length, in a long - * @throws IOException + * @exception IOException if an I/O error occurs */ public long readUnsignedValue(long index, int len) throws IOException { + return readUnsignedValue(converter, index, len); + } + + /** + * Returns the unsigned value of the integer (of the specified length) at the specified offset. + * + * @param dc {@link BigEndianDataConverter BE} or {@link LittleEndianDataConverter LE} + * @param index where the value begins + * @param len the number of bytes that the integer occupies, 1 to 8 + * @return unsigned value of requested length, in a long + * @exception IOException if an I/O error occurs + */ + public long readUnsignedValue(DataConverter dc, long index, int len) throws IOException { byte[] bytes = provider.readBytes(index, len); - return converter.getValue(bytes, len); + return dc.getValue(bytes, len); // NOTE: getValue() is unsigned so this is all good } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java index 4f7abc7b84..4d7ef32aea 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java @@ -1201,25 +1201,32 @@ public class DWARFProgram implements Closeable { private DWARFLocationList readLocationList(DWARFNumericAttribute loclistAttr, DWARFCompilationUnit cu) throws IOException { - switch (loclistAttr.getAttributeForm()) { - case DW_FORM_sec_offset: - int dwarfVer = cu.getDWARFVersion(); - if (dwarfVer < 5) { - debugLocation.setPointerIndex(loclistAttr.getUnsignedValue()); - return DWARFLocationList.readV4(debugLocation, cu); - } - else if (dwarfVer == 5) { - debugLocLists.setPointerIndex(loclistAttr.getUnsignedValue()); + try { + switch (loclistAttr.getAttributeForm()) { + case DW_FORM_sec_offset: + int dwarfVer = cu.getDWARFVersion(); + if (dwarfVer < 5) { + debugLocation.setPointerIndex(loclistAttr.getUnsignedValue()); + return DWARFLocationList.readV4(debugLocation, cu); + } + else if (dwarfVer == 5) { + debugLocLists.setPointerIndex(loclistAttr.getUnsignedValue()); + return DWARFLocationList.readV5(debugLocLists, cu); + } + break; + case DW_FORM_loclistx: + int index = loclistAttr.getUnsignedIntExact(); + long locOffset = locationListTable.getOffset(index, cu); + debugLocLists.setPointerIndex(locOffset); return DWARFLocationList.readV5(debugLocLists, cu); - } - break; - case DW_FORM_loclistx: - int index = loclistAttr.getUnsignedIntExact(); - long locOffset = locationListTable.getOffset(index, cu); - debugLocLists.setPointerIndex(locOffset); - return DWARFLocationList.readV5(debugLocLists, cu); - default: - break; // fallthru to throw + default: + break; // fallthru to throw + } + } + catch (IOException | IllegalArgumentException e) { + throw new IOException( + "Failed to read location list specified by %s".formatted(loclistAttr.toString()), + e); } throw new IOException( "Unsupported loclist form %s".formatted(loclistAttr.getAttributeForm())); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRange.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRange.java index d9a7754f2f..d7ab365e02 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRange.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRange.java @@ -43,7 +43,7 @@ public class DWARFRange implements Comparable { public DWARFRange(long start, long end) { if (Long.compareUnsigned(end, start) < 0) { throw new IllegalArgumentException( - "Range max (%d) cannot be less than min (%d).".formatted(end, start)); + "Range max (%x) cannot be less than min (%x).".formatted(end, start)); } this.start = start; this.end = end; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java index ad978d49e0..f347e77d6a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoBuildInfo.java @@ -189,7 +189,7 @@ public class GoBuildInfo implements ElfInfoItem { return version; } - public GoVer getVerEnum() { + public GoVer getGoVer() { return GoVer.parse(version); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java index 631fc1471c..19d496746c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfo.java @@ -15,11 +15,17 @@ */ package ghidra.app.util.bin.format.golang; +import java.util.ArrayList; import java.util.List; +import ghidra.app.util.bin.format.dwarf.DWARFUtil; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.exception.InvalidInputException; /** * Immutable information about registers, alignment sizes, etc needed to allocate storage @@ -28,6 +34,8 @@ import ghidra.program.model.lang.Register; */ public class GoRegisterInfo { + public enum RegType { INT, FLOAT } + private final List intRegisters; private final List floatRegisters; private final int stackInitialOffset; @@ -36,9 +44,14 @@ public class GoRegisterInfo { private final Register zeroRegister; // always contains a zero value private final boolean zeroRegisterIsBuiltin; // zero register is provided by cpu, or is manually set + private final Register duffzeroDestParam; + private final Register duffzeroZeroParam; // if duffzero has 2nd param + private final RegType duffzeroZeroParamType; + GoRegisterInfo(List intRegisters, List floatRegisters, int stackInitialOffset, int maxAlign, Register currentGoroutineRegister, - Register zeroRegister, boolean zeroRegisterIsBuiltin) { + Register zeroRegister, boolean zeroRegisterIsBuiltin, Register duffzeroDestParam, + Register duffzeroZeroParam, RegType duffzeroZeroParamType) { this.intRegisters = intRegisters; this.floatRegisters = floatRegisters; this.stackInitialOffset = stackInitialOffset; @@ -46,6 +59,10 @@ public class GoRegisterInfo { this.currentGoroutineRegister = currentGoroutineRegister; this.zeroRegister = zeroRegister; this.zeroRegisterIsBuiltin = zeroRegisterIsBuiltin; + + this.duffzeroDestParam = duffzeroDestParam; + this.duffzeroZeroParam = duffzeroZeroParam; + this.duffzeroZeroParamType = duffzeroZeroParamType; } public int getIntRegisterSize() { @@ -80,6 +97,45 @@ public class GoRegisterInfo { return stackInitialOffset; } + public List getDuffzeroParams(Program program) { + if (duffzeroDestParam == null) { + return List.of(); + } + try { + ProgramBasedDataTypeManager dtm = program.getDataTypeManager(); + DataType voidPtr = dtm.getPointer(VoidDataType.dataType); + + List params = new ArrayList<>(); + + params.add(new ParameterImpl("dest", Parameter.UNASSIGNED_ORDINAL, voidPtr, + getStorageForReg(program, duffzeroDestParam, voidPtr.getLength()), true, program, + SourceType.ANALYSIS)); + if (duffzeroZeroParam != null && duffzeroZeroParamType != null) { + int regSize = duffzeroZeroParam.getMinimumByteSize(); + DataType dt = switch (duffzeroZeroParamType) { + case FLOAT -> AbstractFloatDataType.getFloatDataType(regSize, dtm); + case INT -> AbstractIntegerDataType.getUnsignedDataType(regSize, dtm); + }; + params.add(new ParameterImpl("zeroValue", Parameter.UNASSIGNED_ORDINAL, dt, + getStorageForReg(program, duffzeroZeroParam, regSize), true, program, + SourceType.ANALYSIS)); + } + + return params; + } + catch (InvalidInputException e) { + return List.of(); + } + + } + + private VariableStorage getStorageForReg(Program program, Register reg, int len) + throws InvalidInputException { + return new VariableStorage(program, + DWARFUtil.convertRegisterListToVarnodeStorage(List.of(reg), len) + .toArray(Varnode[]::new)); + } + public int getAlignmentForType(DataType dt) { while (dt instanceof TypeDef || dt instanceof Array) { if (dt instanceof TypeDef td) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java index c01d261806..cbe16358c6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoRegisterInfoManager.java @@ -24,6 +24,7 @@ import org.jdom.input.SAXBuilder; import generic.jar.ResourceFile; import ghidra.app.util.bin.format.dwarf.DWARFUtil; +import ghidra.app.util.bin.format.golang.GoRegisterInfo.RegType; import ghidra.program.model.lang.*; import ghidra.util.Msg; import ghidra.util.xml.XmlUtilities; @@ -32,12 +33,13 @@ import ghidra.util.xml.XmlUtilities; * XML config file format: *

  * 	<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"/>
  * 			<float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
  * 			<stack initialoffset="8" maxalign="8"/>
  * 			<current_goroutine register="R14"/>
  * 			<zero_register register="XMM15" builtin="true|false"/>
+ * 			<duffzero dest="RDI" zero_arg="XMM0" zero_type="float|int"/>
  * 		</register_info>
  * 		<register_info versions="V1_2">
  * 			...
@@ -65,23 +67,37 @@ public class GoRegisterInfoManager {
 	 * returned that forces all parameters to be stack allocated.
 	 * 
 	 * @param lang {@link Language}
-	 * @param goVersion {@link GoVer} enum
+	 * @param goVer {@link GoVer}
 	 * @return {@link GoRegisterInfo}, never null
 	 */
-	public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVersion) {
+	public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVer) {
 		Map perVersionRegInfos =
 			cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang));
-		GoRegisterInfo registerInfo = perVersionRegInfos.get(goVersion);
+
+		GoRegisterInfo registerInfo = getMatchingRegisterInfo(perVersionRegInfos, goVer);
 		if (registerInfo == null) {
 			registerInfo = getDefault(lang);
-			perVersionRegInfos.put(goVersion, registerInfo);
+			perVersionRegInfos.put(goVer, registerInfo);
 			int goSize = lang.getInstructionAlignment();
-			Msg.warn(this, "Missing Golang register info for: " + lang.getLanguageID() +
-				", defaulting to abi0, size=" + goSize);
+			Msg.warn(this, "Missing Golang register info for: %s, defaulting to abi0, size=%d"
+					.formatted(lang.getLanguageID(), goSize));
 		}
 		return registerInfo;
 	}
 
+	private GoRegisterInfo getMatchingRegisterInfo(Map 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 loadRegisterInfo(Language lang) {
 		try {
 			ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME);
@@ -136,8 +152,9 @@ public class GoRegisterInfoManager {
 		Element stackElem = regInfoElem.getChild("stack");
 		Element goRoutineElem = regInfoElem.getChild("current_goroutine");
 		Element zeroRegElem = regInfoElem.getChild("zero_register");
+		Element duffZeroElem = regInfoElem.getChild("duffzero");
 		if (intRegsElem == null || floatRegsElem == null || stackElem == null ||
-			goRoutineElem == null || zeroRegElem == null) {
+			goRoutineElem == null || zeroRegElem == null || duffZeroElem == null) {
 			throw new IOException("Bad format");
 		}
 
@@ -154,10 +171,14 @@ public class GoRegisterInfoManager {
 		Register zeroReg = parseRegStr(zeroRegElem.getAttributeValue("register"), lang);
 		boolean zeroRegIsBuiltin =
 			XmlUtilities.parseOptionalBooleanAttr(zeroRegElem, "builtin", false);
+		
+		Register duffzeroDest = parseRegStr(duffZeroElem.getAttributeValue("dest"), lang);
+		Register duffzeroZero = parseRegStr(duffZeroElem.getAttributeValue("zero_arg"), lang);
+		RegType duffzeroZeroType = parseRegTypeStr(duffZeroElem.getAttributeValue("zero_type"));
 
-		GoRegisterInfo registerInfo =
-			new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign,
-				currentGoRoutineReg, zeroReg, zeroRegIsBuiltin);
+		GoRegisterInfo registerInfo = new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset,
+			maxAlign, currentGoRoutineReg, zeroReg, zeroRegIsBuiltin, duffzeroDest, duffzeroZero,
+			duffzeroZeroType);
 		Map result = new HashMap<>();
 		for (GoVer goVer : validGoVersions) {
 			result.put(goVer, registerInfo);
@@ -167,7 +188,8 @@ public class GoRegisterInfoManager {
 
 	private GoRegisterInfo getDefault(Language lang) {
 		int goSize = lang.getInstructionAlignment();
-		return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null, false);
+		return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null, false, null,
+			null, RegType.INT);
 	}
 
 	private List parseRegListStr(String s, Language lang) throws IOException {
@@ -196,26 +218,33 @@ public class GoRegisterInfoManager {
 		return register;
 	}
 
+	private RegType parseRegTypeStr(String s) {
+		return switch (Objects.requireNonNullElse(s, "int").toLowerCase()) {
+			default -> RegType.INT;
+			case "float" -> RegType.FLOAT;
+		};
+
+	}
+
 	private Set parseValidGoVersionsStr(String s) throws IOException {
 		if (s.trim().equalsIgnoreCase("all")) {
-			EnumSet allVers = EnumSet.allOf(GoVer.class);
-			allVers.remove(GoVer.UNKNOWN);
-			return allVers;
+			return Set.of(GoVer.ANY);
 		}
 
-		EnumSet result = EnumSet.noneOf(GoVer.class);
+		Set result = new HashSet<>();
 		for (String verStr : s.split(",")) {
 			verStr = verStr.trim();
 			if (verStr.isEmpty()) {
 				continue;
 			}
-			try {
-				GoVer ver = GoVer.valueOf(verStr);
-				result.add(ver);
+			if (verStr.startsWith("V")) {
+				verStr = verStr.substring(1).replace('_', '.'); // convert "V1_1" -> "1.1"
 			}
-			catch (IllegalArgumentException e) {
+			GoVer ver = GoVer.parse(verStr);
+			if (ver.isInvalid()) {
 				throw new IOException("Unknown go version: " + verStr);
 			}
+			result.add(ver);
 		}
 		return result;
 	}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java
index b9f6bca38f..14e208e97c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/GoVer.java
@@ -15,31 +15,40 @@
  */
 package ghidra.app.util.bin.format.golang;
 
+import java.util.Objects;
+
 import ghidra.framework.options.Options;
 import ghidra.program.model.listing.Program;
 
 /**
  * Golang version numbers
  */
-public enum GoVer {
-	UNKNOWN(0, 0),
-	V1_2(1, 2),
-	V1_16(1, 16),
-	V1_17(1, 17),
-	V1_18(1, 18),
-	V1_19(1, 19),
-	V1_20(1, 20),
-	V1_21(1, 21),
-	V1_22(1, 22);
+public class GoVer implements Comparable {
+	public static final GoVer INVALID = new GoVer(0, 0);
+	public static final GoVer ANY = new GoVer(-1, -1);
+
+	// a couple of well-known versions that are re-used in a few places
+	public static final GoVer V1_2 = new GoVer(1, 2);
+	public static final GoVer V1_16 = new GoVer(1, 16);
+	public static final GoVer V1_17 = new GoVer(1, 17);
+	public static final GoVer V1_18 = new GoVer(1, 18);
 
 	private final int major;
 	private final int minor;
 
-	GoVer(int major, int minor) {
+	public GoVer(int major, int minor) {
 		this.major = major;
 		this.minor = minor;
 	}
 
+	public boolean isInvalid() {
+		return major == 0 && minor == 0;
+	}
+
+	public boolean isWildcard() {
+		return major == -1 && minor == -1;
+	}
+
 	/**
 	 * Major value
 	 * 
@@ -58,6 +67,15 @@ public enum GoVer {
 		return minor;
 	}
 
+	@Override
+	public int compareTo(GoVer o) {
+		int result = Integer.compare(major, o.major);
+		if (result == 0) {
+			result = Integer.compare(minor, o.minor);
+		}
+		return result;
+	}
+
 	/**
 	 * Compares this version to the specified other version and returns true if this version
 	 * is greater than or equal to the other version.
@@ -66,45 +84,75 @@ public enum GoVer {
 	 * @return true if this version is gte other version
 	 */
 	public boolean isAtLeast(GoVer otherVersion) {
-		return this.ordinal() >= otherVersion.ordinal();
+		return compareTo(otherVersion) >= 0;
 	}
 
 	/**
-	 * Parses a version string ("1.2") and returns the matching GoVer enum instance, or
-	 * UNKNOWN if no matching version or bad data.
+	 * Returns true if this version is between the specified min and max versions (inclusive).
+	 * 
+	 * @param min minimum version to allow (inclusive)
+	 * @param max maximum version to allow (inclusive)
+	 * @return boolean true if this version is between the specified min and max versions
+	 */
+	public boolean inRange(GoVer min, GoVer max) {
+		return min.compareTo(this) <= 0 && this.compareTo(max) <= 0;
+	}
+
+	/**
+	 * Parses a version string ("1.2") and returns a GoVer instance, or
+	 * INVALID if no matching version or bad data.
 	 *  
 	 * @param s string to parse
-	 * @return GoVer enum instance, or UNKNOWN
+	 * @return GoVer instance, or INVALID
 	 */
 	public static GoVer parse(String s) {
-		String[] parts = s.split("\\.");
+		String[] parts = Objects.requireNonNullElse(s, "").split("\\.");
 		if (parts.length < 2) {
-			return UNKNOWN;
+			return INVALID;
 		}
 		try {
 			int major = Integer.parseInt(parts[0]);
 			int minor = Integer.parseInt(parts[1]);
-			for (GoVer ver : values()) {
-				if (ver.major == major && ver.minor == minor) {
-					return ver;
-				}
-			}
+			//don't care about patch level right now
+			return new GoVer(major, minor);
 		}
 		catch (NumberFormatException e) {
 			// fall thru, return unknown
 		}
-		return UNKNOWN;
+		return INVALID;
 	}
 
 	public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version";
 	public static GoVer fromProgramProperties(Program program) {
 		Options props = program.getOptions(Program.PROGRAM_INFO);
 		String verStr = props.getString(GOLANG_VERSION_PROPERTY_NAME, null);
-		return verStr != null ? parse(verStr) : UNKNOWN;
+		return parse(verStr);
 	}
 
 	public static void setProgramPropertiesWithOriginalVersionString(Options props, String s) {
 		props.setString(GOLANG_VERSION_PROPERTY_NAME, s);
 	}
 
+	@Override
+	public int hashCode() {
+		return Objects.hash(major, minor);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (!(obj instanceof GoVer)) {
+			return false;
+		}
+		GoVer other = (GoVer) obj;
+		return major == other.major && minor == other.minor;
+	}
+
+	@Override
+	public String toString() {
+		return "%d.%d".formatted(major, minor);
+	}
+
 }
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java
index 0733f9c580..7ba8f8d25c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFuncData.java
@@ -22,7 +22,8 @@ import ghidra.app.util.bin.BinaryReader;
 import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo;
 import ghidra.app.util.bin.format.golang.structmapping.*;
 import ghidra.program.model.address.*;
-import ghidra.program.model.data.*;
+import ghidra.program.model.data.ArrayDataType;
+import ghidra.program.model.data.DataType;
 import ghidra.program.model.listing.Function;
 import ghidra.util.NumericUtilities;
 import ghidra.util.exception.CancelledException;
@@ -39,19 +40,19 @@ public class GoFuncData implements StructureMarkup {
 	@ContextField
 	private StructureContext context;
 
-	@FieldMapping(optional = true, fieldName = { "entryoff", "entryOff" })
+	@FieldMapping(presentWhen = "1.18+", fieldName = { "entryoff", "entryOff" })
 	@EOLComment("getDescription")
 	@MarkupReference("getFuncAddress")
-	private long entryoff;	// valid in >=1.18, relative offset of function
+	private long entryoff;	// relative offset of function
 
-	@FieldMapping(optional = true)
+	@FieldMapping(presentWhen = "-1.17")
 	@EOLComment("getDescription")
 	@MarkupReference("getFuncAddress")
-	private long entry;	// valid in <=1.17, location of function
+	private long entry;	// absolute location of function
 
 	@FieldMapping(fieldName = { "nameoff", "nameOff" })
 	@MarkupReference("getNameAddress")
-	private long nameoff;
+	private long nameoff;	// uint32
 
 	//private long args; // size of arguments
 
@@ -64,14 +65,14 @@ public class GoFuncData implements StructureMarkup {
 	@FieldMapping
 	private int npcdata; // number of elements in varlen pcdata array
 
-	@FieldMapping
-	private long cuOffset;
+	@FieldMapping(presentWhen = "1.16+")
+	private long cuOffset = -1;
 
 	@FieldMapping
 	@EOLComment("getFuncIDEnum")
 	private byte funcID;	// see GoFuncID enum
 
-	@FieldMapping
+	@FieldMapping(presentWhen = "1.17+")
 	@EOLComment("flags")
 	private byte flag;	// runtime.funcFlag, see GoFuncFlag enum
 
@@ -130,13 +131,14 @@ public class GoFuncData implements StructureMarkup {
 		// using the max pc value
 		try {
 			long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1;
-			return max > entry
-					? new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max))
-					: null;
+			if (max > entry) {
+				return new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max));
+			}
 		}
 		catch (IOException e) {
-			return new AddressRangeImpl(getFuncAddress(), getFuncAddress());
+			// fall thru, return 1-byte range
 		}
+		return new AddressRangeImpl(funcAddress, funcAddress);
 	}
 
 	/**
@@ -228,7 +230,7 @@ public class GoFuncData implements StructureMarkup {
 	 * 
 	 * Return value information is unknown and always represented as an "undefined" data type.
 	 * 
-	 * @return pseduo-function signature string, such as "undefined foo( 8, 8 )" which would
+	 * @return pseudo-function signature string, such as "undefined foo( 8, 8 )" which would
 	 * indicate the function had 2 8-byte arguments 
 	 * @throws IOException if error reading lookup data
 	 */
@@ -237,18 +239,6 @@ public class GoFuncData implements StructureMarkup {
 		return sig.toString();
 	}
 
-	/**
-	 * Attempts to return a {@link FunctionDefinition} for this function, based on this
-	 * function's inclusion in a golang interface as a method.
-	 * 
-	 * @return {@link FunctionDefinition}
-	 * @throws IOException if error
-	 */
-	public FunctionDefinition findMethodSignature() throws IOException {
-		MethodInfo methodInfo = findMethodInfo();
-		return methodInfo != null ? methodInfo.getSignature() : null;
-	}
-
 	/**
 	 * Attempts to return a {@link GoMethodInfo} for this function, based on this
 	 * function's inclusion in a golang interface as a method.
@@ -275,9 +265,14 @@ public class GoFuncData implements StructureMarkup {
 	 */
 	public Address getNameAddress() {
 		GoModuledata moduledata = getModuledata();
-		return moduledata != null
-				? moduledata.getFuncnametab().getArrayAddress().add(nameoff)
-				: null;
+		if (moduledata != null) {
+			GoSlice slice = moduledata.getFuncnametab();
+			if (slice == null) {
+				slice = moduledata.getPclntable();
+			}
+			return slice.getArrayAddress().add(nameoff);
+		}
+		return null;
 	}
 
 	/**
@@ -287,15 +282,17 @@ public class GoFuncData implements StructureMarkup {
 	 */
 	public String getName() {
 		GoModuledata moduledata = getModuledata();
-		try {
-			if (moduledata != null) {
-				return programContext
-						.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff)
-						.readNextUtf8String();
+		if (moduledata != null) {
+			try {
+				GoSlice slice = moduledata.getFuncnametab();
+				if (slice == null) {
+					slice = moduledata.getPclntable();
+				}
+				return slice.getElementReader(1, (int) nameoff).readNextUtf8String();
+			}
+			catch (IOException e) {
+				// fall thru
 			}
-		}
-		catch (IOException e) {
-			// fall thru
 		}
 		return "unknown_func_%x_%s".formatted(context.getStructureStart(),
 			funcAddress != null ? funcAddress : "missing_addr");
@@ -369,11 +366,25 @@ public class GoFuncData implements StructureMarkup {
 		int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry);
 		int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry);
 
-		long fileoff = fileno >= 0
-				? moduledata.getCutab()
-						.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno)
-				: -1;
-		String fileName = fileoff != -1 ? moduledata.getFilename(fileoff) : null;
+		if (fileno < 0) {
+			return null;
+		}
+
+		long fileoff;
+		GoSlice cutab = moduledata.getCutab();
+		GoSlice filetab = moduledata.getFiletab();
+		GoSlice nameSlice;
+		if (cutab == null) { // when <= 1.15
+			fileoff = filetab.readUIntElement(4 /*sizeof(uint32*/, fileno);
+			nameSlice = moduledata.getPclntable();
+		}
+		else { // when >= 1.16
+			fileoff = cutab.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno);
+			nameSlice = filetab;
+		}
+		String fileName = fileoff >= 0 // -1 == no value 
+				? nameSlice.getElementReader(1, (int) fileoff).readNextUtf8String()
+				: null;
 		return fileName != null ? new GoSourceFileInfo(fileName, lineNum) : null;
 	}
 
@@ -579,25 +590,3 @@ public class GoFuncData implements StructureMarkup {
 	}
 
 }
-/*
-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
-*/
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java
index 7c7d5933bb..b85cdc3865 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoFunctabEntry.java
@@ -32,13 +32,13 @@ public class GoFunctabEntry {
 	@ContextField
 	private StructureContext context;
 
-	@FieldMapping(optional = true)
+	@FieldMapping(presentWhen = "1.18+")
 	@MarkupReference("getFuncAddress")
-	private long entryoff;	// valid in >=1.18, relative offset of function
+	private long entryoff;	// relative offset of function
 
-	@FieldMapping(optional = true)
+	@FieldMapping(presentWhen = "-1.17")
 	@MarkupReference("getFuncAddress")
-	private long entry;	// valid in <=1.17, location of function
+	private long entry;	// absolute location of function
 
 	@FieldMapping
 	@MarkupReference("getFuncData")
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java
index b307b04965..bbbc90a74f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoModuledata.java
@@ -44,9 +44,27 @@ public class GoModuledata implements StructureMarkup {
 	@ContextField
 	private StructureContext structureContext;
 
-	@FieldMapping
+	@FieldMapping(presentWhen = "1.16+")
 	@MarkupReference
-	private long pcHeader;	// pointer to the GoPcHeader instance, useful for bootstrapping
+	private long pcHeader;	// pointer to the GoPcHeader instance, useful for bootstrapping.  when ver >= 1.16, this is first field
+
+	@FieldMapping(presentWhen = "1.16+")
+	private GoSlice funcnametab;	// []uint8 blob of null term strings
+
+	@FieldMapping(presentWhen = "1.16+")
+	private GoSlice cutab;	// []uint32
+
+	@FieldMapping
+	private GoSlice filetab; // []uint32 when ver <=1.15, []uint8 blob of null term strings when ver >= 1.16
+
+	@FieldMapping(presentWhen = "1.16+")
+	private GoSlice pctab;	// []uint8
+
+	@FieldMapping
+	private GoSlice pclntable;	// []uint8, shares footprint with ftab.  when ver <= 1.15, this is first field and happens to have a GoPcHeader
+
+	@FieldMapping
+	private GoSlice ftab;	// []runtime.functab, shares footprint with pclntable
 
 	@FieldMapping
 	private long data;
@@ -82,24 +100,6 @@ public class GoModuledata implements StructureMarkup {
 	@FieldMapping(fieldName = "typelinks")
 	private GoSlice typeLinks;
 
-	@FieldMapping
-	private GoSlice funcnametab;	// []uint8 blob of null term strings
-
-	@FieldMapping
-	private GoSlice cutab;	// []uint32
-
-	@FieldMapping
-	private GoSlice filetab; // []uint8 blob of null term strings
-
-	@FieldMapping
-	private GoSlice pctab;	// []uint8
-
-	@FieldMapping
-	private GoSlice pclntable;	// []uint8, shares footprint with ftab
-
-	@FieldMapping
-	private GoSlice ftab;	// []runtime.functab, shares footprint with pclntable
-
 	@FieldMapping
 	private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab)
 
@@ -114,17 +114,25 @@ public class GoModuledata implements StructureMarkup {
 	 * Compares the data in this structure to fields in a GoPcHeader and returns true if they
 	 * match.
 	 * 
-	 * @param pclntab GoPcHeader instance
+	 * @param otherPcHeader GoPcHeader instance
 	 * @return boolean true if match, false if no match
 	 */
-	public boolean matchesPclntab(GoPcHeader pclntab) {
-		return (!pclntab.hasTextStart() || pclntab.getTextStart().equals(getText())) &&
-			pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress());
+	public boolean matchesPcHeader(GoPcHeader otherPcHeader) {
+		return (!otherPcHeader.hasTextStart() || otherPcHeader.getTextStart().equals(getText())) &&
+			otherPcHeader.getFuncnameAddress().equals(funcnametab.getArrayAddress());
 	}
 
 	@Markup
 	public GoPcHeader getPcHeader() throws IOException {
-		return programContext.readStructure(GoPcHeader.class, pcHeader);
+		return pcHeader != 0 // when ver >= 1.16 
+				? programContext.readStructure(GoPcHeader.class, pcHeader)
+				: programContext.readStructure(GoPcHeader.class, pclntable.getArrayAddress());
+	}
+
+	public Address getPcHeaderAddress() {
+		return pcHeader != 0
+				? programContext.getDataAddress(pcHeader)
+				: pclntable.getArrayAddress();
 	}
 
 	/**
@@ -236,8 +244,8 @@ public class GoModuledata implements StructureMarkup {
 		}
 
 		// all these static slices should be allocated with len == cap.  If not true, fail.
-		if (!typeLinks.isFull() || !filetab.isFull() || !pctab.isFull() || !pclntable.isFull() ||
-			!ftab.isFull()) {
+		if (!typeLinks.isFull() || !filetab.isFull() || (pctab != null && !pctab.isFull()) ||
+			!pclntable.isFull() || !ftab.isFull()) {
 			return false;
 		}
 
@@ -287,15 +295,8 @@ public class GoModuledata implements StructureMarkup {
 		return filetab;
 	}
 
-	/**
-	 * Returns the filename at the specified offset.
-	 * 
-	 * @param fileoff offset in the filetab of the filename
-	 * @return filename
-	 * @throws IOException if error reading
-	 */
-	public String getFilename(long fileoff) throws IOException {
-		return programContext.getReader(filetab.getElementOffset(1, fileoff)).readNextUtf8String();
+	public GoSlice getPclntable() {
+		return pclntable;
 	}
 
 	/**
@@ -307,6 +308,10 @@ public class GoModuledata implements StructureMarkup {
 		return pctab;
 	}
 
+	public GoSlice getPcValueTable() {
+		return pctab != null ? pctab : pclntable;
+	}
+
 	/**
 	 * Returns a reference to the controlling {@link GoRttiMapper go binary} context.
 	 * 
@@ -329,7 +334,9 @@ public class GoModuledata implements StructureMarkup {
 
 		itablinks.markupArray("moduledata.itablinks", null, GoItab.class, true, session);
 
-		markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
+		if (funcnametab != null) {
+			markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
+		}
 		markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
 
 		GoSlice subSlice = getFunctabEntriesSlice();
@@ -441,15 +448,15 @@ public class GoModuledata implements StructureMarkup {
 	 * Searches memory for a likely GoModuledata structure.
 	 * 
 	 * @param context already initialized {@link GoRttiMapper}
-	 * @param pclntabAddress address of an already found {@link GoPcHeader}
-	 * @param pclntab the {@link GoPcHeader}
+	 * @param pcHeaderAddress address of an already found {@link GoPcHeader}
+	 * @param pcHeader the {@link GoPcHeader}
 	 * @param range memory range to search.  Will be different for different types of binaries
 	 * @param monitor {@link TaskMonitor} 
 	 * @return new GoModuledata instance, or null if not found
 	 * @throws IOException if error reading found structure
 	 */
 	/* package */ static GoModuledata findFirstModule(GoRttiMapper context,
-			Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor)
+			Address pcHeaderAddress, GoPcHeader pcHeader, AddressRange range, TaskMonitor monitor)
 			throws IOException {
 		if (range == null) {
 			return null;
@@ -462,7 +469,7 @@ public class GoModuledata implements StructureMarkup {
 		// field of the GoModuledata structure.
 		int ptrSize = context.getPtrSize();
 		byte[] searchBytes = new byte[ptrSize];
-		context.getDataConverter().putValue(pclntabAddress.getOffset(), ptrSize, searchBytes, 0);
+		context.getDataConverter().putValue(pcHeaderAddress.getOffset(), ptrSize, searchBytes, 0);
 		Address moduleAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
 			searchBytes, null, true, monitor);
 		if (moduleAddr == null) {
@@ -473,50 +480,6 @@ public class GoModuledata implements StructureMarkup {
 
 		// Verify that we read a good GoModuledata struct by comparing some of its values to
 		// the pclntab structure.
-		return moduleData.matchesPclntab(pclntab) ? moduleData : null;
+		return moduleData.matchesPcHeader(pcHeader) ? moduleData : null;
 	}
 }
-
-/*
-struct runtime.moduledata Length:276 Alignment:4{
-  runtime.pcHeader*pcHeader
-  []uint8                                                  funcnametab      
-  []uint32                                                cutab                 
-  []uint8                                                  filetab                 
-  []uint8                                                  pctab                 
-  []uint8                                                  pclntable            
-  []runtime.functab                                ftab
-uintptr                                  findfunctab
-uintptr                                  minpc
-uintptr                                  maxpc
-uintptr                                  text
-uintptr                                  etext
-uintptr                                  noptrdata
-uintptr                                  enoptrdata
-uintptr                                  data
-uintptr                                  edata
-uintptr                                  bss
-uintptr                                  ebss
-uintptr                                  noptrbss
-uintptr                                  enoptrbss
-uintptr                                  end
-uintptr                                  gcdata
-uintptr                                  gcbss
-uintptr                                  types
-uintptr                                  etypes
-uintptr                                  rodata
-uintptr                                  gofunc                
-  []runtime.textsect                              textsectmap       
-  []int32                                                  typelinks             
-  []*runtime.itab                                    itablinks             
-  []
-runtime.ptabEntry ptab
-string                                                   pluginpath          
-  []runtime.modulehash                           pkghashes
-string                                                   modulename      
-  []runtime.modulehash                           modulehashes
-uint8                                     hasmain
-runtime.bitvector                                gcdatamask
-runtime.bitvector                                gcbssmask map[runtime.typeOff]*runtime._type  typemap
-bool                                                       bad runtime.moduledata*next
-}pack()*/
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java
index a56b6d339f..62a6daf388 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcHeader.java
@@ -22,21 +22,26 @@ import ghidra.app.util.bin.format.golang.GoVer;
 import ghidra.app.util.bin.format.golang.structmapping.*;
 import ghidra.program.model.address.Address;
 import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.data.*;
 import ghidra.program.model.lang.Endian;
 import ghidra.program.model.listing.Program;
 import ghidra.program.model.mem.Memory;
 import ghidra.program.model.mem.MemoryBlock;
 import ghidra.program.model.symbol.Symbol;
+import ghidra.util.BigEndianDataConverter;
+import ghidra.util.LittleEndianDataConverter;
 import ghidra.util.task.TaskMonitor;
 
 /**
  * A low-level structure embedded in golang binaries that contains useful bootstrapping
  * information.
  * 

+ * Introduced in golang 1.16 * */ -@StructureMapping(structureName = "runtime.pcHeader") +@StructureMapping(structureName = GoPcHeader.GO_STRUCTURE_NAME) public class GoPcHeader { + public static final String GO_STRUCTURE_NAME = "runtime.pcHeader"; private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab"; public static final String GOPCLNTAB_SECTION_NAME = "gopclntab"; public static final int GO_1_2_MAGIC = 0xfffffffb; @@ -49,29 +54,27 @@ public class GoPcHeader { * @param program {@link Program} * @return {@link Address} of go pclntab, or null if not present */ - public static Address getPclntabAddress(Program program) { + public static Address getPcHeaderAddress(Program program) { MemoryBlock pclntabBlock = GoRttiMapper.getGoSection(program, GOPCLNTAB_SECTION_NAME); if (pclntabBlock != null) { return pclntabBlock.getStart(); } // PE binaries have a symbol instead of a named section Symbol pclntabSymbol = GoRttiMapper.getGoSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME); - return pclntabSymbol != null - ? pclntabSymbol.getAddress() - : null; + return pclntabSymbol != null ? pclntabSymbol.getAddress() : null; } /** - * Returns true if the specified program has an easily found pclntab + * Returns true if the specified program has an easily found pclntab w/pcHeader * * @param program {@link Program} * @return boolean true if program has a pclntab, false otherwise */ - public static boolean hasPclntab(Program program) { - Address addr = getPclntabAddress(program); + public static boolean hasPcHeader(Program program) { + Address addr = getPcHeaderAddress(program); if (addr != null) { try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) { - return isPclntab(provider); + return isPcHeader(provider); } catch (IOException e) { // fall thru @@ -81,43 +84,43 @@ public class GoPcHeader { } /** - * Searches (possibly slowly) for a pclntab structure in the specified memory range, which - * is typically necessary in stripped PE binaries. + * Searches (possibly slowly) for a pclntab/pcHeader structure in the specified memory range, + * which is typically necessary in stripped PE binaries. * * @param programContext {@link GoRttiMapper} * @param range memory range to search (typically .rdata or .noptrdata sections) * @param monitor {@link TaskMonitor} that will let the user cancel - * @return {@link Address} of the found pclntab structure, or null if not found + * @return {@link Address} of the found pcHeader structure, or null if not found * @throws IOException if error reading */ - public static Address findPclntabAddress(GoRttiMapper programContext, AddressRange range, + public static Address findPcHeaderAddress(GoRttiMapper programContext, AddressRange range, TaskMonitor monitor) throws IOException { if (range == null) { return null; } // search for magic signature + padding + wildcard_minLc + ptrSize - byte[] searchBytes = new byte[/*4 + 2 + 1 + 1*/] { + byte[] searchBytes = new byte[] { // 4 + 2 + 1 + 1 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // magic signature 0, 0, // padding 0, // unknown minLc, masked (byte) programContext.getPtrSize() // ptrSize }; - byte[] searchMask = new byte[] { + byte[] searchMask = new byte[] { // also 4 + 2 + 1 + 1 (byte) 0xf0, (byte) 0xff, (byte) 0xff, (byte) 0xf0, // magic, first byte nibble and last byte nibble is wildcard to handle either endian matching (byte) 0xff, (byte) 0xff, // padding 0, // unknown minLc - wildcarded (byte) 0xff // ptrSize }; Memory memory = programContext.getProgram().getMemory(); - Address pclntabAddr = - memory.findBytes(range.getMinAddress(), range.getMaxAddress(), searchBytes, searchMask, - true, monitor); - if (pclntabAddr == null) { + Address pcHeaderAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(), + searchBytes, searchMask, true, monitor); + if (pcHeaderAddr == null) { return null; } - MemoryByteProvider bp = - new MemoryByteProvider(memory, pclntabAddr, range.getMaxAddress()); - return isPclntab(bp) ? pclntabAddr : null; + try (MemoryByteProvider bp = + new MemoryByteProvider(memory, pcHeaderAddr, range.getMaxAddress())) { + return isPcHeader(bp) ? pcHeaderAddr : null; + } } /** @@ -127,11 +130,10 @@ public class GoPcHeader { * @return boolean true if the byte provider has the magic signature of a pclntab * @throws IOException if error reading */ - public static boolean isPclntab(ByteProvider provider) throws IOException { + public static boolean isPcHeader(ByteProvider provider) throws IOException { byte[] header = provider.readBytes(0, 8); // logic from pclntab.go parsePclnTab() - if (provider.length() < 16 || - header[4] != 0 || header[5] != 0 || // pad bytes == 0 + if (provider.length() < 16 || header[4] != 0 || header[5] != 0 || // pad bytes == 0 (header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4 (header[7] != 4 && header[7] != 8) // ptrSize == 4,8 ) { @@ -140,6 +142,23 @@ public class GoPcHeader { return readMagic(provider) != null; } + public static Structure createArtificialGoPcHeaderStructure(CategoryPath cp, + DataTypeManager dtm) { + // this manually creates a minimal struct that matches the header of a <=1.15 pclntab + // section + StructureDataType struct = new StructureDataType(cp, GO_STRUCTURE_NAME, 0, dtm); + struct.setDescription( + "Artificial structure created by Ghidra to represent the header of the pclntable"); + struct.setPackingEnabled(true); + struct.add(AbstractIntegerDataType.getUnsignedDataType(4, null), "magic", null); + struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "pad1", null); + struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "pad2", null); + struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "minLC", null); + struct.add(AbstractIntegerDataType.getUnsignedDataType(1, null), "ptrSize", null); + + return struct; + } + @ContextField private GoRttiMapper programContext; @@ -156,27 +175,27 @@ public class GoPcHeader { @FieldMapping private byte ptrSize; - @FieldMapping(optional = true) // present >= 1.18 + @FieldMapping(presentWhen = "1.18+") @MarkupReference private long textStart; // should be same as offset of ".text" - @FieldMapping + @FieldMapping(presentWhen = "1.16+") @MarkupReference("getFuncnameAddress") private long funcnameOffset; - @FieldMapping + @FieldMapping(presentWhen = "1.16+") @MarkupReference("getCuAddress") private long cuOffset; - @FieldMapping + @FieldMapping(presentWhen = "1.16+") @MarkupReference("getFiletabAddress") private long filetabOffset; - @FieldMapping + @FieldMapping(presentWhen = "1.16+") @MarkupReference("getPctabAddress") private long pctabOffset; - @FieldMapping + @FieldMapping(presentWhen = "1.16+") @MarkupReference("getPclnAddress") private long pclnOffset; @@ -187,7 +206,7 @@ public class GoPcHeader { case GO_1_2_MAGIC -> GoVer.V1_2; case GO_1_16_MAGIC -> GoVer.V1_16; case GO_1_18_MAGIC -> GoVer.V1_18; - default -> GoVer.UNKNOWN; + default -> GoVer.INVALID; }; return ver; } @@ -214,7 +233,9 @@ public class GoPcHeader { * @return address of func name slice */ public Address getFuncnameAddress() { - return programContext.getDataAddress(context.getStructureStart() + funcnameOffset); + return funcnameOffset != 0 + ? programContext.getDataAddress(context.getStructureStart() + funcnameOffset) + : null; } /** @@ -222,7 +243,9 @@ public class GoPcHeader { * @return address of the cu tab slice */ public Address getCuAddress() { - return programContext.getDataAddress(context.getStructureStart() + cuOffset); + return cuOffset != 0 + ? programContext.getDataAddress(context.getStructureStart() + cuOffset) + : null; } /** @@ -230,7 +253,9 @@ public class GoPcHeader { * @return address of the filetab slice */ public Address getFiletabAddress() { - return programContext.getDataAddress(context.getStructureStart() + filetabOffset); + return filetabOffset != 0 + ? programContext.getDataAddress(context.getStructureStart() + filetabOffset) + : null; } /** @@ -238,7 +263,9 @@ public class GoPcHeader { * @return address of the pctab slice */ public Address getPctabAddress() { - return programContext.getDataAddress(context.getStructureStart() + pctabOffset); + return pctabOffset != 0 + ? programContext.getDataAddress(context.getStructureStart() + pctabOffset) + : null; } /** @@ -246,7 +273,9 @@ public class GoPcHeader { * @return address of the pcln slice */ public Address getPclnAddress() { - return programContext.getDataAddress(context.getStructureStart() + pclnOffset); + return pclnOffset != 0 + ? programContext.getDataAddress(context.getStructureStart() + pclnOffset) + : null; } /** @@ -273,8 +302,9 @@ public class GoPcHeader { } private static GoVerEndian readMagic(ByteProvider provider) throws IOException { - int leMagic = new BinaryReader(provider, true /* little endian */).readInt(0); - int beMagic = new BinaryReader(provider, false /* big endian */).readInt(0); + BinaryReader reader = new BinaryReader(provider, true); + int leMagic = reader.readInt(LittleEndianDataConverter.INSTANCE, 0); + int beMagic = reader.readInt(BigEndianDataConverter.INSTANCE, 0); if (leMagic == GO_1_2_MAGIC || beMagic == GO_1_2_MAGIC) { return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java index 5bf66867b5..57160d1140 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoPcValueEvaluator.java @@ -46,7 +46,7 @@ public class GoPcValueEvaluator { GoModuledata moduledata = func.getModuledata(); this.pcquantum = moduledata.getGoBinary().getMinLC(); - this.reader = moduledata.getPctab().getElementReader(1, (int) offset); + this.reader = moduledata.getPcValueTable().getElementReader(1, (int) offset); this.funcEntry = func.getFuncAddress().getOffset(); this.pc = funcEntry; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java index 34a9353539..f006453c9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java @@ -78,7 +78,13 @@ import ghidra.util.task.UnknownProgressWrappingTaskMonitor; * * */ -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 SYMBOL_SEARCH_PREFIXES = List.of("", "_" /* macho symbols */); + private static final List SECTION_PREFIXES = + List.of("." /* ELF */, "__" /* macho sections */); private static final String FAILED_FLAG = "FAILED TO FIND GOLANG BINARY"; @@ -163,20 +169,24 @@ public class GoRttiMapper extends DataTypeMapper { return null; } - GoVer goVer = buildInfo.getVerEnum(); - if (goVer == GoVer.UNKNOWN) { + GoVer goVer = buildInfo.getGoVer(); + if (goVer.isInvalid()) { throw new BootstrapInfoException( - "Unsupported Golang version, version info: '%s'".formatted(buildInfo.getVersion())); + "Invalid Golang version string [%s]".formatted(buildInfo.getVersion())); + } + if (!goVer.inRange(SUPPORTED_MIN_VER, SUPPORTED_MAX_VER)) { + Msg.error(GoRttiMapper.class, "Untested golang version [%s]".formatted(goVer)); } ResourceFile gdtFile = findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program)); if (gdtFile == null) { - Msg.error(GoRttiMapper.class, "Missing golang gdt archive for " + goVer); + Msg.error(GoRttiMapper.class, + "Missing golang gdt archive for golang version [%s]".formatted(goVer)); } - return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), - buildInfo.getVerEnum(), gdtFile); + return new GoRttiMapper(program, buildInfo.getPointerSize(), buildInfo.getEndian(), goVer, + gdtFile); } /** @@ -267,10 +277,6 @@ public class GoRttiMapper extends DataTypeMapper { return false; } - private static final List SYMBOL_SEARCH_PREFIXES = List.of("", "_" /* macho symbols */); - private static final List SECTION_PREFIXES = - List.of("." /* ELF */, "__" /* macho sections */); - /** * Returns a matching symbol from the specified program, using golang specific logic. * @@ -328,6 +334,16 @@ public class GoRttiMapper extends DataTypeMapper { return zerobaseAddr; } + public static List getAllSupportedVersions() { + List result = new ArrayList<>(); + for (int minor = SUPPORTED_MIN_VER.getMinor(); minor <= SUPPORTED_MAX_VER + .getMinor(); minor++) { + // TODO: a bit of a hack only supporting 1.x version number instances + result.add(new GoVer(1, minor)); + } + return result; + } + public final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME = "ARTIFICIAL.runtime.zerobase"; @@ -415,7 +431,7 @@ public class GoRttiMapper extends DataTypeMapper { this.stringDT = getTypeOrDefault("string", Structure.class, null); try { - registerStructures(GOLANG_STRUCTMAPPED_CLASSES); + registerStructures(GOLANG_STRUCTMAPPED_CLASSES, this); } catch (IOException e) { if (archiveGDT == null) { @@ -433,6 +449,20 @@ public class GoRttiMapper extends DataTypeMapper { } } + @Override + public T getType(String name, Class clazz) { + T result = super.getType(name, clazz); + if (result == null && GoPcHeader.GO_STRUCTURE_NAME.equals(name) && + Structure.class.isAssignableFrom(clazz)) { + // create an artificial runtime.pcHeader structure for <=1.15 to enable GoModuledata + // to have references to a GoPcHeader + result = clazz.cast(GoPcHeader.createArtificialGoPcHeaderStructure(GOLANG_CP, + program.getDataTypeManager())); + } + return result; + } + + /** * Returns the golang version * @return {@link GoVer} @@ -462,10 +492,12 @@ public class GoRttiMapper extends DataTypeMapper { GoModuledata firstModule = findFirstModuledata(monitor); if (firstModule != null) { GoPcHeader pcHeader = firstModule.getPcHeader(); - this.minLC = pcHeader.getMinLC(); - if (pcHeader.getPtrSize() != ptrSize) { - throw new IOException( - "Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize)); + if (pcHeader != null) { + this.minLC = pcHeader.getMinLC(); + if (pcHeader.getPtrSize() != ptrSize) { + throw new IOException( + "Mismatched ptrSize: %d vs %d".formatted(pcHeader.getPtrSize(), ptrSize)); + } } addModule(firstModule); } @@ -1401,22 +1433,22 @@ public class GoRttiMapper extends DataTypeMapper { private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException { GoModuledata result = GoModuledata.getFirstModuledata(this); - if (result == null) { - monitor.setMessage("Searching for Golang pclntab"); - monitor.initialize(0); - Address pclntabAddress = GoPcHeader.getPclntabAddress(program); - if (pclntabAddress == null) { - pclntabAddress = - GoPcHeader.findPclntabAddress(this, getPclntabSearchRange(), monitor); - } - if (pclntabAddress != null) { - monitor.setMessage("Searching for Golang firstmoduledata"); - monitor.initialize(0); - GoPcHeader pclntab = readStructure(GoPcHeader.class, pclntabAddress); - result = GoModuledata.findFirstModule(this, pclntabAddress, pclntab, - getModuledataSearchRange(), monitor); - } + Address pcHeaderAddress = + result != null ? result.getPcHeaderAddress() : GoPcHeader.getPcHeaderAddress(program); + if (pcHeaderAddress == null) { + monitor.initialize(0, "Searching for Golang pclntab"); + pcHeaderAddress = + GoPcHeader.findPcHeaderAddress(this, getPclntabSearchRange(), monitor); } + if (result == null && pcHeaderAddress != null) { + // find the moduledata struct by searching for a pointer to the pclntab/pcHeader, + // which should be the first field in the moduledata struct. + monitor.initialize(0, "Searching for Golang firstmoduledata"); + GoPcHeader pcHeader = readStructure(GoPcHeader.class, pcHeaderAddress); + result = GoModuledata.findFirstModule(this, pcHeaderAddress, pcHeader, + getModuledataSearchRange(), monitor); + } + if (result != null && !result.isValid()) { throw new IOException("Invalid Golang moduledata at %s" .formatted(result.getStructureContext().getStructureAddress())); @@ -1554,4 +1586,25 @@ public class GoRttiMapper extends DataTypeMapper { } } + + @Override + public boolean isFieldPresent(String presentWhen) { + presentWhen = presentWhen.strip(); + if (presentWhen.isEmpty()) { + return true; + } + String[] verNums = presentWhen.split("[+-]", -1); // "1.2-1.5" or "1.2+" or "-1.2" + if (verNums.length != 2) { + throw new IllegalArgumentException( + "Invalid 'presentWhen' value [%s]".formatted(presentWhen)); + } + GoVer startVer = verNums[0].isBlank() ? new GoVer(1, 0) : GoVer.parse(verNums[0]); + GoVer endVer = verNums[1].isBlank() ? new GoVer(99, 99) : GoVer.parse(verNums[1]); + if (startVer.isInvalid() || endVer.isInvalid()) { + throw new IllegalArgumentException( + "Invalid 'presentWhen' value [%s]".formatted(presentWhen)); + } + return startVer.compareTo(goVersion) <= 0 && goVersion.compareTo(endVer) <= 0; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java index 6e5685203d..1513f0a5bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoVarlenString.java @@ -20,11 +20,14 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.golang.GoVer; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.program.model.data.*; +import ghidra.util.BigEndianDataConverter; /** - * A pascal-ish string, using a LEB128 value as the length of the following bytes. + * A pascal-ish string, using a LEB128 (or a uint16 in pre-1.16) value as the length of the + * following bytes. *

* Used mainly in lower-level RTTI structures, this class is a ghidra'ism used to parse the * golang rtti data and does not have a counterpart in the golang src. @@ -51,9 +54,16 @@ public class GoVarlenString implements StructureReader { readFrom(context.getReader()); } + private boolean useLEB128() { + return ((GoRttiMapper) context.getDataTypeMapper()).getGolangVersion() + .isAtLeast(GoVer.V1_17); + } + private void readFrom(BinaryReader reader) throws IOException { long startPos = reader.getPointerIndex(); - int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + int strLen = useLEB128() + ? reader.readNextUnsignedVarIntExact(LEB128::unsigned) + : reader.readNextUnsignedShort(BigEndianDataConverter.INSTANCE); this.strlenLen = (int) (reader.getPointerIndex() - startPos); this.bytes = reader.readNextByteArray(strLen); } @@ -68,9 +78,9 @@ public class GoVarlenString implements StructureReader { } /** - * Returns the string length's length (length of the leb128 number) + * Returns the size of the string length field. * - * @return string length's length + * @return size of the string length field */ public int getStrlenLen() { return strlenLen; @@ -100,8 +110,11 @@ public class GoVarlenString implements StructureReader { * @return data type needed to hold the string length field */ public DataTypeInstance getStrlenDataType() { - return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen, - false); + DataType dt = useLEB128() + ? UnsignedLeb128DataType.dataType + : AbstractIntegerDataType.getUnsignedDataType(2, null); + + return DataTypeInstance.getDataTypeInstance(dt, strlenLen, false); } /** @@ -119,5 +132,4 @@ public class GoVarlenString implements StructureReader { return String.format("GoVarlenString [context=%s, strlenLen=%s, bytes=%s, getString()=%s]", context, strlenLen, Arrays.toString(bytes), getString()); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java index 1719ffceb9..f17c7f4e07 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoArrayType.java @@ -20,8 +20,7 @@ import java.util.Set; import ghidra.app.util.bin.format.golang.structmapping.*; import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.DataType; +import ghidra.program.model.data.*; /** * {@link GoType} structure that defines an array. @@ -72,7 +71,15 @@ public class GoArrayType extends GoType { if (self != null) { return self; } - return new ArrayDataType(elementDt, (int) len, -1); + return isValidLength() + ? new ArrayDataType(elementDt, (int) len, -1) + : new TypedefDataType(elementDt.getCategoryPath(), + ".invalid_arraysize_%d_%s".formatted(len, elementDt.getName()), + new ArrayDataType(elementDt, 1, -1), elementDt.getDataTypeManager()); + } + + private boolean isValidLength() { + return 0 <= len && len <= Integer.MAX_VALUE; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java index 1474c29559..d379b3e5f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/types/GoStructField.java @@ -42,10 +42,10 @@ public class GoStructField { @MarkupReference("getType") private long typ; // direct ptr to GoType - @FieldMapping(optional = true) //<=1.18 + @FieldMapping(presentWhen = "-1.18") private long offsetAnon; // offsetAnon >> 1 == actual offset, bit 0 = embedded flag - @FieldMapping(optional = true) //>=1.19 + @FieldMapping(presentWhen = "1.19+") private long offset; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java index 690b4aa795..85cc8b6c53 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapper.java @@ -31,7 +31,7 @@ import ghidra.util.task.TaskMonitor; /** * Information about {@link StructureMapping} classes and their metadata. *

- * 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} * ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data * types being used, and it must {@link #registerStructure(Class) know} about all classes that are @@ -166,9 +166,11 @@ public class DataTypeMapper implements AutoCloseable { * @param structure mapped class type * @param clazz class that represents a structure, marked with {@link StructureMapping} * annotation + * @param context {@link DataTypeMapperContext} * @throws IOException if the class's Ghidra structure data type could not be found */ - public void registerStructure(Class clazz) throws IOException { + public void registerStructure(Class clazz, DataTypeMapperContext context) + throws IOException { StructureMapping sma = clazz.getAnnotation(StructureMapping.class); List structNames = sma != null ? Arrays.asList(sma.structureName()) : List.of(); Structure structDT = getType(structNames, Structure.class); @@ -187,7 +189,7 @@ public class DataTypeMapper implements AutoCloseable { try { StructureMappingInfo structMappingInfo = - StructureMappingInfo.fromClass(clazz, structDT); + StructureMappingInfo.fromClass(clazz, structDT, context); mappingInfo.put(clazz, structMappingInfo); } catch (IllegalArgumentException e) { @@ -199,11 +201,13 @@ public class DataTypeMapper implements AutoCloseable { * Registers the specified {@link StructureMapping structure mapping} classes. * * @param classes list of classes to register + * @param context {@link DataTypeMapperContext} * @throws IOException if a class's Ghidra structure data type could not be found */ - public void registerStructures(List> classes) throws IOException { + public void registerStructures(List> classes, DataTypeMapperContext context) + throws IOException { for (Class clazz : classes) { - registerStructure(clazz); + registerStructure(clazz, context); } } @@ -544,5 +548,4 @@ public class DataTypeMapper implements AutoCloseable { } return new StructureContext<>(this, smi, null); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapperContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapperContext.java new file mode 100644 index 0000000000..1b19de89e3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/DataTypeMapperContext.java @@ -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); + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java index 29ccc9f54f..bb8ee0f9e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/FieldMapping.java @@ -62,6 +62,22 @@ public @interface FieldMapping { */ boolean optional() default false; + /** + * Marks this field as only present in certain context configurations. + *

+ * The specified string is interpreted by the specific {@link DataTypeMapper} and its + * {@link DataTypeMapperContext context}. + *

+ * 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. + *

+ * Similar to {@link #optional()} + * + * @return String + */ + String presentWhen() default ""; + /** * Specifies the name of a setter method that will be used to assign the deserialized value * to the java field. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java index 10927ebdd9..f6c4392be8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/structmapping/StructureMappingInfo.java @@ -39,18 +39,19 @@ public class StructureMappingInfo { * @param targetClass structure mapped class * @param structDataType Ghidra {@link DataType} that defines the binary layout of the mapped * fields of the class, or null if this is a self-reading {@link StructureReader} class + * @param context {@link DataTypeMapperContext} * @return new {@link StructureMappingInfo} for the specified class * @throws IllegalArgumentException if targetClass isn't tagged as a structure mapped class */ public static StructureMappingInfo fromClass(Class targetClass, - Structure structDataType) { + Structure structDataType, DataTypeMapperContext context) { StructureMapping sma = targetClass.getAnnotation(StructureMapping.class); if (sma == null) { throw new IllegalArgumentException( "Missing @StructureMapping annotation on " + targetClass.getSimpleName()); } - return new StructureMappingInfo<>(targetClass, structDataType, sma); + return new StructureMappingInfo<>(targetClass, structDataType, sma, context); } private final Class targetClass; @@ -69,17 +70,17 @@ public class StructureMappingInfo { private Field structureContextField; private StructureMappingInfo(Class targetClass, Structure structDataType, - StructureMapping sma) { + StructureMapping sma, DataTypeMapperContext context) { this.targetClass = targetClass; this.structureDataType = structDataType; this.structureName = structureDataType != null ? structureDataType.getName() : sma.structureName()[0]; - this.fieldNameLookup = indexStructFields(structDataType); + this.fieldNameLookup = indexStructFields(); this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass); this.instanceCreator = findInstanceCreator(); - readFieldInfo(targetClass); + readFieldInfo(targetClass, context); Collections.sort(outputFields, (foi1, foi2) -> Integer.compare(foi1.getOrdinal(), foi2.getOrdinal())); @@ -249,10 +250,10 @@ public class StructureMappingInfo { } } - private void readFieldInfo(Class clazz) { + private void readFieldInfo(Class clazz, DataTypeMapperContext context) { Class superclass = clazz.getSuperclass(); if (superclass != null) { - readFieldInfo(superclass); + readFieldInfo(superclass, context); } for (Field field : clazz.getDeclaredFields()) { @@ -260,7 +261,7 @@ public class StructureMappingInfo { FieldMapping fma = field.getAnnotation(FieldMapping.class); FieldOutput foa = field.getAnnotation(FieldOutput.class); if (fma != null || foa != null) { - FieldMappingInfo fmi = readFieldMappingInfo(field, fma); + FieldMappingInfo fmi = readFieldMappingInfo(field, fma, context); if (fmi == null) { // was marked optional field, just skip continue; @@ -287,7 +288,14 @@ public class StructureMappingInfo { } } - private FieldMappingInfo readFieldMappingInfo(Field field, FieldMapping fma) { + private FieldMappingInfo readFieldMappingInfo(Field field, FieldMapping fma, + DataTypeMapperContext context) { + + if (fma != null && !context.isFieldPresent(fma.presentWhen())) { + // skip if this field was marked as not present + return null; + } + String[] fieldNames = getFieldNamesToSearchFor(field, fma); DataTypeComponent dtc = getFirstMatchingField(fieldNames); if (useFieldMappingInfo && dtc == null) { @@ -379,12 +387,12 @@ public class StructureMappingInfo { return struct.isZeroLength() ? 0 : struct.getLength(); } - private static Map indexStructFields(Structure struct) { - if (struct == null) { + private Map indexStructFields() { + if (structureDataType == null) { return Map.of(); } Map result = new HashMap<>(); - for (DataTypeComponent dtc : struct.getDefinedComponents()) { + for (DataTypeComponent dtc : structureDataType.getDefinedComponents()) { String fieldName = dtc.getFieldName(); if (fieldName != null) { result.put(fieldName.toLowerCase(), dtc); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java index ff8ff98917..2406b9079d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeArchiveIDTest.java @@ -45,6 +45,8 @@ public class DataTypeArchiveIDTest extends AbstractGenericTest { Map.entry(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631"), Map.entry(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971"), Map.entry(MAC_OS_10_9_GDT_PATH, "2650667045259492112"), + Map.entry("typeinfo/golang/golang_1.15_anybit_any.gdt", "3600806988729184131"), + Map.entry("typeinfo/golang/golang_1.16_anybit_any.gdt", "3597021567582750001"), Map.entry("typeinfo/golang/golang_1.17_anybit_any.gdt", "3533627828569507753"), Map.entry("typeinfo/golang/golang_1.18_anybit_any.gdt", "3528902399865061936"), Map.entry("typeinfo/golang/golang_1.19_anybit_any.gdt", "3533812166493410774"), diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs b/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs index 9d83a37e1e..dad5496b43 100644 --- a/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs +++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64.ldefs @@ -15,6 +15,7 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64_golang.register.info b/Ghidra/Processors/AARCH64/data/languages/AARCH64_golang.register.info index ff42eb3c66..683cba8998 100644 --- a/Ghidra/Processors/AARCH64/data/languages/AARCH64_golang.register.info +++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64_golang.register.info @@ -6,5 +6,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info b/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info index 28c42ff062..98b573e051 100644 --- a/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info +++ b/Ghidra/Processors/x86/data/languages/x86-32-golang.register.info @@ -5,5 +5,6 @@ + \ No newline at end of file diff --git a/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec index 8196a9a68e..ed1d68d372 100644 --- a/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec +++ b/Ghidra/Processors/x86/data/languages/x86-64-golang.cspec @@ -26,16 +26,16 @@ - + - + - + - + @@ -63,7 +63,7 @@ - + @@ -195,6 +195,14 @@ + + + + + + + + @@ -228,8 +236,6 @@ - - diff --git a/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info b/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info index 49222ecea8..802caffb0a 100644 --- a/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info +++ b/Ghidra/Processors/x86/data/languages/x86-64-golang.register.info @@ -6,5 +6,14 @@ + + + + + + + + + \ No newline at end of file