GP-4183 add golang 1.21 support

This commit is contained in:
dev747368 2024-01-04 19:26:52 +00:00
parent 514fc2fc54
commit 5e18efdd0d
13 changed files with 197 additions and 123 deletions

View file

@ -91,6 +91,7 @@ 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|
data/typeinfo/golang/golang_1.20_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.21_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/runtimesnapshot.go||GHIDRA||||END|
data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END|
data/typeinfo/rust/rust-common.gdt||GHIDRA||||END|

View file

@ -99,7 +99,7 @@ public class GolangStringAnalyzer extends AbstractAnalyzer {
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
if (goBinary == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper");
Msg.error(this, "Golang string analyzer error: unable to get GoRttiMapper");
return false;
}
markupSession = goBinary.createMarkupSession(monitor);

View file

@ -112,7 +112,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
if (goBinary == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper");
Msg.error(this, "Golang symbol analyzer error: unable to get GoRttiMapper");
return false;
}
@ -136,6 +136,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
}
if (analyzerOptions.propagateRtti) {
Msg.info(this, "Golang symbol analyzer: scheduling RTTI propagation after reference analysis");
aam.schedule(new PropagateRttiBackgroundCommand(goBinary),
AnalysisPriority.REFERENCE_ANALYSIS.after().priority());
}

View file

@ -0,0 +1,38 @@
/* ###
* 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;
import java.io.IOException;
public class BootstrapInfoException extends IOException {
public BootstrapInfoException() {
// empty
}
public BootstrapInfoException(String message) {
super(message);
}
public BootstrapInfoException(Throwable cause) {
super(cause);
}
public BootstrapInfoException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -35,7 +35,7 @@ public enum GoVer {
private final int major;
private final int minor;
private GoVer(int major, int minor) {
GoVer(int major, int minor) {
this.major = major;
this.minor = minor;
}
@ -91,7 +91,7 @@ public enum GoVer {
}
}
catch (NumberFormatException e) {
return UNKNOWN;
// fall thru, return unknown
}
return UNKNOWN;
}

View file

@ -112,54 +112,63 @@ public class GoRttiMapper extends DataTypeMapper {
GoRttiMapper supplier_result = getGoBinary(program);
if (supplier_result != null) {
supplier_result.init(monitor);
return supplier_result;
}
return supplier_result;
}
catch (IllegalArgumentException | IOException e) {
TransientProgramProperties.getProperty(program, FAILED_FLAG,
TransientProgramProperties.SCOPE.PROGRAM, Boolean.class, () -> true); // also sets it
if (e instanceof IOException) {
// this is a more serious error, and the stack trace should be written
// to the application log
Msg.error(GoRttiMapper.class,
"Failed to read golang info for: " + program.getName(), e);
}
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program);
if (aam.isAnalyzing()) {
// should cause a modal popup at end of analysis that the go binary wasn't
// supported
MessageLog log = aam.getMessageLog();
log.appendMsg(e.getMessage());
}
else {
Msg.warn(GoRttiMapper.class, "Golang program: " + e.getMessage());
}
return null;
catch (BootstrapInfoException mbie) {
Msg.warn(GoRttiMapper.class, mbie.getMessage());
logAnalyzerMsg(program, mbie.getMessage());
}
catch (IOException e) {
// this is a more serious error, and the stack trace should be written
// to the application log
Msg.error(GoRttiMapper.class, "Failed to read golang info", e);
logAnalyzerMsg(program, e.getMessage());
}
// this sets the failed flag
TransientProgramProperties.getProperty(program, FAILED_FLAG,
TransientProgramProperties.SCOPE.PROGRAM, Boolean.class, () -> true);
return null;
});
return goBinary;
}
private static void logAnalyzerMsg(Program program, String msg) {
AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program);
if (aam.isAnalyzing()) {
// should cause a modal popup at end of analysis that will show the message
MessageLog log = aam.getMessageLog();
log.appendMsg(msg);
}
}
/**
* Creates a {@link GoRttiMapper} representing the specified program.
*
* @param program {@link Program}
* @return new {@link GoRttiMapper}, or null if basic golang information is not found in the
* binary
* @throws IllegalArgumentException if the golang binary is an unsupported version
* @throws BootstrapInfoException if it is a golang binary and has an unsupported or
* unparseable version number or if there was a missing golang bootstrap .gdt file
* @throws IOException if there was an error in the Ghidra golang rtti reading logic
*/
public static GoRttiMapper getGoBinary(Program program)
throws IllegalArgumentException, IOException {
throws BootstrapInfoException, IOException {
GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program);
GoVer goVer;
if (buildInfo == null || (goVer = buildInfo.getVerEnum()) == GoVer.UNKNOWN) {
if (buildInfo == null) {
// probably not a golang binary
return null;
}
GoVer goVer = buildInfo.getVerEnum();
if (goVer == GoVer.UNKNOWN) {
throw new BootstrapInfoException(
"Unsupported Golang version, version info: '%s'".formatted(buildInfo.getVersion()));
}
ResourceFile gdtFile =
findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program));
if (gdtFile == null) {
@ -324,11 +333,11 @@ public class GoRttiMapper extends DataTypeMapper {
* if not present and types recovered via DWARF should be used instead
* @throws IOException if error linking a structure mapped structure to its matching
* ghidra structure, which is a programming error or a corrupted bootstrap gdt
* @throws IllegalArgumentException if there is no matching bootstrap gdt for this specific
* @throws BootstrapInfoException if there is no matching bootstrap gdt for this specific
* type of golang binary
*/
public GoRttiMapper(Program program, int ptrSize, Endian endian, GoVer goVersion,
ResourceFile archiveGDT) throws IOException, IllegalArgumentException {
ResourceFile archiveGDT) throws IOException, BootstrapInfoException {
super(program, archiveGDT);
this.goVersion = goVersion;
@ -357,13 +366,15 @@ public class GoRttiMapper extends DataTypeMapper {
if (archiveGDT == null) {
// a normal'ish situation where there isn't a .gdt for this arch/binary and there
// isn't any DWARF.
throw new IllegalArgumentException(
"Missing golang .gdt archive for %s, no fallback DWARF info, unable to extract golang RTTI info."
throw new BootstrapInfoException(
"Missing golang .gdt archive for %s, no fallback DWARF info, unable to extract Golang RTTI info."
.formatted(goVersion));
}
// a bad situation where the data type info is corrupted
throw new IOException("Invalid or missing Golang bootstrap GDT file: %s"
.formatted(archiveGDT.getAbsolutePath()));
// we have a .gdt, but something failed.
throw new IOException("Invalid Golang bootstrap GDT file or struct mapping info: %s"
.formatted(archiveGDT.getAbsolutePath()),
e);
}
}

View file

@ -169,26 +169,30 @@ public class DataTypeMapper implements AutoCloseable {
* @throws IOException if the class's Ghidra structure data type could not be found
*/
public <T> void registerStructure(Class<T> clazz) throws IOException {
Structure structDT = null;
for (String structName : StructureMappingInfo.getStructureDataTypeNameForClass(clazz)) {
if (structName != null && !structName.isBlank()) {
structDT = getType(structName, Structure.class);
if (structDT != null) {
break;
}
StructureMapping sma = clazz.getAnnotation(StructureMapping.class);
List<String> structNames = sma != null ? Arrays.asList(sma.structureName()) : List.of();
Structure structDT = getType(structNames, Structure.class);
if (structDT == null) {
String dtName = structNames.isEmpty() ? "<missing>" : String.join("|", structNames);
if (!StructureReader.class.isAssignableFrom(clazz)) {
throw new IOException("Missing struct definition for class %s, structure name: [%s]"
.formatted(clazz.getSimpleName(), dtName));
}
}
if (!StructureReader.class.isAssignableFrom(clazz) && structDT == null) {
String structName = StructureMappingInfo.getStructureDataTypeNameForClass(clazz)[0];
if (structName == null || structName.isBlank()) {
structName = "<missing>";
if (structNames.size() != 1) {
throw new IOException(
"Bad StructMapping,StructureReader definition for class %s, structure name: [%s]"
.formatted(clazz.getSimpleName(), dtName));
}
throw new IOException(
"Missing struct definition %s - %s".formatted(clazz.getSimpleName(), structName));
}
StructureMappingInfo<T> structMappingInfo = StructureMappingInfo.fromClass(clazz, structDT);
mappingInfo.put(clazz, structMappingInfo);
try {
StructureMappingInfo<T> structMappingInfo =
StructureMappingInfo.fromClass(clazz, structDT);
mappingInfo.put(clazz, structMappingInfo);
}
catch (IllegalArgumentException e) {
throw new IOException(e.getMessage());
}
}
/**
@ -287,7 +291,32 @@ public class DataTypeMapper implements AutoCloseable {
}
/**
* Returns a named {@link DataType}, searching the registered
* Returns a named {@link DataType}, searching the registered
* {@link #addProgramSearchCategoryPath(CategoryPath...) program}
* and {@link #addArchiveSearchCategoryPath(CategoryPath...) archive} category paths.
* <p>
* DataTypes that were found in the attached archive gdt manager will be copied into the
* program's data type manager before being returned.
*
* @param <T> DataType or derived type
* @param names list containing the data type name and any alternates
* @param clazz expected DataType class
* @return DataType or null if not found
*/
public <T extends DataType> T getType(List<String> names, Class<T> clazz) {
for (String dtName : names) {
if (dtName != null && !dtName.isBlank()) {
T result = getType(dtName, clazz);
if (result != null) {
return result;
}
}
}
return null;
}
/**
* Returns a named {@link DataType}, searching the registered
* {@link #addProgramSearchCategoryPath(CategoryPath...) program}
* and {@link #addArchiveSearchCategoryPath(CategoryPath...) archive} category paths.
* <p>

View file

@ -15,8 +15,8 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -44,10 +44,10 @@ public @interface FieldMapping {
* Overrides the field name that is matched in the structure.
* <p>
* Can be a single name, or a list of names that will be used to find the structure
* field.
* field. The name is case-insensitive.
*
* @return name, or list of names, of the structure field to map, or unset to use the
* java field's name
* @return name, or list of names (case insensitive), of the structure field to map,
* or unset to use the java field's name
*/
String[] fieldName() default "";

View file

@ -21,54 +21,54 @@ import java.lang.annotation.*;
* Indicates that the tagged class corresponds to a Ghidra structure.
* <p>
* For fixed/static length structures, an existing Ghidra structure data type will be found and
* then bound to the tagged class, and it will control how instances of the tagged class
* then bound to the tagged class, and it will control how instances of the tagged class
* are deserialized. Only fields that are interesting / relevant need to be tagged with
* a {@link FieldMapping} annotation, which causes them to be pulled into the java structure.
* a {@link FieldMapping} annotation, which causes them to be pulled into the java class.
* <p>
* For {@link FieldOutput#isVariableLength() variable} length structures, a unique Ghidra
* For {@link FieldOutput#isVariableLength() variable} length structures, a unique Ghidra
* structure data type will be created for each combination of field lengths, and the tagged
* class must deserialize itself by implementing the {@link StructureReader} interface. (each
* field that needs to be mapped into the Ghidra structure must be tagged with a {@link FieldOutput}
* annotation)
* <p>
* In either case, various annotations on fields and methods will control how this structure
* In either case, various annotations on fields and methods will control how this structure
* will be marked up in the Ghidra program.
* <p>
* The tagged class must be {@link DataTypeMapper#registerStructure(Class) registered} with
* the {@link DataTypeMapper program context} to enable the suite of structure mapped classes
* the {@link DataTypeMapper program context} to enable the suite of structure mapped classes
* to work together when applied to a Ghidra binary.
* <p>
* For variable length structure classes, when the struct mapping system creates a custom-fitted
* structure to markup a specific location with its specific data, the new struct data type's name
* will be patterned as "structurename_NN_MM_...", where NN and MM and etc are the lengths of the
* variable length fields found in the structure.
* variable length fields found in the structure.
* <p>
* Structure mapped classes must have a {@link StructureContext} member variable that is tagged
* with the {@link ContextField} annotation, and probably should have a {@link DataTypeMapper}
* member variable (that corresponds to a more specific type of DataTypeMapper) that is also
* member variable (that corresponds to a more specific type of DataTypeMapper) that is also
* tagged with the ContextField annotation.
*
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StructureMapping {
/**
* Specifies the name of a Ghidra structure that the tagged class represents. For fixed
* length structures, the {@link DataTypeMapper} will search for this Ghidra data type
* in it's configured
* Specifies the name, and optionally alternate names, of a Ghidra structure that the tagged
* class represents. For fixed length structures, the {@link DataTypeMapper} will search
* for this Ghidra data type in it's configured
* {@link DataTypeMapper#addArchiveSearchCategoryPath(ghidra.program.model.data.CategoryPath...) archive}
* and
* and
* {@link DataTypeMapper#addProgramSearchCategoryPath(ghidra.program.model.data.CategoryPath...) program}
* search paths.
*
* @return name of a Ghidra structure data type
*
* @return name(s) of a Ghidra structure data type
*/
String[] structureName();
/**
* Optional reference to a 'function' (implemented via a class) that will be called to do
* Optional reference to a 'function' (implemented via a class) that will be called to do
* custom markup.
*
*
* @return {@link StructureMarkupFunction} class
*/
@SuppressWarnings("rawtypes")

View file

@ -32,18 +32,6 @@ import ghidra.util.exception.DuplicateNameException;
*/
public class StructureMappingInfo<T> {
/**
* Returns the name of the structure data type that will define the binary layout
* of the mapped fields in the target class.
*
* @param targetClass structure mapped class
* @return the structure name
*/
public static String[] getStructureDataTypeNameForClass(Class<?> targetClass) {
StructureMapping sma = targetClass.getAnnotation(StructureMapping.class);
return sma != null ? sma.structureName() : null;
}
/**
* Returns the mapping info for a class, using annotations found in that class.
*
@ -68,8 +56,9 @@ public class StructureMappingInfo<T> {
private final Class<T> targetClass;
private final ObjectInstanceCreator<T> instanceCreator;
private final String[] structureName;
private final String structureName;
private final Structure structureDataType; // null if variable length fields
private final Map<String, DataTypeComponent> fieldNameLookup; // case insensitive lookup
private final List<FieldMappingInfo<T>> fields = new ArrayList<>();
private final List<FieldOutputInfo<T>> outputFields = new ArrayList<>();
@ -84,8 +73,9 @@ public class StructureMappingInfo<T> {
this.targetClass = targetClass;
this.structureDataType = structDataType;
this.structureName = structureDataType != null
? new String[]{structureDataType.getName()}
: sma.structureName();
? structureDataType.getName()
: sma.structureName()[0];
this.fieldNameLookup = indexStructFields(structDataType);
this.useFieldMappingInfo = !StructureReader.class.isAssignableFrom(targetClass);
this.instanceCreator = findInstanceCreator();
@ -109,7 +99,7 @@ public class StructureMappingInfo<T> {
}
public String getDescription() {
return "%s-%s".formatted(targetClass.getSimpleName(), structureName[0]);
return "%s-%s".formatted(targetClass.getSimpleName(), structureName);
}
public Structure getStructureDataType() {
@ -117,7 +107,7 @@ public class StructureMappingInfo<T> {
}
public String getStructureName() {
return structureName[0];
return structureName;
}
public int getStructureLength() {
@ -187,7 +177,7 @@ public class StructureMappingInfo<T> {
Structure newStruct = new StructureDataType(
context.getDataTypeMapper().getDefaultVariableLengthStructCategoryPath(),
structureName[0],
structureName,
0,
context.getDataTypeMapper().getDTM());
@ -204,7 +194,7 @@ public class StructureMappingInfo<T> {
}
if (!nameSuffix.isEmpty()) {
try {
newStruct.setName(structureName[0] + nameSuffix);
newStruct.setName(structureName + nameSuffix);
}
catch (InvalidNameException | DuplicateNameException e) {
throw new IOException(e);
@ -259,18 +249,6 @@ public class StructureMappingInfo<T> {
}
}
private DataTypeComponent getField(String name) {
if (!useFieldMappingInfo || name == null || name.isBlank()) {
return null;
}
for (DataTypeComponent dtc : structureDataType.getDefinedComponents()) {
if (name.equalsIgnoreCase(dtc.getFieldName())) {
return dtc;
}
}
return null;
}
private void readFieldInfo(Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
@ -316,8 +294,9 @@ public class StructureMappingInfo<T> {
if (fma.optional()) {
return null;
}
throw new IllegalArgumentException("Missing structure field: %s in %s"
.formatted(Arrays.toString(fieldNames), targetClass.getSimpleName()));
throw new IllegalArgumentException(
"Missing structure field: %s.%s for %s.%s".formatted(structureName,
Arrays.toString(fieldNames), targetClass.getSimpleName(), field.getName()));
}
Signedness signedness = fma != null ? fma.signedness() : Signedness.Unspecified;
@ -346,7 +325,7 @@ public class StructureMappingInfo<T> {
private DataTypeComponent getFirstMatchingField(String[] fieldNames) {
for (String fieldName : fieldNames) {
DataTypeComponent dtc = getField(fieldName);
DataTypeComponent dtc = fieldNameLookup.get(fieldName.toLowerCase());
if (dtc != null) {
return dtc;
}
@ -400,6 +379,20 @@ public class StructureMappingInfo<T> {
return struct.isZeroLength() ? 0 : struct.getLength();
}
private static Map<String, DataTypeComponent> indexStructFields(Structure struct) {
if (struct == null) {
return Map.of();
}
Map<String, DataTypeComponent> result = new HashMap<>();
for (DataTypeComponent dtc : struct.getDefinedComponents()) {
String fieldName = dtc.getFieldName();
if (fieldName != null) {
result.put(fieldName.toLowerCase(), dtc);
}
}
return result;
}
//---------------------------------------------------------------------------------------------
interface ReadFromStructureFunction<T> {
T readStructure(StructureContext<T> context) throws IOException;

View file

@ -39,17 +39,18 @@ public class DataTypeArchiveIDTest extends AbstractGenericTest {
private static final String MAC_OS_10_9_GDT_PATH = "typeinfo/mac_10.9/mac_osx.gdt";
//@formatter:off
private static final Map<String, String> archiveIdMap = Map.of(
WIN_VS12_32_GDT_PATH, "2644092282468053077",
WIN_VS12_64_GDT_PATH, "3193696833254024484",
GENERIC_CLIB_32_GDT_PATH, "2644097909188870631",
GENERIC_CLIB_64_GDT_PATH, "3193699959493190971",
MAC_OS_10_9_GDT_PATH, "2650667045259492112",
"typeinfo/golang/golang_1.17_anybit_any.gdt", "3533627828569507753",
"typeinfo/golang/golang_1.18_anybit_any.gdt", "3528902399865061936",
"typeinfo/golang/golang_1.19_anybit_any.gdt", "3533812166493410774",
"typeinfo/golang/golang_1.20_anybit_any.gdt", "3533817003441909616",
"typeinfo/rust/rust-common.gdt","3557867258392862055");
private static final Map<String, String> archiveIdMap = Map.ofEntries(
Map.entry(WIN_VS12_32_GDT_PATH, "2644092282468053077"),
Map.entry(WIN_VS12_64_GDT_PATH, "3193696833254024484"),
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.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"),
Map.entry("typeinfo/golang/golang_1.20_anybit_any.gdt", "3533817003441909616"),
Map.entry("typeinfo/golang/golang_1.21_anybit_any.gdt", "3574190573109087960"),
Map.entry("typeinfo/rust/rust-common.gdt", "3557867258392862055"));
//@formatter:on
private Map<ResourceFile, String> getCurrentGdts() {

View file

@ -1,6 +1,6 @@
<golang>
<!-- see https://github.com/golang/go/blob/master/src/internal/abi/abi_amd64.go -->
<register_info versions="V1_17,V1_18,V1_19,V1_20"> <!-- "all", or comma list of: V1_2,V1_16,V1_17,V1_18 -->
<register_info versions="V1_17,V1_18,V1_19,V1_20,V1_21"> <!-- "all", or comma list of: V1_2,V1_16,etc -->
<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"/>