Merge remote-tracking branch 'origin/GP-3440_dev747368_golang_cleanup--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-05-23 11:28:31 -04:00
commit 82f7a0a53d
43 changed files with 1412 additions and 584 deletions

View File

@ -28,6 +28,7 @@ import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ItemWithAddress;
import ghidra.app.util.bin.format.golang.*;
import ghidra.app.util.bin.format.golang.rtti.GoModuledata;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.MarkupSession;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
@ -80,20 +81,22 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
programContext.discoverGoTypes(monitor);
GoModuledata firstModule = programContext.getFirstModule();
programContext.labelStructure(firstModule, "firstmoduledata");
UnknownProgressWrappingTaskMonitor upwtm =
new UnknownProgressWrappingTaskMonitor(monitor, 100);
programContext.setMarkupTaskMonitor(upwtm);
upwtm.initialize(0);
upwtm.setMessage("Marking up Golang RTTI structures");
programContext.markup(firstModule, false);
programContext.setMarkupTaskMonitor(null);
MarkupSession markupSession = programContext.createMarkupSession(upwtm);
markupSession.labelStructure(firstModule, "firstmoduledata");
markupSession.markup(firstModule, false);
markupMiscInfoStructs(program);
markupWellknownSymbols(programContext);
markupWellknownSymbols(programContext, markupSession);
fixupNoReturnFuncs(program);
setupProgramContext(programContext);
setupProgramContext(programContext, markupSession);
programContext.recoverDataTypes(monitor);
if (analyzerOptions.createBootstrapDatatypeArchive) {
@ -121,19 +124,20 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
analyzerOptions.createBootstrapDatatypeArchive);
}
private void markupWellknownSymbols(GoRttiMapper programContext) throws IOException {
private void markupWellknownSymbols(GoRttiMapper programContext, MarkupSession session)
throws IOException {
Program program = programContext.getProgram();
Symbol g0 = SymbolUtilities.getUniqueSymbol(program, "runtime.g0");
Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class);
if (g0 != null && gStruct != null) {
programContext.markupAddressIfUndefined(g0.getAddress(), gStruct);
session.markupAddressIfUndefined(g0.getAddress(), gStruct);
}
Symbol m0 = SymbolUtilities.getUniqueSymbol(program, "runtime.m0");
Structure mStruct = programContext.getGhidraDataType("runtime.m", Structure.class);
if (m0 != null && mStruct != null) {
programContext.markupAddressIfUndefined(m0.getAddress(), mStruct);
session.markupAddressIfUndefined(m0.getAddress(), mStruct);
}
}
@ -225,7 +229,8 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
return newMB.getStart();
}
private void setupProgramContext(GoRttiMapper programContext) throws IOException {
private void setupProgramContext(GoRttiMapper programContext, MarkupSession session)
throws IOException {
Program program = programContext.getProgram();
GoRegisterInfo goRegInfo = GoRegisterInfoManager.getInstance()
.getRegisterInfoForLang(program.getLanguage(),
@ -269,14 +274,14 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
: null;
if (zerobase == null) {
programContext.labelAddress(contextMemoryAddr.add(zerobaseSymbol),
session.labelAddress(contextMemoryAddr.add(zerobaseSymbol),
ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME);
}
if (gStruct != null) {
Address gAddr = contextMemoryAddr.add(gStructOffset);
programContext.markupAddressIfUndefined(gAddr, gStruct);
programContext.labelAddress(gAddr, "CURRENT_G");
session.markupAddressIfUndefined(gAddr, gStruct);
session.labelAddress(gAddr, "CURRENT_G");
Register currentGoroutineReg = goRegInfo.getCurrentGoroutineRegister();
if (currentGoroutineReg != null && txtMemblock != null) {
@ -295,7 +300,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
}
if (mStruct != null) {
Address mAddr = contextMemoryAddr.add(mStructOffset);
programContext.markupAddressIfUndefined(mAddr, mStruct);
session.markupAddressIfUndefined(mAddr, mStruct);
}
}

View File

@ -18,11 +18,10 @@ package ghidra.app.util.bin.format.golang;
import static ghidra.app.util.bin.StructConverter.ASCII;
import static ghidra.app.util.bin.StructConverter.BYTE;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.MemoryByteProvider;
@ -63,13 +62,19 @@ public class GoBuildInfo implements ElfInfoItem {
* Reads a GoBuildInfo ".go.buildinfo" section from the specified Program, if present.
*
* @param program {@link Program} that contains the ".go.buildinfo" section
* @return new {@link GoBuildInfo} section, if present, null if missing or error
* @return new {@link GoBuildInfo} instance, if present, null if missing or error
*/
public static GoBuildInfo fromProgram(Program program) {
ItemWithAddress<GoBuildInfo> wrappedItem = findBuildInfo(program);
return wrappedItem != null ? wrappedItem.item() : null;
}
/**
* Searches for the GoBuildInfo structure in the most common and easy locations.
*
* @param program {@link Program} to search
* @return new {@link GoBuildInfo} instance, if present, null if missing or error
*/
public static ItemWithAddress<GoBuildInfo> findBuildInfo(Program program) {
// try as if binary is ELF
ItemWithAddress<GoBuildInfo> wrappedItem =
@ -135,7 +140,7 @@ public class GoBuildInfo implements ElfInfoItem {
private final List<GoModuleInfo> dependencies;
private final List<GoBuildSettings> buildSettings; // compile/linker flags used during build process
public GoBuildInfo(int pointerSize, Endian endian, String version, String path,
private GoBuildInfo(int pointerSize, Endian endian, String version, String path,
GoModuleInfo moduleInfo, List<GoModuleInfo> dependencies,
List<GoBuildSettings> buildSettings) {
this.pointerSize = pointerSize;

View File

@ -19,8 +19,19 @@ import java.io.IOException;
/**
* Key=value element of Golang Build settings
*
* @param key string name of property
* @param value string value of property
*/
public record GoBuildSettings(String key, String value) {
/**
* Parses a "key=value" string and returns the parts as a {@link GoBuildSettings}.
*
* @param s "key=value" string
* @return new {@link GoBuildSettings} instance
* @throws IOException if error splitting the string into key and value
*/
public static GoBuildSettings fromString(String s) throws IOException {
String[] parts = s.split("=", 2);
if (parts.length != 2) {

View File

@ -17,6 +17,12 @@ package ghidra.app.util.bin.format.golang;
import ghidra.program.model.data.CategoryPath;
/**
* Misc constant values for golang
*/
public class GoConstants {
/**
* Category path to place golang types in
*/
public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang");
}

View File

@ -40,9 +40,9 @@ public class GoFunctionFixup {
* Assigns custom storage for a function's parameters, using the function's current
* parameter list (formal info only) as starting information.
*
* @param func
* @throws DuplicateNameException
* @throws InvalidInputException
* @param func Ghidra {@link Function} to fix
* @throws DuplicateNameException if invalid parameter names
* @throws InvalidInputException if invalid data types or storage
*/
public static void fixupFunction(Function func)
throws DuplicateNameException, InvalidInputException {
@ -51,6 +51,15 @@ public class GoFunctionFixup {
fixupFunction(func, goVersion);
}
/**
* Assigns custom storage for a function's parameters, using the function's current
* parameter list (formal info only) as starting information.
*
* @param func Ghidra {@link Function} to fix
* @param goVersion {@link GoVer} enum
* @throws DuplicateNameException if invalid parameter names
* @throws InvalidInputException if invalid data types or storage
*/
public static void fixupFunction(Function func, GoVer goVersion)
throws DuplicateNameException, InvalidInputException {
Program program = func.getProgram();
@ -150,8 +159,8 @@ public class GoFunctionFixup {
* Returns a Ghidra data type that represents a zero-length array, to be used as a replacement
* for a zero-length array parameter.
*
* @param dt
* @return
* @param dt data type that will donate its name to the created empty array type
* @return {@link DataType} that represents a specific zero-length array type
*/
public static DataType makeEmptyArrayDataType(DataType dt) {
StructureDataType struct = new StructureDataType(dt.getCategoryPath(),
@ -303,7 +312,7 @@ public class GoFunctionFixup {
* <p>
* Only valid for storage scheme that has all register storages listed first / contiguous.
*
* @param varnodes
* @param varnodes list of {@link Varnode varnodes} that will be modified in-place
*/
public static void reverseNonStackStorageLocations(List<Varnode> varnodes) {
int regStorageCount;

View File

@ -15,12 +15,16 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.*;
import java.io.IOException;
import java.util.*;
/**
* Represents information about a single golang module dependency.
*
* @param path module path
* @param version module version
* @param sum checksum
* @param replace replacement module info (may be null)
*/
public record GoModuleInfo(String path, String version, String sum, GoModuleInfo replace) {
@ -28,7 +32,8 @@ public record GoModuleInfo(String path, String version, String sum, GoModuleInfo
* Parses a GoModuleInfo from a formatted string "path[tab]version[tab]checksum".
*
* @param s string to parse
* @param replace GoModuleInfo that is the replacement for this module
* @param replace GoModuleInfo that is the replacement for this module, or null if no
* replacement specified
* @return new GoModuleInfo instance, never null
* @throws IOException if error parsing string
*/

View File

@ -15,10 +15,9 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
@ -66,7 +65,7 @@ public class GoRegisterInfoManager {
* returned that forces all parameters to be stack allocated.
*
* @param lang {@link Language}
* @param goVersion
* @param goVersion {@link GoVer} enum
* @return {@link GoRegisterInfo}, never null
*/
public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVersion) {

View File

@ -53,6 +53,13 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
public static final CategoryPath GOLANG_API_EXPORT =
new CategoryPath(CategoryPath.ROOT, "GolangAPIExport");
/**
* Returns true if the specified {@link DWARFFunction} wrapper refers to a function in a golang
* compile unit.
*
* @param dfunc {@link DWARFFunction}
* @return boolean true or false
*/
public static boolean isGolangFunction(DWARFFunction dfunc) {
DIEAggregate diea = dfunc.diea;
int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage();

View File

@ -19,7 +19,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem;
import ghidra.app.util.bin.format.elf.info.ElfNote;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
@ -32,7 +31,7 @@ public class NoteGoBuildId extends ElfNote {
/**
* Reads a NoteGoBuildId from the specified BinaryReader, matching the signature of
* {@link ElfInfoItem.ReaderFunc}.
* ElfInfoItem.ReaderFunc.
*
* @param br BinaryReader
* @param unusedProgram context (unused but needed to match signature)

View File

@ -19,16 +19,9 @@ import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.SymbolUtilities;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.InvalidInputException;
@StructureMapping(structureName = "runtime._func")
public class GoFuncData implements StructureMarkup<GoFuncData> {
@ -84,33 +77,10 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
}
@Override
public void additionalMarkup() throws IOException {
public void additionalMarkup(MarkupSession session) throws IOException {
Address addr = getFuncAddress();
String name = SymbolUtilities.replaceInvalidChars(getName(), true);
Program program = programContext.getProgram();
Function function = program.getListing().getFunctionAt(addr);
if (function == null) {
try {
if (!program.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(addr)) {
Msg.warn(this,
"Unable to create function not contained within loaded memory: %s@%s"
.formatted(name, addr));
return;
}
function = program.getFunctionManager()
.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED);
}
catch (OverlappingFunctionException | InvalidInputException e) {
Msg.error(this, e);
}
}
else {
// TODO: this does nothing. re-evalulate this logic
programContext.labelAddress(addr, name);
}
session.createFunctionIfMissing(name, addr);
}
}

View File

@ -15,12 +15,11 @@
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.types.GoInterfaceType;
import ghidra.app.util.bin.format.golang.rtti.types.GoType;
import ghidra.app.util.bin.format.golang.structmapping.*;
@ -80,7 +79,7 @@ public class GoItab implements StructureMarkup<GoItab> {
}
@Override
public void additionalMarkup() throws IOException {
public void additionalMarkup(MarkupSession session) throws IOException {
GoSlice funSlice = getFunSlice();
List<Address> funcAddrs = Arrays.stream(funSlice.readUIntList(programContext.getPtrSize()))
.mapToObj(offset -> programContext.getCodeAddress(offset))
@ -88,12 +87,12 @@ public class GoItab implements StructureMarkup<GoItab> {
// this adds references from the elements of the artificial slice. However, the reference
// from element[0] of the real "fun" array won't show anything in the UI even though
// there is a outbound reference there.
funSlice.markupElementReferences(programContext.getPtrSize(), funcAddrs);
funSlice.markupElementReferences(programContext.getPtrSize(), funcAddrs, session);
GoSlice extraFunSlice =
funSlice.getSubSlice(1, funSlice.getLen() - 1, programContext.getPtrSize());
extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", (DataType) null,
true);
true, session);
}
@Override

View File

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.rtti.types.GoType;
import ghidra.app.util.bin.format.golang.structmapping.*;
@ -87,8 +86,16 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab)
public GoModuledata() {
// empty
}
/**
* Compares the data in this structure to fields in a GoPcHeader and returns true if they
* match.
*
* @param pclntab GoPcHeader instance
* @return boolean true if match, false if no match
*/
public boolean matchesPclntab(GoPcHeader pclntab) {
return pclntab.getTextStart().equals(getText()) &&
pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress());
@ -150,15 +157,15 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
}
@Override
public void additionalMarkup() throws IOException {
typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false);
typeLinks.markupElementReferences(4, getTypeList());
public void additionalMarkup(MarkupSession session) throws IOException {
typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false, session);
typeLinks.markupElementReferences(4, getTypeList(), session);
itablinks.markupArray("moduledata.itablinks", GoItab.class, true);
itablinks.markupArray("moduledata.itablinks", GoItab.class, true, session);
//cutab.markupArray("moduledata.cutab", dataTypeMapper.getUint32DT(), false);
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen());
markupStringTable(filetab.getArrayAddress(), filetab.getLen());
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
if (ftab.getLen() > 0) {
// chop off the last entry as it is not a full entry (it just points to the address
@ -166,8 +173,8 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
int entryLen =
programContext.getStructureMappingInfo(GoFunctabEntry.class).getStructureLength();
GoSlice subSlice = ftab.getSubSlice(0, ftab.getLen() - 1, entryLen);
subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false);
subSlice.markupArrayElements(GoFunctabEntry.class);
subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false, session);
subSlice.markupArrayElements(GoFunctabEntry.class, session);
}
}
@ -184,7 +191,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return result;
}
private void markupStringTable(Address addr, long stringTableLength) {
private void markupStringTable(Address addr, long stringTableLength, MarkupSession session) {
DataType stringDT = StringUTF8DataType.dataType;
long startOfString = addr.getOffset();
long endOfStringTable = startOfString + stringTableLength;
@ -195,7 +202,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
long len = reader.getPointerIndex() - startOfString;
if (len > 0 && len < Integer.MAX_VALUE) {
Address stringAddr = addr.getNewAddress(startOfString);
programContext.markupAddress(stringAddr, stringDT, (int) len);
session.markupAddress(stringAddr, stringDT, (int) len);
}
startOfString = reader.getPointerIndex();
}
@ -235,7 +242,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
*
* @param context already initialized {@link GoRttiMapper}
* @return new GoModuledata instance, or null if not found
* @throws IOException
* @throws IOException if error reading found structure
*/
/* package */ static GoModuledata getFirstModuledata(GoRttiMapper context)
throws IOException {
@ -249,7 +256,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
}
/**
* Searches memory for a likely GoModuledata
* Searches memory for a likely GoModuledata structure.
*
* @param context already initialized {@link GoRttiMapper}
* @param pclntabAddress address of an already found {@link GoPcHeader}
@ -257,7 +264,7 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
* @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
* @throws IOException if error reading found structure
*/
/* package */ static GoModuledata findFirstModule(GoRttiMapper context,
Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor)
@ -269,6 +276,8 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
Program program = context.getProgram();
Memory memory = program.getMemory();
// Search memory for a pointer to the pclntab struct. The result should be the first
// field of the GoModuledata structure.
int ptrSize = context.getPtrSize();
byte[] searchBytes = new byte[ptrSize];
context.getDataConverter().putValue(pclntabAddress.getOffset(), ptrSize, searchBytes, 0);
@ -279,6 +288,9 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
}
GoModuledata moduleData = context.readStructure(GoModuledata.class, moduleAddr);
// Verify that we read a good GoModuledata struct by comparing some of its values to
// the pclntab structure.
return moduleData.matchesPclntab(pclntab) ? moduleData : null;
}
}

View File

@ -89,7 +89,7 @@ public class GoPcHeader {
* @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
* @throws IOException
* @throws IOException if error reading
*/
public static Address findPclntabAddress(GoRttiMapper programContext, AddressRange range,
TaskMonitor monitor) throws IOException {
@ -126,7 +126,7 @@ public class GoPcHeader {
*
* @param provider {@link ByteProvider}
* @return boolean true if the byte provider has the magic signature of a pclntab
* @throws IOException
* @throws IOException if error reading
*/
public static boolean isPclntab(ByteProvider provider) throws IOException {
byte[] header = provider.readBytes(0, 8);

View File

@ -77,9 +77,9 @@ public class GoRttiMapper extends DataTypeMapper {
* is not a supported golang binary.
*
* @param program {@link Program}
* @param log
* @param log {@link MessageLog}
* @return new {@link GoRttiMapper}, or null if not a golang binary
* @throws IOException
* @throws IOException if bootstrap gdt is corrupted or some other struct mapping logic error
*/
public static GoRttiMapper getMapperFor(Program program, MessageLog log) throws IOException {
GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program);
@ -104,6 +104,15 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
/**
* Returns the name of the golang bootstrap gdt data type archive, using the specified
* version, pointer size and OS name.
*
* @param goVer {@link GoVer}
* @param pointerSizeInBytes pointer size for this binary, or -1 to use wildcard "any"
* @param osName name of the operating system, or "any"
* @return String, "golang_1.18_64bit_any.gdt"
*/
public static String getGDTFilename(GoVer goVer, int pointerSizeInBytes, String osName) {
String bitSize = pointerSizeInBytes > 0
? Integer.toString(pointerSizeInBytes * 8)
@ -114,6 +123,12 @@ public class GoRttiMapper extends DataTypeMapper {
return gdtFilename;
}
/**
* Returns a golang OS string based on the Ghidra program.
*
* @param program {@link Program}
* @return String golang OS string such as "linux", "win"
*/
public static String getGolangOSString(Program program) {
String loaderName = program.getExecutableFormat();
if (ElfLoader.ELF_NAME.equals(loaderName)) {
@ -137,7 +152,7 @@ public class GoRttiMapper extends DataTypeMapper {
* @param goVer version of Go
* @param ptrSize size of pointers
* @param osName name of OS
* @return
* @return ResourceFile of matching bootstrap gdt, or null if nothing matches
*/
public static ResourceFile findGolangBootstrapGDT(GoVer goVer, int ptrSize, String osName) {
ResourceFile result = null;
@ -185,8 +200,22 @@ public class GoRttiMapper extends DataTypeMapper {
private GoType mapGoType;
private GoType chanGoType;
/**
* Creates a GoRttiMapper using the specified bootstrap information.
*
* @param program {@link Program} containing the go binary
* @param ptrSize size of pointers
* @param endian {@link Endian}
* @param goVersion version of go
* @param archiveGDT path to the matching golang bootstrap gdt data type file, or null
* 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
* type of golang binary
*/
public GoRttiMapper(Program program, int ptrSize, Endian endian, GoVer goVersion,
ResourceFile archiveGDT) throws IOException {
ResourceFile archiveGDT) throws IOException, IllegalArgumentException {
super(program, archiveGDT);
this.goVersion = goVersion;
@ -222,18 +251,41 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
/**
* Returns the golang version
* @return {@link GoVer}
*/
public GoVer getGolangVersion() {
return goVersion;
}
/**
* Returns the first module data instance
*
* @return {@link GoModuledata}
*/
public GoModuledata getFirstModule() {
return modules.get(0);
}
/**
* Adds a module data instance to the context
*
* @param module {@link GoModuledata} to add
*/
public void addModule(GoModuledata module) {
modules.add(module);
}
/**
* Finds the {@link GoModuledata} that contains the specified offset.
* <p>
* Useful for finding the {@link GoModuledata} to resolve a relative offset of the text,
* types or other area.
*
* @param offset absolute offset of a structure that a {@link GoModuledata} contains
* @return {@link GoModuledata} instance that contains the structure, or null if not found
*/
public GoModuledata findContainingModule(long offset) {
for (GoModuledata module : modules) {
if (module.getTypesOffset() <= offset && offset < module.getTypesEndOffset()) {
@ -243,6 +295,13 @@ public class GoRttiMapper extends DataTypeMapper {
return null;
}
/**
* Finds the {@link GoModuledata} that contains the specified func data offset.
*
* @param offset absolute offset of a func data structure
* @return {@link GoModuledata} instance that contains the specified func data, or null if not
* found
*/
public GoModuledata findContainingModuleByFuncData(long offset) {
for (GoModuledata module : modules) {
if (module.containsFuncDataInstance(offset)) {
@ -257,26 +316,56 @@ public class GoRttiMapper extends DataTypeMapper {
return VARLEN_STRUCTS_CP;
}
/**
* Returns the data type that represents a golang uintptr
*
* @return golang uinptr data type
*/
public DataType getUintptrDT() {
return uintptrDT;
}
/**
* Returns the data type that represents a golang int32
*
* @return golang int32 data type
*/
public DataType getInt32DT() {
return int32DT;
}
/**
* Returns the data type that represents a golang uint32
*
* @return golang uint32 data type
*/
public DataType getUint32DT() {
return uint32DT;
}
/**
* Returns the data type that represents a generic golang slice.
*
* @return golang generic slice data type
*/
public Structure getGenericSliceDT() {
return getStructureDataType(GoSlice.class);
}
/**
* Returns the ghidra data type that represents a golang built-in map type.
*
* @return golang map data type
*/
public GoType getMapGoType() {
return mapGoType;
}
/**
* Returns the ghidra data type that represents the built-in golang channel type.
*
* @return golang channel type
*/
public GoType getChanGoType() {
return chanGoType;
}
@ -286,31 +375,22 @@ public class GoRttiMapper extends DataTypeMapper {
return reader.clone();
}
/**
* Returns the size of pointers in this binary.
*
* @return pointer size (ex. 4, or 8)
*/
public int getPtrSize() {
return ptrSize;
}
public GoName resolveNameOff(long ptrInModule, long off) throws IOException {
if (off == 0) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
long nameStart = module.getTypesOffset() + off;
return getGoName(nameStart);
}
public GoName getGoName(long offset) throws IOException {
return offset != 0 ? readStructure(GoName.class, offset) : null;
}
public GoType resolveTypeOff(long ptrInModule, long off) throws IOException {
if (off == 0 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG || off == -1) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
return getGoType(module.getTypesOffset() + off);
}
/**
* Returns a specialized {@link GoType} for the type that is located at the specified location.
*
* @param offset absolute position of a go type
* @return specialized {@link GoType} (example, GoStructType, GoArrayType, etc)
* @throws IOException if error reading
*/
public GoType getGoType(long offset) throws IOException {
if (offset == 0) {
return null;
@ -324,14 +404,36 @@ public class GoRttiMapper extends DataTypeMapper {
return goType;
}
/**
* Returns a specialized {@link GoType} for the type that is located at the specified location.
*
* @param addr location of a go type
* @return specialized {@link GoType} (example, GoStructType, GoArrayType, etc)
* @throws IOException if error reading
*/
public GoType getGoType(Address addr) throws IOException {
return getGoType(addr.getOffset());
}
/**
* Finds a go type by its go-type name, from the list of
* {@link #discoverGoTypes(TaskMonitor) discovered} go types.
*
* @param typeName name string
* @return {@link GoType}, or null if not found
*/
public GoType findGoType(String typeName) {
return typeNameIndex.get(typeName);
}
/**
* Returns the Ghidra {@link DataType} that is equivalent to the named golang type.
*
* @param <T> expected DataType
* @param goTypeName golang type name
* @param clazz class of expected data type
* @return {@link DataType} representing the named golang type, or null if not found
*/
public <T extends DataType> T getGhidraDataType(String goTypeName, Class<T> clazz) {
T dt = getType(goTypeName, clazz);
if (dt == null) {
@ -353,22 +455,16 @@ public class GoRttiMapper extends DataTypeMapper {
return dt;
}
public Address resolveTextOff(long ptrInModule, long off) {
if (off == -1 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
return module != null ? module.getText().add(off) : null;
}
/**
* Export the currently registered struct mapping types to a gdt file.
* Export the currently registered struct mapping types to a gdt file, producing a bootstrap
* GDT archive.
* <p>
* The struct data types will either be from the current program's DWARF data, or
* from an earlier golang.gdt (if this binary doesn't have DWARF)
*
* @param gdtFile
* @throws IOException
* @param gdtFile destination {@link File} to write the bootstrap types to
* @param monitor {@link TaskMonitor}
* @throws IOException if error
*/
public void exportTypesToGDT(File gdtFile, TaskMonitor monitor) throws IOException {
@ -463,12 +559,29 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
/**
* Returns category path that should be used to place recovered golang types.
*
* @return {@link CategoryPath} to use when creating recovered golang types
*/
public CategoryPath getRecoveredTypesCp() {
return RECOVERED_TYPES_CP;
}
/**
* Returns a {@link DataType Ghidra data type} that represents the {@link GoType golang type},
* using a cache of already recovered types to eliminate extra work and self recursion.
*
* @param typ the {@link GoType} to convert
* @return Ghidra {@link DataType}
* @throws IOException if error converting type
*/
public DataType getRecoveredType(GoType typ) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
Address typeStructAddr = getAddressOfStructure(typ);
if (typeStructAddr == null) {
throw new IOException("Unable to get address of a struct mapped instance");
}
long offset = typeStructAddr.getOffset();
DataType dt = cachedRecoveredDataTypes.get(offset);
if (dt != null) {
return dt;
@ -478,13 +591,40 @@ public class GoRttiMapper extends DataTypeMapper {
return dt;
}
/**
* Inserts a mapping between a {@link GoType golang type} and a
* {@link DataType ghidra data type}.
* <p>
* Useful to prepopulate the data type mapping before recursing into contained/referenced types
* that might be self-referencing.
*
* @param typ {@link GoType golang type}
* @param dt {@link DataType Ghidra type}
* @throws IOException if golang type struct is not a valid struct mapped instance
*/
public void cacheRecoveredDataType(GoType typ, DataType dt) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
Address typeStructAddr = getAddressOfStructure(typ);
if (typeStructAddr == null) {
throw new IOException("Unable to get address of a struct mapped instance");
}
long offset = typeStructAddr.getOffset();
cachedRecoveredDataTypes.put(offset, dt);
}
/**
* Returns a {@link DataType Ghidra data type} that represents the {@link GoType golang type},
* using a cache of already recovered types to eliminate extra work and self recursion.
*
* @param typ the {@link GoType} to convert
* @return Ghidra {@link DataType}
* @throws IOException if golang type struct is not a valid struct mapped instance
*/
public DataType getCachedRecoveredDataType(GoType typ) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
Address typeStructAddr = getAddressOfStructure(typ);
if (typeStructAddr == null) {
throw new IOException("Unable to get address of a struct mapped instance");
}
long offset = typeStructAddr.getOffset();
return cachedRecoveredDataTypes.get(offset);
}
@ -492,8 +632,9 @@ public class GoRttiMapper extends DataTypeMapper {
* Converts all discovered golang rtti type records to Ghidra data types, placing them
* in the program's DTM in /golang-recovered
*
* @throws IOException
* @throws CancelledException
* @param monitor {@link TaskMonitor}
* @throws IOException error converting a golang type to a Ghidra type
* @throws CancelledException if the user cancelled the import
*/
public void recoverDataTypes(TaskMonitor monitor) throws IOException, CancelledException {
monitor.setMessage("Converting Golang types to Ghidra data types");
@ -510,6 +651,16 @@ public class GoRttiMapper extends DataTypeMapper {
}
}
/**
* Iterates over all golang rtti types listed in the GoModuledata struct, and recurses into
* each type to discover any types they reference.
* <p>
* The found types are accumulated in {@link #goTypes}.
*
* @param monitor {@link TaskMonitor}
* @throws IOException if error
* @throws CancelledException if cancelled
*/
public void discoverGoTypes(TaskMonitor monitor) throws IOException, CancelledException {
GoModuledata firstModule = findFirstModuledata(monitor);
if (firstModule == null) {
@ -533,13 +684,88 @@ public class GoRttiMapper extends DataTypeMapper {
}
typeNameIndex.clear();
for (GoType goType : goTypes.values()) {
String typeName = goType.getBaseType().getNameString();
String typeName = goType.getNameString();
typeNameIndex.put(typeName, goType);
}
Msg.info(this, "Found %d golang types".formatted(goTypes.size()));
initHiddenCompilerTypes();
}
/**
* Returns the {@link GoType} corresponding to an offset that is relative to the controlling
* GoModuledata's typesOffset.
*
* @param ptrInModule the address of the structure that contains the offset that needs to be
* calculated. The containing-structure's address is important because it indicates which
* GoModuledata is the 'parent'
* @param off offset
* @return {@link GoType}, or null if offset is special value 0 or -1
* @throws IOException if error
*/
public GoType resolveTypeOff(long ptrInModule, long off) throws IOException {
if (off == 0 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG || off == -1) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
return getGoType(module.getTypesOffset() + off);
}
/**
* Returns the {@link Address} to an offset that is relative to the controlling
* GoModuledata's text value.
*
* @param ptrInModule the address of the structure that contains the offset that needs to be
* calculated. The containing-structure's address is important because it indicates which
* GoModuledata is the 'parent'
* @param off offset
* @return {@link Address}, or null if offset was special value -1
*/
public Address resolveTextOff(long ptrInModule, long off) {
if (off == -1 || off == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
return module != null ? module.getText().add(off) : null;
}
/**
* Returns the {@link GoName} corresponding to an offset that is relative to the controlling
* GoModuledata's typesOffset.
* <p>
*
* @param ptrInModule the address of the structure that contains the offset that needs to be
* calculated. The containing-structure's address is important because it indicates which
* GoModuledata is the 'parent'
* @param off offset
* @return {@link GoName}, or null if offset was special value 0
* @throws IOException if error reading name or unable to find containing module
*/
public GoName resolveNameOff(long ptrInModule, long off) throws IOException {
if (off == 0) {
return null;
}
GoModuledata module = findContainingModule(ptrInModule);
if (module == null) {
throw new IOException(
"Unable to find containing module for structure at 0x%x".formatted(ptrInModule));
}
long nameStart = module.getTypesOffset() + off;
return getGoName(nameStart);
}
/**
* Returns the {@link GoName} instance at the specified offset.
*
* @param offset location to read
* @return {@link GoName} instance, or null if offset was special value 0
* @throws IOException if error reading
*/
public GoName getGoName(long offset) throws IOException {
return offset != 0 ? readStructure(GoName.class, offset) : null;
}
//--------------------------------------------------------------------------------------------
private void initHiddenCompilerTypes() {
// these structure types are what golang map and chan types actually point to.
mapGoType = findGoType("runtime.hmap");
@ -574,9 +800,9 @@ public class GoRttiMapper extends DataTypeMapper {
private AddressRange getPclntabSearchRange() {
Memory memory = program.getMemory();
for (String blockToSearch : List.of(".noptrdata", ".rdata")) {
MemoryBlock noptrdataBlock = memory.getBlock(blockToSearch);
if (noptrdataBlock != null) {
return new AddressRangeImpl(noptrdataBlock.getStart(), noptrdataBlock.getEnd());
MemoryBlock memBlock = memory.getBlock(blockToSearch);
if (memBlock != null) {
return new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd());
}
}
return null;
@ -585,9 +811,9 @@ public class GoRttiMapper extends DataTypeMapper {
private AddressRange getModuledataSearchRange() {
Memory memory = program.getMemory();
for (String blockToSearch : List.of(".noptrdata", ".data")) {
MemoryBlock noptrdataBlock = memory.getBlock(blockToSearch);
if (noptrdataBlock != null) {
return new AddressRangeImpl(noptrdataBlock.getStart(), noptrdataBlock.getEnd());
MemoryBlock memBlock = memory.getBlock(blockToSearch);
if (memBlock != null) {
return new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd());
}
}
return null;

View File

@ -25,7 +25,6 @@ import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
@StructureMapping(structureName = "runtime.slice")
@ -61,10 +60,10 @@ public class GoSlice {
/**
* Return a artificial view of a portion of this slice's contents.
*
* @param startElement
* @param elementCount
* @param elementSize
* @return
* @param startElement index of element that will be the new sub-slice's starting element
* @param elementCount number of elements to include in new sub-slice
* @param elementSize size of an individual element
* @return new {@link GoSlice} instance that is limited to a portion of this slice
*/
public GoSlice getSubSlice(long startElement, long elementCount, long elementSize) {
return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, programContext);
@ -110,10 +109,10 @@ public class GoSlice {
* Reads the content of the slice, treating each element as an instance of the specified
* structure mapped class.
*
* @param <T>
* @param clazz element type
* @param <T> struct mapped type of element
* @param clazz element type
* @return list of instances
* @throws IOException
* @throws IOException if error reading an element
*/
public <T> List<T> readList(Class<T> clazz) throws IOException {
return readList((reader) -> programContext.readStructure(clazz, reader));
@ -123,10 +122,10 @@ public class GoSlice {
* Reads the contents of the slice, treating each element as an instance of an object that can
* be read using the supplied reading function.
*
* @param <T>
* @param <T> struct mapped type of element
* @param readFunc function that will read an instance from a BinaryReader
* @return list of instances
* @throws IOException
* @throws IOException if error reading an element
*/
public <T> List<T> readList(ReaderFunction<T> readFunc) throws IOException {
List<T> result = new ArrayList<>();
@ -171,12 +170,13 @@ public class GoSlice {
* @param elementClazz structure mapped class of the element of the array
* @param ptr boolean flag, if true the element type is really a pointer to the supplied
* data type
* @param session state and methods to assist marking up the program
* @throws IOException if error
*/
public void markupArray(String sliceName, Class<?> elementClazz, boolean ptr)
throws IOException {
public void markupArray(String sliceName, Class<?> elementClazz, boolean ptr,
MarkupSession session) throws IOException {
DataType dt = programContext.getStructureDataType(elementClazz);
markupArray(sliceName, dt, ptr);
markupArray(sliceName, dt, ptr, session);
}
/**
@ -186,10 +186,11 @@ public class GoSlice {
* @param elementType Ghidra datatype of the array elements, null ok if ptr == true
* @param ptr boolean flag, if true the element type is really a pointer to the supplied
* data type
* @param session state and methods to assist marking up the program
* @throws IOException if error
*/
public void markupArray(String sliceName, DataType elementType, boolean ptr)
throws IOException {
public void markupArray(String sliceName, DataType elementType, boolean ptr,
MarkupSession session) throws IOException {
if (len == 0) {
return;
}
@ -200,27 +201,29 @@ public class GoSlice {
ArrayDataType arrayDT = new ArrayDataType(elementType, (int) cap, -1, dtm);
Address addr = programContext.getDataAddress(array);
programContext.markupAddress(addr, arrayDT);
session.markupAddress(addr, arrayDT);
if (sliceName != null) {
programContext.labelAddress(addr, sliceName);
session.labelAddress(addr, sliceName);
}
}
/**
* Marks up each element of the array, useful when the elements are themselves structures.
*
* @param <T> structure type
* @param clazz class of the structure type
* @param <T> element type
* @param clazz structure mapped class of element
* @param session state and methods to assist marking up the program
* @return list of element instances
* @throws IOException if error reading
*/
public <T> List<T> markupArrayElements(Class<T> clazz) throws IOException {
public <T> List<T> markupArrayElements(Class<T> clazz, MarkupSession session)
throws IOException {
if (len == 0) {
return List.of();
}
List<T> elementList = readList(clazz);
programContext.markup(elementList, true);
session.markup(elementList, true);
return elementList;
}
@ -235,23 +238,14 @@ public class GoSlice {
*
* @param elementSize size of each element in the array
* @param targetAddrs list of addresses, should be same size as this slice
* @throws IOException
* @param session state and methods to assist marking up the program
* @throws IOException if error creating references
*/
public void markupElementReferences(int elementSize, List<Address> targetAddrs)
throws IOException {
public void markupElementReferences(int elementSize, List<Address> targetAddrs,
MarkupSession session) throws IOException {
if (!targetAddrs.isEmpty()) {
ReferenceManager refMgr = programContext.getProgram().getReferenceManager();
Address srcAddr = programContext.getDataAddress(array);
for (Address targetAddr : targetAddrs) {
if (targetAddr != null) {
refMgr.addMemoryReference(srcAddr, targetAddr, RefType.DATA,
SourceType.IMPORTED, 0);
}
srcAddr = srcAddr.add(elementSize);
}
session.markupArrayElementReferences(getArrayAddress(), elementSize, targetAddrs);
}
}
private static long[] readUIntList(BinaryReader reader, long index, int intSize, int count)

View File

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.GoFunctionMultiReturn;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.structmapping.*;
@ -82,9 +81,9 @@ public class GoFuncType extends GoType {
}
@Override
public void additionalMarkup() throws IOException {
public void additionalMarkup(MarkupSession session) throws IOException {
GoSlice slice = getParamListSlice();
slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true);
slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true, session);
}
public String getFuncPrototypeString(String funcName) throws IOException {
@ -103,7 +102,7 @@ public class GoFuncType extends GoType {
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getBaseType().getNameString());
sb.append(paramType.getNameString());
}
sb.append(")");
if (!outParamTypes.isEmpty()) {
@ -113,7 +112,7 @@ public class GoFuncType extends GoType {
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getBaseType().getNameString());
sb.append(paramType.getNameString());
}
sb.append(")");
}

View File

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.DataType;
@ -57,9 +56,9 @@ public class GoInterfaceType extends GoType {
}
@Override
public void additionalMarkup() throws IOException {
mhdr.markupArray(null, GoIMethod.class, false);
mhdr.markupArrayElements(GoIMethod.class);
public void additionalMarkup(MarkupSession session) throws IOException {
mhdr.markupArray(null, GoIMethod.class, false, session);
mhdr.markupArrayElements(GoIMethod.class, session);
}
@Override

View File

@ -48,6 +48,12 @@ public enum GoKind {
public static final int GC_PROG = (1 << 6);
public static final int DIRECT_IFACE = (1 << 5);
/**
* Parses the byte value read from the runtime._type kind field.
*
* @param b byte value
* @return {@link GoKind} enum, or {@link #invalid} if bad value
*/
public static GoKind parseByte(int b) {
int ordinal = b & KIND_MASK;
return Bool.ordinal() <= ordinal && ordinal <= UnsafePointer.ordinal()

View File

@ -15,14 +15,20 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
/**
* Golang type info about a specific map type.
* <p>
* See {@link GoRttiMapper#getMapGoType()} or the "runtime.hmap" type for the definition of
* a instance of a map variable in memory.
*/
@StructureMapping(structureName = "runtime.maptype")
public class GoMapType extends GoType {
@ -51,6 +57,7 @@ public class GoMapType extends GoType {
private int flags;
public GoMapType() {
// empty
}
@Markup

View File

@ -15,13 +15,19 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
/**
* Golang type information about a specific slice type.
* <p>
* See {@link GoRttiMapper#getGenericSliceDT()} or the "runtime.slice" type for the definition of
* a instance of a slice variable in memory.
*/
@StructureMapping(structureName = "runtime.slicetype")
public class GoSliceType extends GoType {

View File

@ -15,9 +15,8 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.util.*;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.app.util.bin.format.golang.rtti.GoName;
@ -26,6 +25,9 @@ import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
/**
* Golang type information about a specific structure type.
*/
@StructureMapping(structureName = "runtime.structtype")
public class GoStructType extends GoType {
@ -37,6 +39,7 @@ public class GoStructType extends GoType {
private GoSlice fields;
public GoStructType() {
// empty
}
@Markup
@ -54,10 +57,10 @@ public class GoStructType extends GoType {
}
@Override
public void additionalMarkup() throws IOException {
super.additionalMarkup();
fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false);
fields.markupArrayElements(GoStructField.class);
public void additionalMarkup(MarkupSession session) throws IOException {
super.additionalMarkup(session);
fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false, session);
fields.markupArrayElements(GoStructField.class, session);
}
@Override
@ -92,7 +95,7 @@ public class GoStructType extends GoType {
long offset = field.getOffset();
long fieldSize = field.getType().getBaseType().getSize();
sb.append("%s %s // %d..%d".formatted(field.getNameString(),
field.getType().getBaseType().getNameString(), offset, offset + fieldSize));
field.getType().getNameString(), offset, offset + fieldSize));
}
return sb.toString();
}
@ -138,7 +141,7 @@ public class GoStructType extends GoType {
String comment = dtc.getComment();
comment = comment == null ? "" : (comment + "\n");
comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getNameString(),
skippedFieldType.getBaseType().getNameString());
skippedFieldType.getNameString());
dtc.setComment(comment);
}
}

View File

@ -15,16 +15,18 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
/**
* Common abstract base class for GoType classes
*/
@PlateComment()
public abstract class GoType implements StructureMarkup<GoType> {
private static final Map<GoKind, Class<? extends GoType>> specializedTypeClasses =
@ -38,6 +40,15 @@ public abstract class GoType implements StructureMarkup<GoType> {
Map.entry(GoKind.Map, GoMapType.class),
Map.entry(GoKind.Interface, GoInterfaceType.class));
/**
* Returns the specific GoType derived class that will handle the go type located at the
* specified offset.
*
* @param programContext program-level mapper context
* @param offset absolute location of go type struct
* @return GoType class that will best handle the type struct
* @throws IOException if error reading
*/
public static Class<? extends GoType> getSpecializedTypeClass(GoRttiMapper programContext,
long offset) throws IOException {
GoTypeDetector typeDetector = programContext.readStructure(GoTypeDetector.class, offset);
@ -59,10 +70,14 @@ public abstract class GoType implements StructureMarkup<GoType> {
@FieldOutput
protected GoBaseType typ;
public GoBaseType getBaseType() {
protected GoBaseType getBaseType() {
return typ;
}
public String getNameString() throws IOException {
return typ.getNameString();
}
public String getDebugId() {
return "%s@%s".formatted(
context.getMappingInfo().getDescription(),
@ -95,14 +110,14 @@ public abstract class GoType implements StructureMarkup<GoType> {
}
@Override
public void additionalMarkup() throws IOException {
public void additionalMarkup(MarkupSession session) throws IOException {
GoUncommonType uncommonType = getUncommonType();
if (uncommonType != null) {
GoSlice slice = uncommonType.getMethodsSlice();
slice.markupArray(getStructureName() + "_methods", GoMethod.class, false);
slice.markupArrayElements(GoMethod.class);
slice.markupArray(getStructureName() + "_methods", GoMethod.class, false, session);
slice.markupArrayElements(GoMethod.class, session);
programContext.labelStructure(uncommonType, typ.getNameString() + "_" +
session.labelStructure(uncommonType, typ.getNameString() + "_" +
programContext.getStructureDataTypeName(GoUncommonType.class));
}
}
@ -153,7 +168,7 @@ public abstract class GoType implements StructureMarkup<GoType> {
* Converts a golang RTTI type structure into a Ghidra data type.
*
* @return {@link DataType} that represents the golang type
* @throws IOException
* @throws IOException if error getting name of the type
*/
public DataType recoverDataType() throws IOException {
DataType dt = Undefined.getUndefinedDataType((int) typ.getSize());

View File

@ -16,7 +16,6 @@
package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;
import generic.jar.ResourceFile;
@ -25,18 +24,12 @@ import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.bin.format.dwarf4.next.DWARFDataTypeConflictHandler;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.DataConverter;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Information about {@link StructureMapping} classes and their metadata, as well as
* accumulated information about structure instances that have been deserialized.
* Information about {@link StructureMapping} classes and their metadata.
* <p>
* To use the full might and majesty of StructureMapping(tm), a DataTypeMapper must be created. It
* must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find}
@ -83,14 +76,14 @@ public class DataTypeMapper implements AutoCloseable {
protected List<CategoryPath> programSearchCPs = new ArrayList<>();
protected List<CategoryPath> archiveSearchCPs = new ArrayList<>();
protected Map<Class<?>, StructureMappingInfo<?>> mappingInfo = new HashMap<>();
protected Set<Address> markedupStructs = new HashSet<>();
protected TaskMonitor markupTaskMonitor = TaskMonitor.DUMMY;
/**
* Creates and initializes a DataTypeMapper.
*
* @param program
* @param archiveGDT
* @throws IOException
* @param program the {@link Program} that will contain the deserialized data
* @param archiveGDT path to a gdt data type archive that will be searched when
* a {@link #getType(String, Class)} is called, or {@code null} if no archive
* @throws IOException if error opening data type archive
*/
protected DataTypeMapper(Program program, ResourceFile archiveGDT) throws IOException {
this.program = program;
@ -108,39 +101,72 @@ public class DataTypeMapper implements AutoCloseable {
}
}
/**
* CategoryPath location (in the program) where new data types will be created to represent
* variable length structures.
*
* @return {@link CategoryPath}, default is ROOT
*/
public CategoryPath getDefaultVariableLengthStructCategoryPath() {
return CategoryPath.ROOT;
}
/**
* Returns the program.
*
* @return ghidra {@link Program}
*/
public Program getProgram() {
return program;
}
protected BinaryReader createProgramReader() {
MemoryByteProvider bp =
new MemoryByteProvider(program.getMemory(), program.getImageBase().getAddressSpace());
return new BinaryReader(bp, !program.getMemory().isBigEndian());
/**
* Creates a {@link MarkupSession} that is controlled by the specified {@link TaskMonitor}.
*
* @param monitor {@link TaskMonitor}
* @return new {@link MarkupSession}
*/
public MarkupSession createMarkupSession(TaskMonitor monitor) {
return new MarkupSession(this, monitor);
}
/**
* Returns a {@link DataConverter} appropriate for the current program.
*
* @return {@link DataConverter}
*/
public DataConverter getDataConverter() {
return DataConverter.getInstance(program.getMemory().isBigEndian());
}
public DataTypeMapper addProgramSearchCategoryPath(CategoryPath... paths) {
/**
* Adds category paths to a search list, used when looking for a data type.
* <p>
* See {@link #getType(String, Class)}.
*
* @param paths vararg list of {@link CategoryPath}s
*/
public void addProgramSearchCategoryPath(CategoryPath... paths) {
programSearchCPs.addAll(Arrays.asList(paths));
return this;
}
public DataTypeMapper addArchiveSearchCategoryPath(CategoryPath... paths) {
/**
* Adds category paths to a search list, used when looking for a data type.
* <p>
* See {@link #getType(String, Class)}.
*
* @param paths vararg list of {@link CategoryPath}s
*/
public void addArchiveSearchCategoryPath(CategoryPath... paths) {
archiveSearchCPs.addAll(Arrays.asList(paths));
return this;
}
/**
* Registers a class that has {@link StructureMapping structure mapping} information.
*
* @param <T>
* @param clazz
* @param <T> structure mapped class type
* @param clazz class that represents a structure, marked with {@link StructureMapping}
* annotation
* @throws IOException if the class's Ghidra structure data type could not be found
*/
public <T> void registerStructure(Class<T> clazz) throws IOException {
@ -162,18 +188,41 @@ public class DataTypeMapper implements AutoCloseable {
mappingInfo.put(clazz, structMappingInfo);
}
/**
* Registers the specified {@link StructureMapping structure mapping} classes.
*
* @param classes list of classes to register
* @throws IOException if a class's Ghidra structure data type could not be found
*/
public void registerStructures(List<Class<?>> classes) throws IOException {
for (Class<?> clazz : classes) {
registerStructure(clazz);
}
}
/**
* Returns the {@link StructureMappingInfo} for a class (that has already been registered).
*
* @param <T> structure mapped class type
* @param clazz the class
* @return {@link StructureMappingInfo} for the specified class, or null if the class was
* not previously {@link #registerStructure(Class) registered}
*/
@SuppressWarnings("unchecked")
public <T> StructureMappingInfo<T> getStructureMappingInfo(Class<T> clazz) {
StructureMappingInfo<?> smi = mappingInfo.get(clazz);
return (StructureMappingInfo<T>) smi;
}
/**
* Returns the {@link StructureMappingInfo} for an object instance.
*
* @param <T> structure mapped class type
* @param structureInstance an instance of a previously registered
* {@link StructureMapping structure mapping} class, or null
* @return {@link StructureMappingInfo} for the instance, or null if the class was
* not previously {@link #registerStructure(Class) registered}
*/
@SuppressWarnings("unchecked")
public <T> StructureMappingInfo<T> getStructureMappingInfo(T structureInstance) {
return structureInstance != null
@ -189,41 +238,35 @@ public class DataTypeMapper implements AutoCloseable {
* fields
*/
public Structure getStructureDataType(Class<?> clazz) {
StructureMappingInfo<?> mi = mappingInfo.get(clazz);
return mi != null ? mi.getStructureDataType() : null;
StructureMappingInfo<?> smi = mappingInfo.get(clazz);
return smi != null ? smi.getStructureDataType() : null;
}
/**
* Returns the name of the Ghidra structure that has been registered for the specified
* structure mapped class.
*
* @param clazz
* @return
* @param clazz a structure mapped class
* @return name of the corresponding Ghidra structure data type, or null if class was not
* registered
*/
public String getStructureDataTypeName(Class<?> clazz) {
StructureMappingInfo<?> mi = mappingInfo.get(clazz);
return mi != null ? mi.getStructureName() : null;
}
protected DataType findType(String name, List<CategoryPath> searchList, DataTypeManager dtm) {
for (CategoryPath searchCP : searchList) {
DataType dataType = dtm.getDataType(searchCP, name);
if (dataType != null) {
return dataType;
}
}
return null;
}
/**
* 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>
* @param name
* @param clazz
* @return
* @param <T> DataType or derived type
* @param name {@link DataType} name
* @param clazz expected DataType class
* @return DataType or null if not found
*/
public <T extends DataType> T getType(String name, Class<T> clazz) {
DataType dataType = findType(name, programSearchCPs, programDTM);
@ -240,27 +283,43 @@ public class DataTypeMapper implements AutoCloseable {
return clazz.isInstance(dataType) ? clazz.cast(dataType) : null;
}
/**
* 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 name {@link DataType} name
* @param clazz expected DataType class
* @param defaultValue value to return if the requested data type was not found
* @return DataType or {@code defaultValue} if not found
*/
public <T extends DataType> T getTypeOrDefault(String name, Class<T> clazz, T defaultValue) {
T result = getType(name, clazz);
return result != null ? result : defaultValue;
}
/**
* Returns the program's data type manager.
*
* @return program's {@link DataTypeManager}
*/
public DataTypeManager getDTM() {
return programDTM;
}
private <T> StructureContext<T> getStructureContext(Class<T> structureClass,
BinaryReader reader) {
StructureMappingInfo<T> smi = getStructureMappingInfo(structureClass);
if (smi == null) {
throw new IllegalArgumentException(
"Unknown structure mapped class: " + structureClass.getSimpleName());
}
return new StructureContext<>(this, smi, reader);
}
public <T> StructureContext<T> getExistingStructureContext(T structureInstance)
throws IOException {
/**
* Returns the {@link StructureContext} of a structure mapped instance.
*
* @param <T> java type of a class that is structure mapped
* @param structureInstance an existing instance of type T
* @return {@link StructureContext} of the instance, or null if instance was null or not
* a structure mapped object
*/
public <T> StructureContext<T> getStructureContextOfInstance(T structureInstance) {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
@ -275,9 +334,8 @@ public class DataTypeMapper implements AutoCloseable {
* @param structureInstance instance of an object that represents something in the program's
* memory
* @return {@link Address} of the object, or null if not found or not a supported object
* @throws IOException
*/
public <T> Address getExistingStructureAddress(T structureInstance) throws IOException {
public <T> Address getAddressOfStructure(T structureInstance) {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
@ -289,118 +347,120 @@ public class DataTypeMapper implements AutoCloseable {
: null;
}
public void setMarkupTaskMonitor(TaskMonitor monitor) {
this.markupTaskMonitor = Objects.requireNonNullElse(monitor, TaskMonitor.DUMMY);
}
public <T> void markup(T obj, boolean nested) throws IOException {
if (markupTaskMonitor.isCancelled()) {
throw new IOException("Markup canceled");
}
if (obj == null) {
return;
}
if (obj instanceof Collection<?> list) {
for (Object listElement : list) {
markup(listElement, nested);
}
}
else if (obj.getClass().isArray()) {
int len = Array.getLength(obj);
for (int i = 0; i < len; i++) {
markup(Array.get(obj, i), nested);
}
}
else if (obj instanceof Iterator<?> it) {
while (it.hasNext()) {
Object itElement = it.next();
markup(itElement, nested);
}
}
else {
StructureContext<T> structureContext = getExistingStructureContext(obj);
if (structureContext == null) {
throw new IllegalArgumentException();
}
markupTaskMonitor.incrementProgress(1);
structureContext.markupStructure(nested);
}
}
/**
* Reads a structure mapped object from the current position of the specified BinaryReader.
*
* @param <T> type of object
* @param structureClass structure mapped object class
* @param structReader {@link BinaryReader} positioned at the start of an object
* @return new object instance of type T
* @throws IOException if error reading
* @throws IllegalArgumentException if specified structureClass is not valid
*/
public <T> T readStructure(Class<T> structureClass, BinaryReader structReader)
throws IOException {
StructureContext<T> structureContext = getStructureContext(structureClass, structReader);
StructureContext<T> structureContext = createStructureContext(structureClass, structReader);
T result = structureContext.readNewInstance();
return result;
}
/**
* Reads a structure mapped object from the specified position of the program.
*
* @param <T> type of object
* @param structureClass structure mapped object class
* @param position of object
* @return new object instance of type T
* @throws IOException if error reading
* @throws IllegalArgumentException if specified structureClass is not valid
*/
public <T> T readStructure(Class<T> structureClass, long position) throws IOException {
return readStructure(structureClass, getReader(position));
}
/**
* Reads a structure mapped object from the specified Address of the program.
*
* @param <T> type of object
* @param structureClass structure mapped object class
* @param address location of object
* @return new object instance of type T
* @throws IOException if error reading
* @throws IllegalArgumentException if specified structureClass is not valid
*/
public <T> T readStructure(Class<T> structureClass, Address address) throws IOException {
return readStructure(structureClass, getReader(address.getOffset()));
}
/**
* Creates a {@link BinaryReader}, at the specified position.
*
* @param position location in the program
* @return new {@link BinaryReader}
*/
public BinaryReader getReader(long position) {
BinaryReader reader = createProgramReader();
reader.setPointerIndex(position);
return reader;
}
/**
* Converts an offset into an Address.
*
* @param offset numeric offset
* @return {@link Address}
*/
public Address getDataAddress(long offset) {
return program.getImageBase().getNewAddress(offset);
}
/**
* Converts an offset into an Address.
*
* @param offset numeric offset
* @return {@link Address}
*/
public Address getCodeAddress(long offset) {
return program.getImageBase().getNewAddress(offset);
}
public void labelAddress(Address addr, String symbolName) throws IOException {
try {
SymbolTable symbolTable = getProgram().getSymbolTable();
Symbol[] symbols = symbolTable.getSymbols(addr);
if (symbols.length == 0 || symbols[0].isDynamic()) {
symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true);
symbolTable.createLabel(addr, symbolName, SourceType.IMPORTED);
}
}
catch (InvalidInputException e) {
throw new IOException(e);
}
}
public <T> void labelStructure(T obj, String symbolName) throws IOException {
Address addr = getExistingStructureAddress(obj);
labelAddress(addr, symbolName);
}
public void markupAddress(Address addr, DataType dt) throws IOException {
markupAddress(addr, dt, -1);
}
public void markupAddress(Address addr, DataType dt, int length) throws IOException {
try {
DataUtilities.createData(program, addr, dt, length, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
}
catch (CodeUnitInsertionException e) {
throw new IOException(e);
}
}
public void markupAddressIfUndefined(Address addr, DataType dt) throws IOException {
Data data = DataUtilities.getDataAtAddress(program, addr);
if (data == null || Undefined.isUndefined(data.getBaseDataType())) {
markupAddress(addr, dt);
}
}
@Override
public String toString() {
return "DataTypeMapper { program: %s}".formatted(program.getName());
return "DataTypeMapper { program: %s }".formatted(program.getName());
}
/**
* Creates a new BinaryReader that reads bytes from the current program's memory image.
* <p>
* Address offsets and index offsets in the BinaryReader should be synonymous.
*
* @return new BinaryReader
*/
protected BinaryReader createProgramReader() {
MemoryByteProvider bp =
new MemoryByteProvider(program.getMemory(), program.getImageBase().getAddressSpace());
return new BinaryReader(bp, !program.getMemory().isBigEndian());
}
protected DataType findType(String name, List<CategoryPath> searchList, DataTypeManager dtm) {
for (CategoryPath searchCP : searchList) {
DataType dataType = dtm.getDataType(searchCP, name);
if (dataType != null) {
return dataType;
}
}
return null;
}
private <T> StructureContext<T> createStructureContext(Class<T> structureClass,
BinaryReader reader) throws IllegalArgumentException {
StructureMappingInfo<T> smi = getStructureMappingInfo(structureClass);
if (smi == null) {
throw new IllegalArgumentException(
"Unknown structure mapped class: " + structureClass.getSimpleName());
}
return new StructureContext<>(this, smi, reader);
}
}

View File

@ -27,13 +27,14 @@ import java.lang.annotation.Target;
* or the return value of a specified method as the string.
*/
@Retention(RUNTIME)
@Target({ FIELD })
@Target(FIELD)
public @interface EOLComment {
/**
* Name of a "getter" method that's return value will be converted to a string and used
* Optional name of a "getter" method that's return value will be converted to a string and used
* as the EOL comment
*
* @return
* @return optional name of a getter method, or if not set, defaults to use the object's
* {@link Object#toString() toString()} method
*/
String value() default "";
}

View File

@ -18,15 +18,18 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.symbol.*;
/**
* Context of an individual field that is being deserialized
* Context of an individual field that is being deserialized, or being markedup.
*
* @param <T> the class that contains this field
* @param structureContext {@link StructureContext} that contains this field
* @param fieldInfo {@link FieldMappingInfo} immutable information about this field
* @param dtc {@link DataTypeComponent} that this field maps to
* @param reader {@link BinaryReader} to use when reading data, may be null if this context is
* for markup operations instead of deserialization
*/
public record FieldContext<T> (
StructureContext<T> structureContext,
@ -34,31 +37,32 @@ public record FieldContext<T> (
DataTypeComponent dtc,
BinaryReader reader) {
/**
* Returns the structure instance that contains this field.
*
* @return structure instance that contains this field
*/
public T getStructureInstance() {
return structureContext.getStructureInstance();
}
public DataTypeMapper getDataTypeMapper() {
return structureContext().getDataTypeMapper();
}
public void appendComment(int commentType, String prefix, String comment, String sep)
throws IOException {
DWARFUtil.appendComment(structureContext.getProgram(), getAddress(), commentType, prefix,
comment, sep);
}
public void addReference(Address refDest) throws IOException {
ReferenceManager refMgr = structureContext.getProgram().getReferenceManager();
Address fieldAddr = getAddress();
refMgr.addMemoryReference(fieldAddr, refDest, RefType.DATA, SourceType.IMPORTED, 0);
}
/**
* Returns the address of this structure field.
*
* @return the address of this field
*/
public Address getAddress() {
return structureContext.getStructureAddress().add(dtc.getOffset());
}
/**
* Returns the value of this java field.
*
* @param <R> result type
* @param expectedType class of expected result type
* @return value of this java field, as type R
* @throws IOException if error getting or converting value
*/
public <R> R getValue(Class<R> expectedType) throws IOException {
return fieldInfo.getValue(structureContext.getStructureInstance(), expectedType);
}

View File

@ -29,22 +29,43 @@ import java.lang.annotation.Target;
* <p>
* The type of the tagged java field can be a java primitive, or a
* {@link StructureMapping structure mapped} class.
* <p>
* Supported java primitive types:
* <ul>
* <li>long, int, short, byte
* <li>char
* </ul>
*
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface FieldMapping {
/**
* Overrides the field name that is matched in the structure
*
* @return name of the structure field to map, or unset to use the java field's name
*/
String fieldName() default "";
/**
* Optional function that will deserialize the tagged field
*
* @return {@link FieldReadFunction}
*/
@SuppressWarnings("rawtypes")
Class<? extends FieldReadFunction> readFunc() default FieldReadFunction.class;
/**
* Allows override the length of the structure field
*
* @return length of the structure field, or unset to use the field's data type
*/
int length() default -1;
/**
* Override the signedness the underlying numeric field.
* Override the signedness of the underlying numeric field.
*
* @return
* @return {@link Signedness} enum, or unset to use the data type's normal signedness
*/
Signedness signedness() default Signedness.Unspecified;
}

View File

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
import java.util.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
@ -29,18 +28,18 @@ import ghidra.program.model.listing.CodeUnit;
/**
* Immutable information needed to deserialize a field in a structure mapped class.
*
* @param <T>
* @param <T> structure mapped class type
*/
public class FieldMappingInfo<T> {
/**
* Creates a FieldMappingInfo instance, used when the structure is not variable length.
*
* @param <T>
* @param field
* @param dtc
* @param signedness
* @param length
* @return
* @param <T> structure mapped class type
* @param field java field
* @param dtc Ghidra structure field
* @param signedness {@link Signedness} enum
* @param length override of structure field, or -1
* @return new {@link FieldMappingInfo} instance
*/
public static <T> FieldMappingInfo<T> createEarlyBinding(Field field, DataTypeComponent dtc,
Signedness signedness, int length) {
@ -57,12 +56,12 @@ public class FieldMappingInfo<T> {
* Creates a FieldMappingInfo instance, used when the structure is variable length and there is
* no pre-defined Ghidra Structure data type.
*
* @param <T>
* @param field
* @param fieldName
* @param signedness
* @param length
* @return
* @param <T> structure mapped class type
* @param field java field
* @param fieldName name of Ghidra structure field
* @param signedness {@link Signedness} enum
* @param length override of structure field, or -1
* @return new {@link FieldMappingInfo} instance
*/
public static <T> FieldMappingInfo<T> createLateBinding(Field field, String fieldName,
Signedness signedness, int length) {
@ -186,14 +185,14 @@ public class FieldMappingInfo<T> {
private FieldMarkupFunction<T> createCommentMarkupFunc(Method commentGetter, int commentType,
String sep) {
return context -> {
return (context, session) -> {
T obj = context.getStructureInstance();
Object val = ReflectionHelper.callGetter(commentGetter, obj);
if (val != null) {
if (val instanceof Collection<?> c && c.isEmpty()) {
return;
}
context.appendComment(commentType, null, val.toString(), sep);
session.appendComment(context, commentType, null, val.toString(), sep);
}
};
}
@ -219,6 +218,9 @@ public class FieldMappingInfo<T> {
}
private FieldReadFunction<T> getReadPrimitiveValueFunc(Class<?> destClass) {
// Create a lambda that reads a primitive value from a context that is specific to the
// java field's type (destClass)
// TODO: floats, other primitive types(?)
if (destClass == Long.class || destClass == Long.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
@ -255,51 +257,9 @@ public class FieldMappingInfo<T> {
.readStructure(field.getType(), context.reader());
}
// @SuppressWarnings({ "unchecked", "rawtypes" })
// private FieldMarkupFunction<T> makeMarkupFunc(
// Class<? extends FieldMarkupFunction> markupFuncClass, String getterName) {
// if (markupFuncClass != FieldMarkupFunction.class) {
// return ReflectionHelper.createInstance(markupFuncClass, this);
// }
//
// if (getterName != null && !getterName.isBlank()) {
// Method getter = ReflectionHelper.findGetter(field.getDeclaringClass(), getterName);
// if (getter == null) {
// throw new IllegalArgumentException(
// "Missing FieldMarkup getter %s for %s".formatted(getterName, field));
// }
// getter.setAccessible(true);
// return (context) -> markupFieldWithGetter(getter, context);
// }
//
// Class<?> fieldType = field.getType();
// if (ReflectionHelper.hasStructureMapping(fieldType)) {
// return this::markupNestedStructure;
// }
//
// Method getter = ReflectionHelper.findGetter(field.getDeclaringClass(), field.getName());
// if (getter != null) {
// getter.setAccessible(true);
// return (context) -> markupFieldWithGetter(getter, context);
// }
//
// throw new IllegalArgumentException("Invalid FieldMarkup: " + field);
// }
// private void markupFieldWithGetter(Method getterMethod, FieldContext<T> fieldContext)
// throws IOException {
// Object getterValue =
// ReflectionHelper.callGetter(getterMethod, fieldContext.getStructureInstance());
// if (getterValue != null) {
// if (getterValue instanceof Collection<?> c && c.isEmpty()) {
// return;
// }
// fieldContext.appendComment(CodeUnit.EOL_COMMENT, "", getterValue.toString(), ";");
// }
// }
private void markupNestedStructure(FieldContext<T> fieldContext) throws IOException {
fieldContext.getDataTypeMapper().markup(fieldContext.getValue(Object.class), true);
private void markupNestedStructure(FieldContext<T> fieldContext, MarkupSession markupSession)
throws IOException {
markupSession.markup(fieldContext.getValue(Object.class), true);
}
private FieldMarkupFunction<T> makeMarkupReferenceFunc(String getterName) {
@ -307,19 +267,19 @@ public class FieldMappingInfo<T> {
Method getter = ReflectionHelper.requireGetter(field.getDeclaringClass(), getterName);
getter.setAccessible(true);
return (context) -> addRefToFieldWithGetter(getter, context);
return (context, session) -> addRefToFieldWithGetter(getter, context, session);
}
private void addRefToFieldWithGetter(Method getterMethod, FieldContext<T> fieldContext)
throws IOException {
private void addRefToFieldWithGetter(Method getterMethod, FieldContext<T> fieldContext,
MarkupSession markupSession) throws IOException {
Object getterValue =
ReflectionHelper.callGetter(getterMethod, fieldContext.getStructureInstance());
if (getterValue != null) {
Address addr = getterValue instanceof Address getterAddr
? getterAddr
: fieldContext.getDataTypeMapper().getExistingStructureAddress(getterValue);
: markupSession.getMappingContext().getAddressOfStructure(getterValue);
if (addr != null) {
fieldContext.addReference(addr);
markupSession.addReference(fieldContext, addr);
}
}
}

View File

@ -17,6 +17,19 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
/**
* A function that decorates a field in a structure mapped class.
*
* @param <T> structure mapped class type
*/
public interface FieldMarkupFunction<T> {
void markupField(FieldContext<T> fieldContext) throws IOException;
/**
* Decorates the specified field.
*
* @param fieldContext information about the field
* @param markupSession state and methods to assist marking up the program
* @throws IOException thrown if error performing the markup
*/
void markupField(FieldContext<T> fieldContext, MarkupSession markupSession) throws IOException;
}

View File

@ -34,17 +34,44 @@ import ghidra.program.model.data.DataType;
@Retention(RUNTIME)
@Target(FIELD)
public @interface FieldOutput {
/**
* Overrides the default logic used to add the marked field to the structure.
*
* @return {@link FieldOutputFunction} class that implements custom logic
*/
@SuppressWarnings("rawtypes")
Class<? extends FieldOutputFunction> fieldOutputFunc() default FieldOutputFunction.class;
/**
* Optional ordinal of the marked field in the structure that will be created.
* <p>
* If unset, the order of the marked fields in the java class would be preserved.
*
* @return integer field ordinal, or if unset, the native java field order
*/
int ordinal() default -1;
/**
* Optional offset for the marked field to be added at.
* <p>
* If the structure under construction is smaller than the specified offset, padding will be
* added to the structure. If the structure is already larger than the specified offset,
* an error will occur.
*
* @return integer offset for the marked field, or if unset, the next location in the
* structure will be used
*/
int offset() default -1;
/**
* Specifies the name of a Ghidra {@link DataType} that will be used for this field when
* creating a Ghidra structure.
* <p>
* If unset, the type of the java field will be consulted to pick a Ghidra {@link DataType}
* for the structure field.
*
* @return
* @return name of the data type to use for this field, or if unset, the java field's type
* will be used to pick the data type
*/
String dataTypeName() default "";
@ -52,7 +79,8 @@ public @interface FieldOutput {
* Marks this field as variable length, which will cause the Ghidra structure containing
* this field to have a "_NN" name suffix that specifies the length of this instance.
*
* @return
* @return boolean true if the marked field's length varies between instances of the same
* structure, false if it is a fixed length field
*/
boolean isVariableLength() default false;
@ -60,7 +88,7 @@ public @interface FieldOutput {
* Specifies a method that will return a Ghidra {@link DataType} that should be used for this
* field when creating a Ghidra structure.
*
* @return
* @return optional name of getter method that will return a Ghidra {@link DataType}
*/
String getter() default "";

View File

@ -17,16 +17,23 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
/**
* A function that helps construct a Ghidra {@link DataType} from annotated field information
* A function that adds a field to a Ghidra structure using annotated field information
* found in a Java class.
*
* @param <T>
* @param <T> type of the structure mapped class
*/
public interface FieldOutputFunction<T> {
/**
* Adds the specified field (in {@code fieldOutputInfo}) to the structure.
*
* @param context {@link StructureContext}
* @param structure {@link Structure} data type
* @param fieldOutputInfo {@link FieldOutputInfo} field info
* @throws IOException if error
*/
void addFieldToStructure(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> fieldOutputInfo) throws IOException;
}

View File

@ -25,7 +25,7 @@ import ghidra.program.model.data.*;
* Immutable information needed to create fields in a Ghidra structure data type, using information
* from a java field.
*
* @param <T>
* @param <T> structure mapped class type
*/
public class FieldOutputInfo<T> {
private final FieldMappingInfo<T> fmi;
@ -63,11 +63,11 @@ public class FieldOutputInfo<T> {
/**
* Returns the value of this java field.
*
* @param <R>
* @param <R> type of the result value
* @param structInstance object containing the field
* @param expectedType expected class of the value
* @return value of the field
* @throws IOException
* @return value of the field, or null if the field's value is not of expected type
* @throws IOException if error accessing java field
*/
public <R> R getValue(T structInstance, Class<R> expectedType) throws IOException {
try {
@ -198,7 +198,7 @@ public class FieldOutputInfo<T> {
}
StructureContext<?> nestedStructContext =
context.getDataTypeMapper().getExistingStructureContext(nestedStruct);
context.getDataTypeMapper().getStructureContextOfInstance(nestedStruct);
if (nestedStructContext == null) {
throw new IOException(
"Missing StructureContext for " + nestedStruct.getClass().getSimpleName());

View File

@ -17,8 +17,22 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
/**
* Functional interface to read a structure field's value.
* <p>
* @see #get(FieldContext)
*
* @param <T> type of structure mapped class that contains this field
*/
@FunctionalInterface
public interface FieldReadFunction<T> {
/**
* Deserializes and returns a field's value.
*
* @param context context for this field
* @return value of the field
* @throws IOException if error reading
*/
Object get(FieldContext<T> context) throws IOException;
}

View File

@ -38,5 +38,11 @@ import ghidra.program.model.address.Address;
@Retention(RUNTIME)
@Target(FIELD)
public @interface MarkupReference {
/**
* Optional name of a 'getter' method to use instead of the getter for the tagged field.
*
* @return name of 'getter' method, if unset the field's normal getter will be used
*/
String value() default "";
}

View File

@ -0,0 +1,375 @@
/* ###
* 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;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* State and methods needed for structure mapped objects to add markup, comments, labels, etc
* to a program.
*/
public class MarkupSession {
protected Program program;
protected DataTypeMapper mappingContext;
protected Set<Address> markedupStructs = new HashSet<>();
protected TaskMonitor monitor;
/**
* Creates a new markup session
*
* @param programContext program-level structure mapping context
* @param monitor allows user to cancel
*/
public MarkupSession(DataTypeMapper programContext, TaskMonitor monitor) {
this.mappingContext = programContext;
this.monitor = monitor;
this.program = programContext.getProgram();
}
/**
* Returns the Ghidra program
*
* @return Ghidra {@link Program}
*/
public Program getProgram() {
return program;
}
/**
* Returns the program level mapping context
*
* @return {@link DataTypeMapper}
*/
public DataTypeMapper getMappingContext() {
return mappingContext;
}
/**
* Decorates the specified object's memory using the various structure mapping tags that
* were applied the object's class definition.
* <p>
* The object can be a structure mapped object, or a collection, array or iterator of structure
* mapped objects.
*
* @param <T> structure mapped object type
* @param obj structure mapped object instance
* @param nested boolean flag, if true the specified object is contained inside another object
* who's data type has already been laid down in memory, removing the need for this object's
* data type to be applied to memory
* @throws IOException if error or cancelled
* @throws IllegalArgumentException if object instance is not a supported type
*/
public <T> void markup(T obj, boolean nested) throws IOException {
if (monitor.isCancelled()) {
throw new IOException("Markup canceled");
}
if (obj == null) {
return;
}
if (obj instanceof Collection<?> list) {
for (Object listElement : list) {
markup(listElement, nested);
}
}
else if (obj.getClass().isArray()) {
int len = Array.getLength(obj);
for (int i = 0; i < len; i++) {
markup(Array.get(obj, i), nested);
}
}
else if (obj instanceof Iterator<?> it) {
while (it.hasNext()) {
Object itElement = it.next();
markup(itElement, nested);
}
}
else {
StructureContext<T> structureContext = mappingContext.getStructureContextOfInstance(obj);
if (structureContext == null) {
throw new IllegalArgumentException();
}
monitor.incrementProgress(1);
markupStructure(structureContext, nested);
}
}
/**
* Applies the specified {@link DataType} to the specified {@link Address}.
*
* @param addr location to place DataType
* @param dt {@link DataType}
* @throws IOException if error marking up address
*/
public void markupAddress(Address addr, DataType dt) throws IOException {
markupAddress(addr, dt, -1);
}
/**
* Applies the specified {@link DataType} to the specified {@link Address}.
*
* @param addr location to place DataType
* @param dt {@link DataType}
* @param length length of the data type instance, or -1 if the data type is fixed length
* @throws IOException if error marking up address
*/
public void markupAddress(Address addr, DataType dt, int length) throws IOException {
try {
DataUtilities.createData(program, addr, dt, length, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
}
catch (CodeUnitInsertionException e) {
throw new IOException(e);
}
}
/**
* Applies the specified {@link DataType} to the specified {@link Address}.
*
* @param addr location to place DataType
* @param dt {@link DataType}
* @throws IOException if error marking up address
*/
public void markupAddressIfUndefined(Address addr, DataType dt) throws IOException {
Data data = DataUtilities.getDataAtAddress(program, addr);
if (data == null || Undefined.isUndefined(data.getBaseDataType())) {
markupAddress(addr, dt);
}
}
/**
* Places a label at the specified structure mapped object's address.
*
* @param <T> structure mapped object type
* @param obj structure mapped object
* @param symbolName name
* @throws IOException if error
*/
public <T> void labelStructure(T obj, String symbolName) throws IOException {
Address addr = mappingContext.getAddressOfStructure(obj);
labelAddress(addr, symbolName);
}
/**
* Places a label at the specified address.
*
* @param addr {@link Address}
* @param symbolName name
* @throws IOException if error
*/
public void labelAddress(Address addr, String symbolName) throws IOException {
try {
SymbolTable symbolTable = program.getSymbolTable();
Symbol[] symbols = symbolTable.getSymbols(addr);
if (symbols.length == 0 || symbols[0].isDynamic()) {
symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true);
symbolTable.createLabel(addr, symbolName, SourceType.IMPORTED);
}
}
catch (InvalidInputException e) {
throw new IOException(e);
}
}
/**
* Adds a comment to the specified field, appending to any previous values
* already there. If the existing comment already contains the specified comment value,
* the operation is skipped.
*
* @param fieldContext the field
* @param commentType {@link CodeUnit#EOL_COMMENT}, {@link CodeUnit#PLATE_COMMENT},
* {@link CodeUnit#POST_COMMENT}, {@link CodeUnit#PRE_COMMENT}
* @param prefix String prefix to place in front of the comment string
* @param comment String value to append
* @param sep separator to use between existing comments (for example, "\n")
* @throws IOException if error adding comment
*/
public void appendComment(FieldContext<?> fieldContext, int commentType, String prefix,
String comment, String sep) throws IOException {
DWARFUtil.appendComment(program, fieldContext.getAddress(), commentType, prefix, comment,
sep);
}
/**
* Adds a comment to the specified structure, appending to any previous values
* already there. If the existing comment already contains the specified comment value,
* the operation is skipped.
*
* @param structureContext the structure
* @param commentType {@link CodeUnit#EOL_COMMENT}, {@link CodeUnit#PLATE_COMMENT},
* {@link CodeUnit#POST_COMMENT}, {@link CodeUnit#PRE_COMMENT}
* @param prefix String prefix to place in front of the comment string
* @param comment String value to append
* @param sep separator to use between existing comments (for example, "\n")
* @throws IOException if error adding comment
*/
public void appendComment(StructureContext<?> structureContext, int commentType, String prefix,
String comment, String sep) throws IOException {
DWARFUtil.appendComment(program, structureContext.getStructureAddress(), commentType,
prefix, comment, sep);
}
/**
* Decorates a structure mapped structure, and everything it contains.
*
* @param <T> structure mapped type
* @param structureContext {@link StructureContext}
* @param nested if true, it is assumed that the Ghidra data types have already been
* placed and only markup needs to be performed.
* @throws IOException if error marking up structure
*/
public <T> void markupStructure(StructureContext<T> structureContext, boolean nested)
throws IOException {
Address addr = structureContext.getStructureAddress();
if (!nested && !markedupStructs.add(addr)) {
return;
}
T instance = structureContext.getStructureInstance();
if (!nested) {
try {
Structure structDT = structureContext.getStructureDataType();
markupAddress(addr, structDT);
}
catch (IOException e) {
StructureMappingInfo<T> mappingInfo = structureContext.getMappingInfo();
throw new IOException("Markup failed for structure %s at %s"
.formatted(mappingInfo.getDescription(), addr),
e);
}
if (instance instanceof StructureMarkup<?> sm) {
String structureLabel = sm.getStructureLabel();
if (structureLabel != null && structureLabel.isBlank()) {
labelAddress(addr, structureLabel);
}
}
}
markupFields(structureContext);
if (instance instanceof StructureMarkup<?> sm) {
sm.additionalMarkup(this);
}
}
<T> void markupFields(StructureContext<T> structureContext) throws IOException {
T structureInstance = structureContext.getStructureInstance();
StructureMappingInfo<T> mappingInfo = structureContext.getMappingInfo();
for (FieldMappingInfo<T> fmi : mappingInfo.getFields()) {
for (FieldMarkupFunction<T> func : fmi.getMarkupFuncs()) {
FieldContext<T> fieldContext = structureContext.createFieldContext(fmi, false);
func.markupField(fieldContext, this);
}
}
if (structureInstance instanceof StructureMarkup<?> sm) {
for (Object externalInstance : sm.getExternalInstancesToMarkup()) {
markup(externalInstance, false);
}
}
for (StructureMarkupFunction<T> markupFunc : mappingInfo.getMarkupFuncs()) {
markupFunc.markupStructure(structureContext, this);
}
}
/**
* Creates references from each element of an array to a list of target addresses.
*
* @param arrayAddr the address of the start of the array
* @param elementSize the size of each array element
* @param targetAddrs list of addresses that will receive references from each array elements
* @throws IOException if error
*/
public void markupArrayElementReferences(Address arrayAddr, int elementSize,
List<Address> targetAddrs) throws IOException {
if (!targetAddrs.isEmpty()) {
ReferenceManager refMgr = program.getReferenceManager();
for (Address targetAddr : targetAddrs) {
if (targetAddr != null) {
refMgr.addMemoryReference(arrayAddr, targetAddr, RefType.DATA,
SourceType.IMPORTED, 0);
}
arrayAddr = arrayAddr.add(elementSize);
}
}
}
/**
* Creates a default function at the specified address.
*
* @param name name of the new function
* @param addr address of the new function
* @return {@link Function} that was created
*/
public Function createFunctionIfMissing(String name, Address addr) {
Function function = program.getListing().getFunctionAt(addr);
if (function == null) {
try {
if (!program.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(addr)) {
Msg.warn(this,
"Unable to create function not contained within loaded memory: %s@%s"
.formatted(name, addr));
return null;
}
function = program.getFunctionManager()
.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED);
}
catch (OverlappingFunctionException | InvalidInputException e) {
Msg.error(this, e);
}
}
else {
// TODO: this does nothing. re-evalulate this logic
//mappingContext.labelAddress(addr, name);
}
return function;
}
/**
* Creates a reference from the specified field to the specified address.
*
* @param fieldContext field, is the source of the reference
* @param refDest destination address of the reference
*/
public void addReference(FieldContext<?> fieldContext, Address refDest) {
ReferenceManager refMgr = program.getReferenceManager();
Address fieldAddr = fieldContext.getAddress();
refMgr.addMemoryReference(fieldAddr, refDest, RefType.DATA, SourceType.IMPORTED, 0);
}
}

View File

@ -34,7 +34,8 @@ public @interface PlateComment {
* Name of a "getter" method that's return value will be converted to a string and used
* as the comment
*
* @return
* @return name of a 'getter' method that will return the string to use for the comment, or
* if unset, the containing object's "toString()" method
*/
String value() default "";
}

View File

@ -15,12 +15,11 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
import java.util.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.lang.reflect.Array;
import java.util.*;
import ghidra.program.model.data.*;
@ -63,7 +62,7 @@ public class ReflectionHelper {
* @param field reflection {@link Field}
* @param obj java instance that contains the field
* @param value value to write
* @throws IOException
* @throws IOException if error accessing field or converting value
*/
public static void assignField(Field field, Object obj, Object value) throws IOException {
Class<?> fieldType = field.getType();
@ -92,11 +91,11 @@ public class ReflectionHelper {
* Return Ghidra data type representing an array of primitive values.
*
* @param array_value java array object
* @param fieldType
* @param length
* @param signedness
* @param dataTypeMapper
* @return
* @param fieldType class representing the java array type
* @param length length of an element of the array, or -1
* @param signedness {@link Signedness} enum
* @param dataTypeMapper program level structure mapping context
* @return Ghdira {@link ArrayDataType} representing the specified java array type
*/
public static DataType getArrayOutputDataType(Object array_value, Class<?> fieldType, int length,
Signedness signedness, DataTypeMapper dataTypeMapper) {
@ -239,6 +238,17 @@ public class ReflectionHelper {
}
/**
* Creates an instance of the specified target class, using an optional context parameter
* to the constructor.
*
* @param <T> type of the class to be created
* @param <CTX> type of the context to be passed to the constructor
* @param targetClass class to be created
* @param optionalContext anything, or null
* @return new instance of type T
* @throws IllegalArgumentException if error creating instance
*/
public static <T, CTX> T createInstance(Class<T> targetClass, CTX optionalContext)
throws IllegalArgumentException {
@ -293,9 +303,25 @@ public class ReflectionHelper {
}
}
public static void getMarkedMethods(Class<?> targetClass,
/**
* Returns a list of methods that have been marked with a specific annotation.
*
* @param targetClass class to query
* @param annotationClass annotation to search for
* @param methods list to accumulate results into, or null to allocate new list. Also returned
* as the result of this function
* @param includeParentClasses boolean flag, if true recurse into parent classes first
* @param paramClasses list of parameters that the tagged methods should declare. Methods
* will be skipped if they don't match
* @return list of found methods that match the annotation and param list
*/
public static List<Method> getMarkedMethods(Class<?> targetClass,
Class<? extends Annotation> annotationClass, List<Method> methods,
boolean includeParentClasses, Class<?>... paramClasses) {
if (methods == null) {
methods = new ArrayList<>();
}
if (includeParentClasses && targetClass.getSuperclass() != null) {
getMarkedMethods(targetClass.getSuperclass(), annotationClass, methods,
includeParentClasses, paramClasses);
@ -317,6 +343,7 @@ public class ReflectionHelper {
methods.add(method);
}
}
return methods;
}
public static <T extends Annotation> List<T> getAnnotations(Class<?> targetClass,

View File

@ -15,6 +15,9 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
/**
* Signedness attribute of a structure mapped field
*/
public enum Signedness {
Unspecified, Signed, Unsigned
}

View File

@ -18,12 +18,9 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
/**
* Information about an instance of a structure that has been read from the memory of a
@ -53,6 +50,13 @@ public class StructureContext<T> {
protected T structureInstance;
protected Structure structureDataType;
/**
* Creates an instance of a {@link StructureContext}.
*
* @param dataTypeMapper mapping context for the program
* @param mappingInfo mapping information about this structure
* @param reader {@link BinaryReader} positioned at the start of the structure to be read
*/
public StructureContext(DataTypeMapper dataTypeMapper, StructureMappingInfo<T> mappingInfo,
BinaryReader reader) {
this.dataTypeMapper = dataTypeMapper;
@ -62,6 +66,13 @@ public class StructureContext<T> {
this.structureDataType = mappingInfo.getStructureDataType();
}
/**
* Creates a new instance of the structure by deserializing the structure's marked
* fields into java fields.
*
* @return new instance of structure
* @throws IOException if error reading
*/
public T readNewInstance() throws IOException {
structureInstance = mappingInfo.getInstanceCreator().get(this);
@ -79,7 +90,7 @@ public class StructureContext<T> {
/**
* Returns the {@link StructureMappingInfo} for this structure's class.
*
* @return
* @return {@link StructureMappingInfo} for this structure's class
*/
public StructureMappingInfo<T> getMappingInfo() {
return mappingInfo;
@ -91,16 +102,12 @@ public class StructureContext<T> {
* a {@link ContextField} tag on a field in your class that specifies the correct
* DataTypeMapper type.
*
* @return
* @return the program mapping context that control's this structure instance
*/
public DataTypeMapper getDataTypeMapper() {
return dataTypeMapper;
}
public Program getProgram() {
return dataTypeMapper.program;
}
/**
* Returns the address in the program of this structure instance.
*
@ -113,8 +120,9 @@ public class StructureContext<T> {
/**
* Returns the address of an offset from the start of this structure instance.
*
* @param fieldOffset
* @return
* @param fieldOffset number of bytes from the beginning of this structure where a field (or
* other location of interest) starts
* @return {@link Address} of specified offset
*/
public Address getFieldAddress(long fieldOffset) {
return getStructureAddress().add(fieldOffset);
@ -123,8 +131,9 @@ public class StructureContext<T> {
/**
* Returns the stream location of an offset from the start of this structure instance.
*
* @param fieldOffset
* @return
* @param fieldOffset number of bytes from the beginning of this structure where a field (or
* other location of interest) starts
* @return absolute offset / position in the program / BinaryReader stream
*/
public long getFieldLocation(long fieldOffset) {
return structureStart + fieldOffset;
@ -133,7 +142,7 @@ public class StructureContext<T> {
/**
* Returns the stream location of this structure instance.
*
* @return
* @return absolute offset / position in the program / BinaryReader stream of this structure
*/
public long getStructureStart() {
return structureStart;
@ -142,7 +151,8 @@ public class StructureContext<T> {
/**
* Returns the stream location of the end of this structure instance.
*
* @return
* @return absolute offset / position in the program / BinaryReader stream of the byte after
* this structure
*/
public long getStructureEnd() {
return structureStart + getStructureLength();
@ -151,7 +161,8 @@ public class StructureContext<T> {
/**
* Returns the length of this structure instance.
*
* @return
* @return length of this structure, or 0 if this structure is a variable length structure
* that does not have a fixed length
*/
public int getStructureLength() {
return structureDataType != null
@ -159,10 +170,20 @@ public class StructureContext<T> {
: 0;
}
/**
* Returns a reference to the object instance that was deserialized.
*
* @return reference to deserialized structure mapped object
*/
public T getStructureInstance() {
return structureInstance;
}
/**
* Returns the {@link BinaryReader} that is used to deserialize this structure.
*
* @return {@link BinaryReader} that is used to deserialize this structure
*/
public BinaryReader getReader() {
return reader;
}
@ -171,14 +192,23 @@ public class StructureContext<T> {
* Returns an independent {@link BinaryReader} that is positioned at the start of the
* specified field.
*
* @param fieldOffset
* @return
* @param fieldOffset number of bytes from the beginning of this structure where a field (or
* other location of interest) starts
* @return new {@link BinaryReader} positioned at the specified relative offset
*/
public BinaryReader getFieldReader(long fieldOffset) {
return reader.clone(structureStart + fieldOffset);
}
/**
* Creates a new {@link FieldContext} for a specific field.
*
* @param fmi {@link FieldMappingInfo field} of interest
* @param includeReader boolean flag, if true create a BinaryReader for the field, if false no
* BinaryReader will be created
* @return new {@link FieldContext}
*/
public FieldContext<T> createFieldContext(FieldMappingInfo<T> fmi, boolean includeReader) {
DataTypeComponent dtc = fmi.getDtc(structureDataType);
BinaryReader fieldReader = includeReader ? getFieldReader(dtc.getOffset()) : null;
@ -187,87 +217,14 @@ public class StructureContext<T> {
}
/**
* Places a comment at the start of this structure, appending to any previous values
* already there.
* Returns the Ghidra {@link Structure structure data type} that represents this object.
* <p>
* If this is an instance of a variable length structure mapped class, a custom structure data
* type will be minted that exactly matches this instance's variable length fields.
*
* @param commentType
* @param prefix
* @param comment
* @param sep
* @throws IOException
* @return Ghidra {@link Structure structure data type} that represents this object
* @throws IOException if error constructing new struct data type
*/
public void appendComment(int commentType, String prefix, String comment, String sep)
throws IOException {
DWARFUtil.appendComment(dataTypeMapper.getProgram(), getStructureAddress(), commentType,
prefix, comment, sep);
}
public boolean isAlreadyMarkedup() {
Address addr = getStructureAddress();
Data data = getProgram().getListing().getDataContaining(addr);
if (data != null && data.getBaseDataType() instanceof Structure) {
return true;
}
return false;
}
/**
* @param nested if true, it is assumed that the Ghidra data types have already been
* placed and only markup needs to be performed.
*
* @throws IOException
*/
public void markupStructure(boolean nested) throws IOException {
Address addr = getStructureAddress();
if (!nested && !dataTypeMapper.markedupStructs.add(addr)) {
return;
}
if (!nested) {
try {
Structure structDT = getStructureDataType();
dataTypeMapper.markupAddress(addr, structDT);
}
catch (IOException e) {
throw new IOException("Markup failed for structure %s at %s"
.formatted(mappingInfo.getDescription(), getStructureAddress()),
e);
}
if (structureInstance instanceof StructureMarkup<?> sm) {
String structureLabel = sm.getStructureLabel();
if (structureLabel != null) {
dataTypeMapper.labelAddress(addr, structureLabel);
}
}
}
markupFields();
if (structureInstance instanceof StructureMarkup<?> sm) {
sm.additionalMarkup();
}
}
public void markupFields() throws IOException {
for (FieldMappingInfo<T> fmi : mappingInfo.getFields()) {
for (FieldMarkupFunction<T> func : fmi.getMarkupFuncs()) {
func.markupField(createFieldContext(fmi, false));
}
}
if (structureInstance instanceof StructureMarkup<?> sm) {
for (Object externalInstance : sm.getExternalInstancesToMarkup()) {
dataTypeMapper.markup(externalInstance, false);
}
}
for (StructureMarkupFunction<T> markupFunc : mappingInfo.getMarkupFuncs()) {
markupFunc.markupStructure(this);
}
}
public Structure getStructureDataType() throws IOException {
if (structureDataType == null) {
// if this is a variable length struct, a new custom struct datatype needs to be created

View File

@ -35,8 +35,8 @@ import java.lang.annotation.*;
* will be marked up in the Ghidra program.
* <p>
* The tagged class must be {@link DataTypeMapper#registerStructure(Class) registered} with
* the program context to enable the suite of structure mapped classes to work together when
* applied to a Ghidra binary.
* 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
@ -61,10 +61,16 @@ public @interface StructureMapping {
* {@link DataTypeMapper#addProgramSearchCategoryPath(ghidra.program.model.data.CategoryPath...) program}
* search paths.
*
* @return
* @return name of a Ghidra structure data type
*/
String structureName();
/**
* Optional reference to a 'function' (implemented via a class) that will be called to do
* custom markup.
*
* @return {@link StructureMarkupFunction} class
*/
@SuppressWarnings("rawtypes")
Class<? extends StructureMarkupFunction> markupFunc() default StructureMarkupFunction.class;
}

View File

@ -15,10 +15,9 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
import java.util.*;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.CodeUnit;
@ -33,11 +32,28 @@ 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.
*
* @param <T> structure mapped class
* @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
* @return new {@link StructureMappingInfo} for the specified class
* @throws IllegalArgumentException if targetClass isn't tagged as a structure mapped class
*/
public static <T> StructureMappingInfo<T> fromClass(Class<T> targetClass,
Structure structDataType) {
StructureMapping sma = targetClass.getAnnotation(StructureMapping.class);
@ -59,7 +75,7 @@ public class StructureMappingInfo<T> {
private final List<FieldOutputInfo<T>> outputFields = new ArrayList<>();
private final List<StructureMarkupFunction<T>> markupFuncs = new ArrayList<>();
private final List<Field> contextFields = new ArrayList<>();
private final List<Method> afterMethods = new ArrayList<>();
private final List<Method> afterMethods;
private final boolean useFieldMappingInfo;
private Field structureContextField;
@ -77,17 +93,13 @@ public class StructureMappingInfo<T> {
Collections.sort(outputFields,
(foi1, foi2) -> Integer.compare(foi1.getOrdinal(), foi2.getOrdinal()));
ReflectionHelper.getMarkedMethods(targetClass, AfterStructureRead.class, afterMethods,
true);
afterMethods =
ReflectionHelper.getMarkedMethods(targetClass, AfterStructureRead.class, null, true);
List<Method> markupGetters = new ArrayList<>();
ReflectionHelper.getMarkedMethods(targetClass, Markup.class, markupGetters, true);
List<Method> markupGetters =
ReflectionHelper.getMarkedMethods(targetClass, Markup.class, null, true);
for (Method markupGetterMethod : markupGetters) {
markupFuncs.add(context -> {
T obj = context.getStructureInstance();
Object val = ReflectionHelper.callGetter(markupGetterMethod, obj);
context.getDataTypeMapper().markup(val, false);
});
markupFuncs.add(createMarkupFuncFromGetter(markupGetterMethod));
}
for (PlateComment pca : ReflectionHelper.getAnnotations(targetClass, PlateComment.class,
@ -131,6 +143,13 @@ public class StructureMappingInfo<T> {
return afterMethods;
}
/**
* Deserializes a structure mapped instance by assigning values to its
* {@link FieldMapping &#64;FieldMapping mapped} java fields.
*
* @param context {@link StructureContext}
* @throws IOException if error reading the structure
*/
public void readStructure(StructureContext<T> context) throws IOException {
T newInstance = context.getStructureInstance();
if (newInstance instanceof StructureReader<?> selfReader) {
@ -154,6 +173,15 @@ public class StructureMappingInfo<T> {
return markupFuncs;
}
/**
* Creates a new customized {@link Structure structure data type} for a variable length
* structure mapped class.
*
* @param context {@link StructureContext} of a variable length structure mapped instance
* @return new {@link Structure structure data type} with a name that encodes the size
* information of the variable length fields
* @throws IOException if error creating the Ghidra data type
*/
public Structure createStructureDataType(StructureContext<T> context) throws IOException {
// used to create a structure that has variable length fields
@ -185,19 +213,32 @@ public class StructureMappingInfo<T> {
return newStruct;
}
/**
* Reaches into a structure mapped instance and extracts its StructureContext field value.
*
* @param structureInstance instance to query
* @return {@link StructureContext}, or null if error extracting value
*/
@SuppressWarnings("unchecked")
public StructureContext<T> recoverStructureContext(T structureInstance) throws IOException {
return structureContextField != null
? ReflectionHelper.getFieldValue(structureInstance, structureContextField,
StructureContext.class)
: null;
public StructureContext<T> recoverStructureContext(T structureInstance) {
try {
if (structureContextField != null) {
return ReflectionHelper.getFieldValue(structureInstance, structureContextField,
StructureContext.class);
}
}
catch (IOException e) {
// ignore, drop thru return null
}
return null;
}
/**
* Initializes any {@link ContextField} fields in a new structure instance.
*
* @param context
* @throws IOException
* @param context {@link StructureContext}
* @throws IOException if error assigning values to context fields in the structure mapped
* instance
*/
public void assignContextFieldValues(StructureContext<T> context) throws IOException {
Class<?> dataTypeMapperType = context.getDataTypeMapper().getClass();
@ -230,10 +271,10 @@ public class StructureMappingInfo<T> {
return null;
}
private void assignField(FieldContext<T> readContext, Object value)
private void assignField(FieldContext<T> fieldContext, Object value)
throws IOException {
Field field = readContext.fieldInfo().getField();
T structureInstance = readContext.getStructureInstance();
Field field = fieldContext.fieldInfo().getField();
T structureInstance = fieldContext.getStructureInstance();
ReflectionHelper.assignField(field, structureInstance, value);
}
@ -321,15 +362,23 @@ public class StructureMappingInfo<T> {
private void addPlateCommentMarkupFuncs(PlateComment pca) {
Method commentGetter =
ReflectionHelper.getCommentMethod(targetClass, pca.value(), "toString");
markupFuncs.add(context -> {
markupFuncs.add((context, session) -> {
T obj = context.getStructureInstance();
Object val = ReflectionHelper.callGetter(commentGetter, obj);
if (val != null) {
context.appendComment(CodeUnit.PLATE_COMMENT, null, val.toString(), "\n");
session.appendComment(context, CodeUnit.PLATE_COMMENT, null, val.toString(), "\n");
}
});
}
private StructureMarkupFunction<T> createMarkupFuncFromGetter(Method markupGetterMethod) {
return (context, session) -> {
T obj = context.getStructureInstance();
Object val = ReflectionHelper.callGetter(markupGetterMethod, obj);
session.markup(val, false);
};
}
private static int getStructLength(Structure struct) {
return struct.isZeroLength() ? 0 : struct.getLength();
}

View File

@ -15,9 +15,8 @@
*/
package ghidra.app.util.bin.format.golang.structmapping;
import java.util.List;
import java.io.IOException;
import java.util.List;
/**
* Optional interface that structure mapped classes can implement that allows them to control how
@ -36,7 +35,7 @@ public interface StructureMarkup<T> {
* Returns the name of the instance, typically retrieved from data found inside the instance.
*
* @return string name, or null if this instance does not have a name
* @throws IOException
* @throws IOException if error getting name
*/
default String getStructureName() throws IOException {
return null;
@ -46,7 +45,7 @@ public interface StructureMarkup<T> {
* Returns a string that can be used to place a label on the instance.
*
* @return string to be used as a labe, or null if there is not a valid label for the instance
* @throws IOException
* @throws IOException if error getting label
*/
default String getStructureLabel() throws IOException {
String name = getStructureName();
@ -59,9 +58,10 @@ public interface StructureMarkup<T> {
/**
* Called to allow the implementor to perform custom markup of itself.
*
* @throws IOException
* @param session state and methods to assist marking up the program
* @throws IOException if error during markup
*/
default void additionalMarkup() throws IOException {
default void additionalMarkup(MarkupSession session) throws IOException {
// empty
}
@ -69,7 +69,7 @@ public interface StructureMarkup<T> {
* Returns a list of items that should be recursively marked up.
*
* @return list of structure mapped object instances that should be marked up
* @throws IOException
* @throws IOException if error getting instances
*/
default List<?> getExternalInstancesToMarkup() throws IOException {
return List.of();

View File

@ -17,6 +17,20 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
/**
* Function that decorates a Ghidra structure
*
* @param <T> structure mapped class type
*/
public interface StructureMarkupFunction<T> {
void markupStructure(StructureContext<T> context) throws IOException;
/**
* Decorates the specified structure.
*
* @param context {@link StructureContext}
* @param markupSession state and methods to assist marking up the program
* @throws IOException thrown if error performing the markup
*/
void markupStructure(StructureContext<T> context, MarkupSession markupSession)
throws IOException;
}

View File

@ -21,7 +21,7 @@ import java.io.IOException;
* Interface used by structure mapped classes that need to manually deserialize themselves from
* the raw data, required when the structure contains variable length fields.
*
* @param <T>
* @param <T> structure mapped type
*/
public interface StructureReader<T> {
/**