Merge remote-tracking branch 'origin/GP-2114_dev747368_Golang--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-05-01 06:19:11 -04:00
commit aa893f6721
125 changed files with 12264 additions and 1492 deletions

View file

@ -15,6 +15,7 @@
Module.manifest||GHIDRA||||END|
data/ElfFunctionsThatDoNotReturn||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
data/GolangFunctionsThatDoNotReturn||GHIDRA||||END|
data/MachOFunctionsThatDoNotReturn||GHIDRA||||END|
data/PEFunctionsThatDoNotReturn||GHIDRA||||END|
data/base.file.extensions.icons.theme.properties||GHIDRA||||END|
@ -85,6 +86,7 @@ data/symbols/win64/mfc90u.exports||GHIDRA||||END|
data/symbols/win64/msvcrt.hints||GHIDRA||||END|
data/typeinfo/generic/generic_clib.gdt||GHIDRA||||END|
data/typeinfo/generic/generic_clib_64.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END|
data/typeinfo/win32/msvcrt/clsids.txt||GHIDRA||reviewed||END|
data/typeinfo/win32/msvcrt/guids.txt||GHIDRA||reviewed||END|

View file

@ -19,4 +19,5 @@ InstructionSkipper
DataTypeReferenceFinder
ChecksumAlgorithm
OverviewColorService
DWARFFunctionFixup
ElfInfoProducer

View file

@ -0,0 +1,66 @@
# Golang function names which do not return
runtime.abort.abi0
runtime.exit.abi0
runtime.dieFromSignal
runtime.exitThread
runtime.fatal
runtime.fatalthrow
runtime.fatalpanic
runtime.gopanic
runtime.panicdivide
runtime.throw
runtime.goPanicIndex
runtime.goPanicIndexU
runtime.goPanicSliceAlen
runtime.goPanicSliceAlenU
runtime.goPanicSliceAcap
runtime.goPanicSliceAcapU
runtime.goPanicSliceB
runtime.goPanicSliceBU
runtime.goPanicSlice3Alen
runtime.goPanicSlice3AlenU
runtime.goPanicSlice3Acap
runtime.goPanicSlice3AcapU
runtime.goPanicSlice3B
runtime.goPanicSlice3BU
runtime.goPanicSlice3C
runtime.goPanicSlice3CU
runtime.goPanicSliceConvert
runtime.panicIndex
runtime.panicIndexU
runtime.panicSliceAlen
runtime.panicSliceAlenU
runtime.panicSliceAcap
runtime.panicSliceAcapU
runtime.panicSliceB
runtime.panicSliceBU
runtime.panicSlice3Alen
runtime.panicSlice3AlenU
runtime.panicSlice3Acap
runtime.panicSlice3AcapU
runtime.panicSlice3B
runtime.panicSlice3BU
runtime.panicSlice3C
runtime.panicSlice3CU
runtime.panicSliceConvert
runtime.panicdottypeE
runtime.panicdottypeI
runtime.panicnildottype
runtime.panicoverflow
runtime.panicfloat
runtime.panicmem
runtime.panicmemAddr
runtime.panicshift
runtime.goexit0
runtime.goexit0.abi0
runtime.goexit1
runtime.goexit.abi0
runtime.Goexit
runtime.sigpanic
runtime.sigpanic0.abi0

View file

@ -1,5 +1,8 @@
<noReturnFunctionConstraints>
<executable_format name="Executable and Linking Format (ELF)">
<compiler id="golang">
<functionNamesFile>GolangFunctionsThatDoNotReturn</functionNamesFile>
</compiler>
<functionNamesFile>ElfFunctionsThatDoNotReturn</functionNamesFile>
</executable_format>
<executable_format name="Mac OS X Mach-O">
@ -9,6 +12,9 @@
<functionNamesFile>MachOFunctionsThatDoNotReturn</functionNamesFile>
</executable_format>
<executable_format name="Portable Executable (PE)">
<compiler id="golang">
<functionNamesFile>GolangFunctionsThatDoNotReturn</functionNamesFile>
</compiler>
<functionNamesFile>PEFunctionsThatDoNotReturn</functionNamesFile>
</executable_format>
</noReturnFunctionConstraints>

View file

@ -57,7 +57,6 @@
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.format.dwarf4.next.*;
import ghidra.program.model.data.BuiltInDataTypeManager;
public class DWARF_ExtractorScript extends GhidraScript {
@ -70,8 +69,7 @@ public class DWARF_ExtractorScript extends GhidraScript {
DWARFImportOptions importOptions = new DWARFImportOptions();
importOptions.setImportLimitDIECount(Integer.MAX_VALUE);
try (DWARFProgram dwarfProg = new DWARFProgram(currentProgram, importOptions, monitor)) {
BuiltInDataTypeManager dtms = BuiltInDataTypeManager.getDataTypeManager();
DWARFParser dp = new DWARFParser(dwarfProg, dtms, monitor);
DWARFParser dp = new DWARFParser(dwarfProg, monitor);
DWARFImportSummary importSummary = dp.parse();
importSummary.logSummaryResults();
}

View file

@ -0,0 +1,47 @@
/* ###
* 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.
*/
// Assigns custom storage for params of a golang function to match golang's abi-internal
// register-based calling convention, or abi0 (all stack based) if abi-internal is not
// specified for the arch.
//@category Functions
//@menupath Tools.Fix Golang Function Param Storage
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.format.golang.GoFunctionFixup;
import ghidra.app.util.bin.format.golang.GoVer;
import ghidra.program.model.listing.Function;
public class FixupGolangFuncParamStorageScript extends GhidraScript {
@Override
protected void run() throws Exception {
Function func;
if (currentAddress == null || (func = getFunctionContaining(currentAddress)) == null) {
return;
}
GoVer goVersion = GoVer.fromProgramProperties(currentProgram);
if ( goVersion == GoVer.UNKNOWN ) {
List<GoVer> versions = List.of(GoVer.values());
goVersion =
askChoice("Golang Version", "What is the golang version?", versions, GoVer.UNKNOWN);
}
println("Fixing param storage for function %s@%s".formatted(func.getName(),
func.getEntryPoint()));
GoFunctionFixup.fixupFunction(func, goVersion);
}
}

View file

@ -82,6 +82,14 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
setDefaultEnablement(true);
}
@Override
public boolean getDefaultEnablement(Program program) {
if ("golang".equals(program.getCompilerSpec().getCompilerSpecID().toString())) {
return false;
}
return super.getDefaultEnablement(program);
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
dtmService = AutoAnalysisManager.getAnalysisManager(program).getDataTypeManagerService();

View file

@ -26,7 +26,6 @@ import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProvid
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.BuiltInDataTypeManager;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
@ -180,13 +179,14 @@ public class DWARFAnalyzer extends AbstractAnalyzer {
return false;
}
try (DWARFSectionProvider dsp =
DWARFSectionProviderFactory.createSectionProviderFor(program, monitor)) {
DWARFSectionProvider dsp =
DWARFSectionProviderFactory.createSectionProviderFor(program, monitor); // closed by DWARFProgram
if (dsp == null) {
Msg.info(this, "Unable to find DWARF information, skipping DWARF analysis");
return false;
}
try {
try (DWARFProgram prog = new DWARFProgram(program, importOptions, monitor, dsp)) {
if (prog.getRegisterMappings() == null && importOptions.isImportFuncs()) {
log.appendMsg(
@ -195,8 +195,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer {
"], function information may be incorrect / incomplete.");
}
DWARFParser dp =
new DWARFParser(prog, BuiltInDataTypeManager.getDataTypeManager(), monitor);
DWARFParser dp = new DWARFParser(prog, monitor);
DWARFImportSummary parseResults = dp.parse();
parseResults.logSummaryResults();
}

View file

@ -0,0 +1,369 @@
/* ###
* 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.plugin.core.analysis;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;
import generic.jar.ResourceFile;
import ghidra.app.services.*;
import ghidra.app.util.MemoryBlockUtils;
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.GoRttiContext;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.Structure;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
import ghidra.xml.XmlParseException;
import utilities.util.FileUtilities;
/**
* Analyzes Golang binaries for RTTI and function symbol information.
*/
public class GolangSymbolAnalyzer extends AbstractAnalyzer {
private final static String NAME = "Golang Symbol";
private final static String DESCRIPTION = """
Analyze Golang binaries for RTTI and function symbols.
'Apply Data Archives' and 'Shared Return Calls' analyzers should be disabled \
for best results.""";
private final static String ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME =
"ARTIFICIAL.runtime.zerobase";
private GolangAnalyzerOptions analyzerOptions = new GolangAnalyzerOptions();
public GolangSymbolAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
setPriority(AnalysisPriority.FORMAT_ANALYSIS.after().after());
setDefaultEnablement(true);
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
monitor.setMessage("Golang symbol analyzer");
try (GoRttiContext programContext = GoRttiContext.getContextFor(program, log)) {
if (programContext == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiContext");
return false;
}
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);
markupMiscInfoStructs(program);
markupWellknownSymbols(programContext);
fixupNoReturnFuncs(program);
setupProgramContext(programContext);
programContext.recoverDataTypes(monitor);
if (analyzerOptions.createBootstrapDatatypeArchive) {
createBootstrapGDT(programContext, program, monitor);
}
}
catch (IOException e) {
Msg.error(this, "Golang analysis failure", e);
}
return true;
}
@Override
public void registerOptions(Options options, Program program) {
options.registerOption(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME,
analyzerOptions.createBootstrapDatatypeArchive, null,
GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_DESC);
}
@Override
public void optionsChanged(Options options, Program program) {
analyzerOptions.createBootstrapDatatypeArchive =
options.getBoolean(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME,
analyzerOptions.createBootstrapDatatypeArchive);
}
private void markupWellknownSymbols(GoRttiContext programContext) 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);
}
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);
}
}
private void markupMiscInfoStructs(Program program) {
// this also adds "golang" info to program properties
ItemWithAddress<GoBuildInfo> wrappedBuildInfo = GoBuildInfo.findBuildInfo(program);
if (wrappedBuildInfo != null && program.getListing()
.isUndefined(wrappedBuildInfo.address(), wrappedBuildInfo.address())) {
// this will mostly be PE binaries that don't have Elf markup magic stuff
wrappedBuildInfo.item().markupProgram(program, wrappedBuildInfo.address());
}
ItemWithAddress<PEGoBuildId> wrappedPeBuildId = PEGoBuildId.findBuildId(program);
if (wrappedPeBuildId != null && program.getListing()
.isUndefined(wrappedPeBuildId.address(), wrappedPeBuildId.address())) {
// HACK to handle golang hack: check if a function symbol was laid down at the location
// of the buildId string. If true, convert it to a plain label
Symbol[] buildIdSymbols =
program.getSymbolTable().getSymbols(wrappedPeBuildId.address());
for (Symbol sym : buildIdSymbols) {
if (sym.getSymbolType() == SymbolType.FUNCTION) {
String symName = sym.getName();
sym.delete();
try {
program.getSymbolTable()
.createLabel(wrappedPeBuildId.address(), symName,
SourceType.IMPORTED);
}
catch (InvalidInputException e) {
// ignore
}
break;
}
}
wrappedPeBuildId.item().markupProgram(program, wrappedPeBuildId.address());
}
}
private void fixupNoReturnFuncs(Program program) {
Set<String> noreturnFuncnames = new HashSet<>();
try {
for (ResourceFile file : NonReturningFunctionNames.findDataFiles(program)) {
FileUtilities.getLines(file)
.stream()
.map(String::trim)
.filter(s -> !s.isBlank() && !s.startsWith("#"))
.forEach(noreturnFuncnames::add);
}
}
catch (IOException | XmlParseException e) {
Msg.error(this, "Failed to read Golang noreturn func data file", e);
}
int count = 0;
SymbolTable symbolTable = program.getSymbolTable();
for (Symbol symbol : symbolTable.getPrimarySymbolIterator(true)) {
String name = symbol.getName(false);
if (symbol.isExternal() /* typically not an issue with golang */
|| !noreturnFuncnames.contains(name)) {
continue;
}
Function functionAt = program.getFunctionManager().getFunctionAt(symbol.getAddress());
if (functionAt == null) {
continue;
}
if (!functionAt.hasNoReturn()) {
functionAt.setNoReturn(true);
program.getBookmarkManager()
.setBookmark(symbol.getAddress(), BookmarkType.ANALYSIS,
"Non-Returning Function", "Non-Returning Golang Function Identified");
count++;
}
}
Msg.info(this, "Marked %d golang funcs as NoReturn".formatted(count));
}
private Address createFakeContextMemory(Program program, long len) {
long offset_from_eom = 0x100_000;
Address max = program.getAddressFactory().getDefaultAddressSpace().getMaxAddress();
Address mbStart = max.subtract(offset_from_eom + len - 1);
MemoryBlock newMB =
MemoryBlockUtils.createUninitializedBlock(program, false, "ARTIFICAL_GOLANG_CONTEXT",
mbStart, len, "Artifical memory block created to hold golang context data types",
null, true, true, false, null);
return newMB.getStart();
}
private void setupProgramContext(GoRttiContext programContext) throws IOException {
Program program = programContext.getProgram();
GoRegisterInfo goRegInfo = GoRegisterInfoManager.getInstance()
.getRegisterInfoForLang(program.getLanguage(),
programContext.getGolangVersion());
MemoryBlock txtMemblock = program.getMemory().getBlock(".text");
if (txtMemblock != null && goRegInfo.getZeroRegister() != null) {
try {
program.getProgramContext()
.setValue(goRegInfo.getZeroRegister(), txtMemblock.getStart(),
txtMemblock.getEnd(), BigInteger.ZERO);
}
catch (ContextChangeException e) {
Msg.error(this, "Unexpected Error", e);
}
}
int alignment = programContext.getPtrSize();
long sizeNeeded = 0;
Symbol zerobase = SymbolUtilities.getUniqueSymbol(program, "runtime.zerobase");
long zerobaseSymbol = sizeNeeded;
sizeNeeded += zerobase == null
? NumericUtilities.getUnsignedAlignedValue(1 /* sizeof(byte) */, alignment)
: 0;
long gStructOffset = sizeNeeded;
Structure gStruct = programContext.getGhidraDataType("runtime.g", Structure.class);
sizeNeeded += gStruct != null
? NumericUtilities.getUnsignedAlignedValue(gStruct.getLength(), alignment)
: 0;
long mStructOffset = sizeNeeded;
Structure mStruct = programContext.getGhidraDataType("runtime.m", Structure.class);
sizeNeeded += mStruct != null
? NumericUtilities.getUnsignedAlignedValue(mStruct.getLength(), alignment)
: 0;
Address contextMemoryAddr = sizeNeeded > 0
? createFakeContextMemory(program, sizeNeeded)
: null;
if (zerobase == null) {
programContext.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");
Register currentGoroutineReg = goRegInfo.getCurrentGoroutineRegister();
if (currentGoroutineReg != null && txtMemblock != null) {
// currentGoroutineReg is set in a platform's arch-golang.register.info in
// the <current_goroutine> element for arch's that have a dedicated processor
// register that points at G
try {
program.getProgramContext()
.setValue(currentGoroutineReg, txtMemblock.getStart(),
txtMemblock.getEnd(), gAddr.getOffsetAsBigInteger());
}
catch (ContextChangeException e) {
Msg.error(this, "Unexpected Error", e);
}
}
}
if (mStruct != null) {
Address mAddr = contextMemoryAddr.add(mStructOffset);
programContext.markupAddressIfUndefined(mAddr, mStruct);
}
}
private void createBootstrapGDT(GoRttiContext programContext, Program program,
TaskMonitor monitor) throws IOException {
GoVer goVer = programContext.getGolangVersion();
String osName = GoRttiContext.getGolangOSString(program);
String gdtFilename =
GoRttiContext.getGDTFilename(goVer, programContext.getPtrSize(), osName);
gdtFilename =
gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis()));
File gdt = new File(System.getProperty("user.home"), gdtFilename);
programContext.exportTypesToGDT(gdt, monitor);
Msg.info(this, "Golang bootstrap GDT created: " + gdt);
}
@Override
public void analysisEnded(Program program) {
}
@Override
public boolean canAnalyze(Program program) {
return "golang".equals(
program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName());
}
@Override
public boolean getDefaultEnablement(Program program) {
return true;
}
private static Address getArtificalZerobaseAddress(Program program) {
Symbol zerobaseSym =
SymbolUtilities.getUniqueSymbol(program, ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME);
return zerobaseSym != null ? zerobaseSym.getAddress() : null;
}
/**
* Return the address of the golang zerobase symbol, or an artificial substitute.
* <p>
* The zerobase symbol is used as the location of parameters that are zero-length.
*
* @param prog
* @return
*/
public static Address getZerobaseAddress(Program prog) {
Symbol zerobaseSym = SymbolUtilities.getUniqueSymbol(prog, "runtime.zerobase");
Address zerobaseAddr = zerobaseSym != null
? zerobaseSym.getAddress()
: getArtificalZerobaseAddress(prog);
if (zerobaseAddr == null) {
zerobaseAddr = prog.getImageBase().getAddressSpace().getMinAddress(); // ICKY HACK
Msg.warn(GoFunctionFixup.class,
"Unable to find Golang runtime.zerobase, using " + zerobaseAddr);
}
return zerobaseAddr;
}
//--------------------------------------------------------------------------------------------
private static class GolangAnalyzerOptions {
static final String CREATE_BOOTSTRAP_GDT_OPTIONNAME = "Create Bootstrap GDT";
static final String CREATE_BOOTSTRAP_GDT_DESC = """
Creates a Ghidra data type archive that contains just the necessary \
data types to parse other golang binaries. \
DWARF data is needed for this to succeed. \
The new GDT file will be placed in the user's home directory and will \
be called golang_MajorVer.MinorVer_XXbit_osname.NNNNNNNNN.gdt, where NNNNNN \
is a timestamp.""";
boolean createBootstrapDatatypeArchive;
}
}

View file

@ -15,11 +15,12 @@
*/
package ghidra.app.plugin.core.analysis;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.xml.sax.SAXException;
import generic.constraint.DecisionSet;
@ -30,7 +31,7 @@ import ghidra.util.Msg;
import ghidra.util.constraint.ProgramDecisionTree;
import ghidra.xml.XmlParseException;
class NonReturningFunctionNames {
public class NonReturningFunctionNames {
private static final String CONSTRAINED_FILENAME_PROPERTY = "functionNamesFile";
private static final String DATA_DIR = "data";

View file

@ -20,8 +20,9 @@ import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.lang.GhidraLanguagePropertyKeys;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -86,6 +87,9 @@ public class SharedReturnAnalyzer extends AbstractAnalyzer {
boolean sharedReturnEnabled = language.getPropertyAsBoolean(
GhidraLanguagePropertyKeys.ENABLE_SHARED_RETURN_ANALYSIS, true);
if ("golang".equals(program.getCompilerSpec().getCompilerSpecID().toString())) {
sharedReturnEnabled = false;
}
// If the language (in the .pspec file) overrides this setting, use that value
boolean contiguousFunctionsEnabled = language.getPropertyAsBoolean(

View file

@ -60,7 +60,7 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
"a newly discovered starting symbol, provided the user hasn't manually moved.";
private static final String DEFAULT_STARTING_SYMBOLS =
"main, WinMain, libc_start_main, WinMainStartup, start, entry";
"main, WinMain, libc_start_main, WinMainStartup, start, entry, main.main";
public static enum StartLocationType {
LOWEST_ADDRESS("Lowest Address"),

View file

@ -98,9 +98,9 @@ public class BinaryReader {
T get(InputStream is) throws IOException;
}
private final ByteProvider provider;
private DataConverter converter;
private long currentIndex;
protected final ByteProvider provider;
protected DataConverter converter;
protected long currentIndex;
/**
* Constructs a reader using the given ByteProvider and endian-order.

View file

@ -151,7 +151,7 @@ public class MemoryByteProvider implements ByteProvider {
* @param maxAddress the highest address accessible by this provider (inclusive), or null
* if there is no memory
*/
private MemoryByteProvider(Memory memory, Address baseAddress, Address maxAddress) {
public MemoryByteProvider(Memory memory, Address baseAddress, Address maxAddress) {
this.memory = memory;
this.baseAddress = baseAddress;
this.maxOffset = maxAddress != null

View file

@ -17,9 +17,8 @@ package ghidra.app.util.bin.format.dwarf4;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_formal_parameter;
import java.util.*;
import java.io.IOException;
import java.util.*;
import org.apache.commons.lang3.ArrayUtils;
@ -301,6 +300,32 @@ public class DIEAggregate {
return null;
}
/**
* Return an attribute that is present in this {@link DIEAggregate}, or in any of its
* direct children (of a specific type)
*
* @param <T>
* @param attribute the attribute to find
* @param childTag the type of children to search
* @param clazz type of the attribute to return
* @return attribute value, or null if not found
*/
public <T extends DWARFAttributeValue> T findAttributeInChildren(int attribute, int childTag,
Class<T> clazz) {
T attributeValue = getAttribute(attribute, clazz);
if (attributeValue != null) {
return attributeValue;
}
for (DebugInfoEntry childDIE : getChildren(childTag)) {
DIEAggregate childDIEA = getProgram().getAggregate(childDIE);
attributeValue = childDIEA.getAttribute(attribute, clazz);
if (attributeValue != null) {
return attributeValue;
}
}
return null;
}
/**
* Finds a {@link DWARFAttributeValue attribute} with a matching {@link DWARFAttribute} type
* <p>
@ -620,11 +645,13 @@ public class DIEAggregate {
* Blob attributes are treated as a single location record for the current CU, using the
* blob bytes as the DWARF expression of the location record.
* <p>
* @param attribute
* @return
* @param attribute the attribute to evaluate
* @param range the address range the location covers (may be discarded if the attribute
* value is a location list with its own range values)
* @return list of locations, empty if missing, never null
* @throws IOException
*/
public List<DWARFLocation> getAsLocation(int attribute) throws IOException {
public List<DWARFLocation> getAsLocation(int attribute, DWARFRange range) throws IOException {
AttrInfo attrInfo = findAttribute(attribute);
if (attrInfo == null) {
return List.of();
@ -633,7 +660,7 @@ public class DIEAggregate {
return readDebugLocList(dnum.getUnsignedValue());
}
else if (attrInfo.attr instanceof DWARFBlobAttribute dblob) {
return _exprBytesAsLocation(dblob);
return _exprBytesAsLocation(dblob, range);
}
else {
throw new UnsupportedOperationException(
@ -727,22 +754,8 @@ public class DIEAggregate {
return results;
}
private List<DWARFLocation> _exprBytesAsLocation(DWARFBlobAttribute attr) {
List<DWARFLocation> list = new ArrayList<>(1);
Number highPc = getCompilationUnit().getCompileUnit().getHighPC();
Number lowPc = getCompilationUnit().getCompileUnit().getLowPC();
if (highPc == null) {
// a DW_AT_high_pc is not required
// in this case presumably we don't have to choose from a location list based on range
// Make a 1-byte range
highPc = lowPc;
}
// If there is no low either, assume we don't need a range, and make a (0,0) range
DWARFRange range = (lowPc == null) ? new DWARFRange(0, 0)
: new DWARFRange(lowPc.longValue(), highPc.longValue());
list.add(new DWARFLocation(range, attr.getBytes()));
return list;
private List<DWARFLocation> _exprBytesAsLocation(DWARFBlobAttribute attr, DWARFRange range) {
return List.of(new DWARFLocation(range, attr.getBytes()));
}
/**
@ -997,8 +1010,9 @@ public class DIEAggregate {
}
}
if ( !params.isEmpty() ) {
Msg.warn(this, "Extra params in concrete DIE instance: " + params);
Msg.warn(this, this.toString());
//Msg.warn(this, "Extra params in concrete DIE instance: " + params);
//Msg.warn(this, this.toString());
newParams.addAll(params);
}
params = newParams;
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.app.util.bin.format.dwarf4;
import java.util.List;
public class DWARFLocation {
private DWARFRange addressRange;
private byte[] location;
@ -37,6 +39,39 @@ public class DWARFLocation {
return this.location;
}
/**
* Get the location that corresponds to the entry point of the function If
* there is only a single location, assume it applies to whole function
*
* @param locList
* @param funcAddr
* @return the byte array corresponding to the location expression
*/
public static DWARFLocation getTopLocation(List<DWARFLocation> locList, long funcAddr) {
if (locList.size() == 1) {
return locList.get(0);
}
for (DWARFLocation loc : locList) {
if (loc.getRange().getFrom() == funcAddr) {
return loc;
}
}
return null;
}
public static DWARFLocation getEntryLocation(List<DWARFLocation> locList, long funcAddr) {
for (DWARFLocation loc : locList) {
if (loc.getRange().getFrom() == funcAddr) {
return loc;
}
}
return null;
}
public static DWARFLocation getFirstLocation(List<DWARFLocation> locList) {
return !locList.isEmpty() ? locList.get(0) : null;
}
/*
* I know we frown on keeping large chunks of code around that have been commented
* out, but...

View file

@ -19,6 +19,7 @@ package ghidra.app.util.bin.format.dwarf4;
* Holds the start (inclusive) and end (exclusive) addresses of a range.
*/
public class DWARFRange implements Comparable<DWARFRange> {
public static final DWARFRange EMPTY = new DWARFRange(0, 1);
private final long start;
private final long end;

View file

@ -23,6 +23,8 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import generic.jar.ResourceFile;
import ghidra.app.cmd.comments.AppendCommentCmd;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeValue;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute;
@ -30,9 +32,13 @@ import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag;
import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.listing.Program;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.SymbolType;
import ghidra.util.Conv;
@ -451,6 +457,38 @@ public class DWARFUtil {
dtc.setComment(prev + description);
}
public static void appendComment(Program program, Address address, int commentType,
String prefix, String comment, String sep) {
if (comment == null || comment.isBlank()) {
return;
}
CodeUnit cu = getCodeUnitForComment(program, address);
if (cu != null) {
String existingComment = cu.getComment(commentType);
if (existingComment != null && existingComment.contains(comment)) {
// don't add same comment twice
return;
}
}
AppendCommentCmd cmd = new AppendCommentCmd(address, commentType,
Objects.requireNonNullElse(prefix, "") + comment, sep);
cmd.applyTo(program);
}
public static CodeUnit getCodeUnitForComment(Program program, Address address) {
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
if (cu == null) {
return null;
}
Address cuAddr = cu.getMinAddress();
if (cu instanceof Data && !address.equals(cuAddr)) {
Data data = (Data) cu;
return data.getPrimitiveAt((int) address.subtract(cuAddr));
}
return cu;
}
/**
* Read an offset value who's size depends on the DWARF format: 32 vs 64.
* <p>
@ -558,16 +596,41 @@ public class DWARFUtil {
// A DW_AT_object_pointer property in the parent function is an explict way of
// referencing the param that points to the object instance (ie. "this").
//
String paramName = paramDIEA.getName();
if (paramDIEA.getBool(DWARFAttribute.DW_AT_artificial, false) ||
"this".equals(paramDIEA.getName())) {
Function.THIS_PARAM_NAME.equals(paramName)) {
return true;
}
DIEAggregate funcDIEA = paramDIEA.getParent();
DWARFAttributeValue dwATObjectPointer =
paramDIEA.getParent().getAttribute(DWARFAttribute.DW_AT_object_pointer);
return dwATObjectPointer != null &&
dwATObjectPointer instanceof DWARFNumericAttribute dnum &&
paramDIEA.hasOffset(dnum.getUnsignedValue());
funcDIEA.getAttribute(DWARFAttribute.DW_AT_object_pointer);
if (dwATObjectPointer != null && dwATObjectPointer instanceof DWARFNumericAttribute dnum &&
paramDIEA.hasOffset(dnum.getUnsignedValue())) {
return true;
}
// If the variable is not named, check to see if the parent of the function
// is a struct/class, and the parameter points to it
DIEAggregate classDIEA = funcDIEA.getParent();
if (paramName == null && classDIEA != null && classDIEA.isStructureType()) {
// Check to see if the parent data type equals the parameters' data type
return isPointerTo(classDIEA, paramDIEA.getTypeRef());
}
return false;
}
public static boolean isPointerTo(DIEAggregate targetDIEA, DIEAggregate testDIEA) {
return testDIEA != null && testDIEA.getTag() == DWARFTag.DW_TAG_pointer_type &&
testDIEA.getTypeRef() == targetDIEA;
}
public static boolean isPointerDataType(DIEAggregate diea) {
while (diea.getTag() == DWARFTag.DW_TAG_typedef) {
diea = diea.getTypeRef();
}
return diea.getTag() == DWARFTag.DW_TAG_pointer_type;
}
/**
@ -633,7 +696,7 @@ public class DWARFUtil {
// it writes a raw 64bit long (BE). The upper 32 bits (already read as length) will
// always be 0 since super-large binaries from that system weren't really possible.
// The next 32 bits will be the remainder of the value.
if ( reader.isBigEndian() && program.getDefaultPointerSize() == 8) {
if (reader.isBigEndian() && program.getDefaultPointerSize() == 8) {
length = reader.readNextUnsignedInt();
format = DWARFCompilationUnit.DWARF_64;
}
@ -649,4 +712,151 @@ public class DWARFUtil {
return new LengthResult(length, format);
}
/**
* Returns a file that has been referenced in the specified {@link Language language's}
* ldefs description via a
* <pre>&lt;external_name tool="<b>name</b>" name="<b>value</b>"/&gt;</pre>
* entry.
*
* @param lang {@link Language} to query
* @param name name of the option in the ldefs file
* @return file pointed to by the specified external_name tool entry
* @throws IOException
*/
public static ResourceFile getLanguageExternalFile(Language lang, String name)
throws IOException {
String filename = getLanguageExternalNameValue(lang, name);
return filename != null
? new ResourceFile(getLanguageDefinitionDirectory(lang), filename)
: null;
}
/**
* Returns the base directory of a language definition.
*
* @param lang {@link Language} to get base definition directory
* @return base directory for language definition files
* @throws IOException
*/
public static ResourceFile getLanguageDefinitionDirectory(Language lang) throws IOException {
LanguageDescription langDesc = lang.getLanguageDescription();
if (!(langDesc instanceof SleighLanguageDescription)) {
throw new IOException("Not a Sleigh Language: " + lang.getLanguageID());
}
SleighLanguageDescription sld = (SleighLanguageDescription) langDesc;
ResourceFile defsFile = sld.getDefsFile();
ResourceFile parentFile = defsFile.getParentFile();
return parentFile;
}
/**
* Returns a value specified in a {@link Language} definition via a
* <pre>&lt;external_name tool="<b>name</b>" name="<b>value</b>"/&gt;</pre>
* entry.
* <p>
* @param lang {@link Language} to query
* @param name name of the value
* @return String value
* @throws IOException
*/
public static String getLanguageExternalNameValue(Language lang, String name)
throws IOException {
LanguageDescription langDesc = lang.getLanguageDescription();
if (!(langDesc instanceof SleighLanguageDescription)) {
throw new IOException("Not a Sleigh Language: " + lang.getLanguageID());
}
List<String> values = langDesc.getExternalNames(name);
if (values == null || values.isEmpty()) {
return null;
}
if (values.size() > 1) {
throw new IOException(
String.format("Multiple external name values for %s found in language %s", name,
lang.getLanguageID()));
}
return values.get(0);
}
public static void packCompositeIfPossible(Composite original, DataTypeManager dtm) {
if (original.isZeroLength() || original.getNumComponents() == 0) {
// don't try to pack empty structs, this would throw off conflicthandler logic.
// also don't pack sized structs with no fields because when packed down to 0 bytes they
// cause errors when used as a param type
return;
}
Composite copy = (Composite) original.copy(dtm);
copy.setToDefaultPacking();
if (copy.getLength() != original.getLength()) {
// so far, typically because trailing zero-len flex array caused toolchain to
// bump struct size to next alignment value in a way that doesn't mesh with ghidra's
// logic
return; // fail
}
DataTypeComponent[] preComps = original.getDefinedComponents();
DataTypeComponent[] postComps = copy.getDefinedComponents();
if (preComps.length != postComps.length) {
return; // fail
}
for (int index = 0; index < preComps.length; index++) {
DataTypeComponent preDTC = preComps[index];
DataTypeComponent postDTC = postComps[index];
if (preDTC.getOffset() != postDTC.getOffset() ||
preDTC.getLength() != postDTC.getLength() ||
preDTC.isBitFieldComponent() != postDTC.isBitFieldComponent()) {
return; // fail
}
if (preDTC.isBitFieldComponent()) {
BitFieldDataType preBFDT = (BitFieldDataType) preDTC.getDataType();
BitFieldDataType postBFDT = (BitFieldDataType) postDTC.getDataType();
if (preBFDT.getBitOffset() != postBFDT.getBitOffset() ||
preBFDT.getBitSize() != postBFDT.getBitSize()) {
return; // fail
}
}
}
original.setToDefaultPacking();
}
public static List<Varnode> convertRegisterListToVarnodeStorage(List<Register> registers,
int dataTypeSize) {
List<Varnode> results = new ArrayList<>();
for (Register reg : registers) {
int regSize = reg.getMinimumByteSize();
int bytesUsed = Math.min(dataTypeSize, regSize);
Address addr = reg.getAddress();
if (reg.isBigEndian() && bytesUsed < regSize) {
addr = addr.add(regSize - bytesUsed);
}
results.add(new Varnode(addr, bytesUsed));
dataTypeSize -= bytesUsed;
}
return results;
}
public static boolean isEmptyArray(DataType dt) {
return dt instanceof Array array && array.getNumElements() == 0;
}
public static boolean isZeroByteDataType(DataType dt) {
if (VoidDataType.dataType.isEquivalent(dt)) {
return true;
}
if (!dt.isZeroLength() && dt instanceof Array) {
dt = DataTypeUtilities.getArrayBaseDataType((Array) dt);
}
return dt.isZeroLength();
}
public static boolean isVoid(DataType dt) {
return VoidDataType.dataType.isEquivalent(dt);
}
public static boolean isStackVarnode(Varnode varnode) {
return varnode != null &&
varnode.getAddress().getAddressSpace().getType() == AddressSpace.TYPE_STACK;
}
}

View file

@ -157,6 +157,16 @@ public final class DWARFAttribute {
public static final int DW_AT_GNU_pubtypes = 0x2135;
// end GNU DebugFission
// Golang
public static final int DW_AT_go_kind = 0x2900;
public static final int DW_AT_go_key = 0x2901;
public static final int DW_AT_go_elem = 0x2902;
public static final int DW_AT_go_embedded_field = 0x2903;
public static final int DW_AT_go_runtime_type = 0x2904;
public static final int DW_AT_go_package_name = 0x2905;
public static final int DW_AT_go_dict_index = 0x2906;
// end Golang
// Apple proprietary tags
public static final int DW_AT_APPLE_ptrauth_key = 0x3e04;
public static final int DW_AT_APPLE_ptrauth_address_discriminated = 0x3e05;

View file

@ -277,6 +277,8 @@ public class DWARFExpressionEvaluator {
else {
useUnknownRegister = true;
if (offset == 0) {
// if offset is 0, we can represent the location as a ghidra register location
// also implies a deref by the user of this location info
registerLoc = true;
}
}
@ -366,6 +368,7 @@ public class DWARFExpressionEvaluator {
case DW_OP_call_frame_cfa: {
push(registerMappings.getCallFrameCFA());
lastStackRelative = true;
break;
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.dwarf4.funcfixup;
import java.util.List;
import ghidra.app.util.bin.format.dwarf4.DWARFException;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.program.model.listing.Function;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
/**
* Interface for add-in logic to fix/modify/tweak DWARF functions before they are written
* to the Ghidra program.
* <p>
* Use {@code @ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_*)} to
* control the order of evaluation (higher numbers are run earlier).
* <p>
* Fixups are found using {@link ClassSearcher}, and their class names must end
* in "DWARFFunctionFixup" (see ExtensionPoint.manifest).
*/
public interface DWARFFunctionFixup extends ExtensionPoint {
public static final int PRIORITY_NORMAL_EARLY = 4000;
public static final int PRIORITY_NORMAL = 3000;
public static final int PRIORITY_NORMAL_LATE = 2000;
public static final int PRIORITY_LAST = 1000;
/**
* Called before a {@link DWARFFunction} is used to create a Ghidra Function.
* <p>
* If processing of the function should terminate (and the function be skipped), throw
* a {@link DWARFException}.
*
* @param dfunc {@link DWARFFunction} info read from DWARF about the function
* @param gfunc the Ghidra {@link Function} that will receive the DWARF information
*/
void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException;
/**
* Return a list of all current {@link DWARFFunctionFixup fixups} found in the classpath
* by ClassSearcher.
*
* @return list of all current fixups found in the classpath
*/
public static List<DWARFFunctionFixup> findFixups() {
return ClassSearcher.getInstances(DWARFFunctionFixup.class);
}
}

View file

@ -0,0 +1,42 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable;
import ghidra.program.model.listing.Function;
import ghidra.util.Msg;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Complains about function parameters that are marked as 'output' and don't have storage
* locations.
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE)
public class OutputParamCheckDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
// Complain about parameters that are marked as 'output' that haven't been handled by
// some other fixup, as we don't know what to do with them.
for (DWARFVariable dvar : dfunc.params) {
if (dvar.isOutputParameter && dvar.isMissingStorage()) {
Msg.warn(this, String.format("Unsupported output parameter for %s@%s: %s",
gfunc.getName(), gfunc.getEntryPoint(), dvar.name.getName()));
}
}
}
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.next.*;
import ghidra.program.model.listing.Function;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Ensures that function parameter names are unique and valid
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_LAST)
public class ParamNameDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
// Fix any dups among the parameters, to-be-added-local vars, and already present local vars
NameDeduper nameDeduper = new NameDeduper();
nameDeduper.addReservedNames(dfunc.getAllParamNames());
nameDeduper.addReservedNames(dfunc.getAllLocalVariableNames());
nameDeduper.addUsedNames(dfunc.getNonParamSymbolNames(gfunc));
for (DWARFVariable param : dfunc.params) {
String newName = nameDeduper.getUniqueName(param.name.getName());
if (newName != null) {
param.name = param.name.replaceName(newName, param.name.getOriginalName());
}
}
for (DWARFVariable localVar : dfunc.localVars) {
String newName = nameDeduper.getUniqueName(localVar.name.getName());
if (newName != null) {
localVar.name = localVar.name.replaceName(newName, localVar.name.getOriginalName());
}
}
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable;
import ghidra.program.model.listing.Function;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Steal storage location from parameters that are defined in a function's local variable
* area, because the storage location isn't the parameter location during call, but its location
* after being spilled.
*
* Create a local variable at that storage location.
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE)
public class ParamSpillDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
for (DWARFVariable param : dfunc.params) {
if (!param.isStackStorage()) {
continue;
}
long paramStackOffset = param.getStackOffset();
if (dfunc.isInLocalVarStorageArea(paramStackOffset) &&
dfunc.getLocalVarByOffset(paramStackOffset) == null) {
DWARFVariable paramSpill = DWARFVariable.fromDataType(dfunc, param.type);
String paramName = param.name.getName();
paramSpill.name =
param.name.replaceName(paramName + "-local", paramName + "-local");
paramSpill.setStackStorage(paramStackOffset);
dfunc.localVars.add(paramSpill);
param.clearStorage();
}
}
}
}

View file

@ -0,0 +1,43 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.DIEAggregate;
import ghidra.app.util.bin.format.dwarf4.DWARFException;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.program.model.listing.Function;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Prevent functions in a Rust compile unit from incorrectly being locked down to an empty signature.
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY)
public class RustDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException {
DIEAggregate diea = dfunc.diea;
int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage();
if (cuLang == DWARFSourceLanguage.DW_LANG_Rust && dfunc.params.isEmpty()) {
// if there were no defined parameters and the language is Rust, don't force an
// empty param signature. Rust language emit dwarf info without types (signatures)
// when used without -g.
throw new DWARFException("Rust empty param list" /* string doesnt matter */);
}
}
}

View file

@ -0,0 +1,45 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.DWARFException;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeValue;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.program.model.listing.Function;
import ghidra.util.Msg;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Check for errors and prevent probable bad function info from being locked in
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_LATE)
public class SanityCheckDWARFFunctionFixup implements DWARFFunctionFixup, DWARFAttributeValue {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException {
// if there were no defined parameters and we had problems decoding local variables,
// don't force the method to have an empty param signature because there are other
// issues afoot.
if (dfunc.params.isEmpty() && dfunc.localVarErrors) {
Msg.error(this,
String.format(
"Inconsistent function signature information, leaving undefined: %s@%s",
gfunc.getName(), gfunc.getEntryPoint()));
throw new DWARFException("Failed sanity check");
}
}
}

View file

@ -0,0 +1,55 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode;
import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable;
import ghidra.program.model.listing.Function;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Downgrades the function's signature commit mode to FORMAL-param-info-only if there are
* problems with param storage info.
* <p>
* Does not check the function's return value storage as that typically won't have information
* because DWARF does not specify that.
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_LAST - 1)
public class StorageVerificationDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
boolean storageIsGood = true;
for (DWARFVariable param : dfunc.params) {
if (param.isMissingStorage() && !param.isZeroByte()) {
storageIsGood = false;
break;
}
// downgrade to formal if the location info for the param starts somewhere inside the
// function instead of at the entry point
if (!param.isLocationValidOnEntry()) {
storageIsGood = false;
break;
}
}
if (!storageIsGood) {
dfunc.signatureCommitMode = CommitMode.FORMAL;
}
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.dwarf4.funcfixup;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction;
import ghidra.app.util.bin.format.dwarf4.next.DWARFVariable;
import ghidra.program.model.data.GenericCallingConvention;
import ghidra.program.model.listing.Function;
import ghidra.util.Msg;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* Update the function's calling convention (if unset) if there is a "this" parameter.
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL)
public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
if (dfunc.params.isEmpty() || dfunc.callingConvention != null) {
// if someone else set calling convention, don't override it
return;
}
DWARFVariable firstParam = dfunc.params.get(0);
if (firstParam.isThis) {
if (!firstParam.name.isAnon() &&
!Function.THIS_PARAM_NAME.equals(firstParam.name.getOriginalName())) {
Msg.error(this,
String.format("WARNING: Renaming %s to %s in function %s@%s",
firstParam.name.getName(), Function.THIS_PARAM_NAME, gfunc.getName(),
gfunc.getEntryPoint()));
}
firstParam.name =
firstParam.name.replaceName(Function.THIS_PARAM_NAME, Function.THIS_PARAM_NAME);
dfunc.callingConvention = GenericCallingConvention.thiscall;
}
}
}

View file

@ -0,0 +1,199 @@
/* ###
* 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.dwarf4.next;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.*;
/**
* Logic to test if a Data instance is replaceable with a data type.
*/
public class DWARFDataInstanceHelper {
private Program program;
private Listing listing;
public DWARFDataInstanceHelper(Program program) {
this.program = program;
this.listing = program.getListing();
}
private boolean isArrayDataTypeCompatibleWithExistingData(Array arrayDT, Data existingData) {
DataType existingDataDT = existingData.getBaseDataType();
if (existingDataDT.isEquivalent(arrayDT)) {
return true;
}
DataType elementDT = arrayDT.getDataType();
if (elementDT instanceof TypeDef typedef) {
elementDT = typedef.getBaseDataType();
}
DataType existingElementDT = existingDataDT instanceof Array existingArrayDT
? existingArrayDT.getDataType()
: null;
if (elementDT instanceof CharDataType && existingDataDT instanceof StringDataType) {
// hack to allow a char array to overwrite a string in memory
existingElementDT = elementDT;
}
if (existingElementDT instanceof TypeDef typedef) {
existingElementDT = typedef.getBaseDataType();
}
if (existingDataDT instanceof Array || existingDataDT instanceof StringDataType) {
if (!existingElementDT.isEquivalent(elementDT)) {
return false;
}
if (arrayDT.getLength() <= existingData.getLength()) {
// if proposed array is smaller than in-memory array: ok
return true;
}
// if proposed array is longer than in-memory array, check if there is only
// undefined data following the in-memory array
return hasTrailingUndefined(existingData, arrayDT);
}
// existing data wasn't an array, test each location the proposed array would overwrite
Address address = existingData.getAddress();
for (int i = 0; i < arrayDT.getNumElements(); i++) {
Address elementAddress = address.add(arrayDT.getElementLength() * i);
Data data = listing.getDataAt(elementAddress);
if (data != null && !isDataTypeCompatibleWithExistingData(elementDT, data)) {
return false;
}
}
return true;
}
private boolean hasTrailingUndefined(Data data, DataType replacementDT) {
Address address = data.getAddress();
return DataUtilities.isUndefinedRange(program, address.add(data.getLength()),
address.add(replacementDT.getLength() - 1));
}
private boolean isStructDataTypeCompatibleWithExistingData(Structure structDT,
Data existingData) {
DataType existingDataDT = existingData.getBaseDataType();
if (existingDataDT instanceof Structure) {
return existingDataDT.isEquivalent(structDT);
}
// existing data wasn't a structure, test each location the proposed structure would overwrite
Address address = existingData.getAddress();
for (DataTypeComponent dtc : structDT.getDefinedComponents()) {
Address memberAddress = address.add(dtc.getOffset());
Data data = listing.getDataAt(memberAddress);
if (data != null && !isDataTypeCompatibleWithExistingData(dtc.getDataType(), data)) {
return false;
}
}
return true;
}
private boolean isPointerDataTypeCompatibleWithExistingData(Pointer pdt, Data existingData) {
DataType existingDT = existingData.getBaseDataType();
// allow 'upgrading' an integer type to a pointer
boolean isRightType =
(existingDT instanceof Pointer) || (existingDT instanceof AbstractIntegerDataType);
return isRightType && existingDT.getLength() == pdt.getLength();
}
private boolean isSimpleDataTypeCompatibleWithExistingData(DataType simpleDT,
Data existingData) {
// dataType will only be a base data type, not a typedef
DataType existingDT = existingData.getBaseDataType();
if (simpleDT instanceof CharDataType && existingDT instanceof StringDataType) {
// char overwriting a string
return true;
}
if (!simpleDT.getClass().isInstance(existingDT)) {
return false;
}
int dataTypeLen = simpleDT.getLength();
if (dataTypeLen > 0 && dataTypeLen != existingData.getLength()) {
return false;
}
return true;
}
private boolean isEnumDataTypeCompatibleWithExistingData(Enum enumDT, Data existingData) {
// This is a very fuzzy check to see if the value located at address is compatible.
// Match if its an enum or integer with correct size. The details about enum
// members are ignored.
DataType existingDT = existingData.getBaseDataType();
if (!(existingDT instanceof Enum || existingDT instanceof AbstractIntegerDataType)) {
return false;
}
if (existingDT instanceof BooleanDataType) {
return false;
}
if (existingDT.getLength() != enumDT.getLength()) {
return false;
}
return true;
}
private boolean isDataTypeCompatibleWithExistingData(DataType dataType, Data existingData) {
if (existingData == null || !existingData.isDefined()) {
return true;
}
if (dataType instanceof Array) {
return isArrayDataTypeCompatibleWithExistingData((Array) dataType, existingData);
}
if (dataType instanceof Pointer) {
return isPointerDataTypeCompatibleWithExistingData((Pointer) dataType, existingData);
}
if (dataType instanceof Structure) {
return isStructDataTypeCompatibleWithExistingData((Structure) dataType, existingData);
}
if (dataType instanceof TypeDef) {
return isDataTypeCompatibleWithExistingData(((TypeDef) dataType).getBaseDataType(),
existingData);
}
if (dataType instanceof Enum) {
return isEnumDataTypeCompatibleWithExistingData((Enum) dataType, existingData);
}
if (dataType instanceof AbstractIntegerDataType ||
dataType instanceof AbstractFloatDataType || dataType instanceof StringDataType ||
dataType instanceof WideCharDataType || dataType instanceof WideChar16DataType ||
dataType instanceof WideChar32DataType) {
return isSimpleDataTypeCompatibleWithExistingData(dataType, existingData);
}
return false;
}
public boolean isDataTypeCompatibleWithAddress(DataType dataType, Address address) {
if (DataUtilities.isUndefinedRange(program, address,
address.add(dataType.getLength() - 1))) {
return true;
}
Data data = listing.getDataAt(address);
return data == null || isDataTypeCompatibleWithExistingData(dataType, data);
}
}

View file

@ -55,9 +55,9 @@ import ghidra.util.SystemUtilities;
* structures.
*
*/
class DWARFDataTypeConflictHandler extends DataTypeConflictHandler {
public class DWARFDataTypeConflictHandler extends DataTypeConflictHandler {
static final DWARFDataTypeConflictHandler INSTANCE = new DWARFDataTypeConflictHandler();
public static final DWARFDataTypeConflictHandler INSTANCE = new DWARFDataTypeConflictHandler();
private DWARFDataTypeConflictHandler() {
// do not create instances of this class

View file

@ -27,8 +27,9 @@ import org.apache.commons.lang3.StringUtils;
import ghidra.app.util.DataTypeNamingUtil;
import ghidra.app.util.bin.format.dwarf4.*;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf4.encoding.*;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFEndianity;
import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.golang.rtti.types.GoKind;
import ghidra.program.database.DatabaseObject;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.data.*;
@ -87,12 +88,11 @@ public class DWARFDataTypeImporter {
* @param dwarfDTM {@link DWARFDataTypeManager} helper
* @param importOptions {@link DWARFImportOptions} control optional features during import
*/
public DWARFDataTypeImporter(DWARFProgram prog, DWARFDataTypeManager dwarfDTM,
DWARFImportOptions importOptions) {
public DWARFDataTypeImporter(DWARFProgram prog, DWARFDataTypeManager dwarfDTM) {
this.prog = prog;
this.dataTypeManager = prog.getGhidraProgram().getDataTypeManager();
this.dwarfDTM = dwarfDTM;
this.importOptions = importOptions;
this.importOptions = prog.getImportOptions();
this.voidDDT = new DWARFDataType(dwarfDTM.getVoidType(),
DWARFNameInfo.fromDataType(dwarfDTM.getVoidType()), -1);
}
@ -109,7 +109,7 @@ public class DWARFDataTypeImporter {
//Msg.warn(this, "Allowed recursive loop in datatype detected at " + id);
break;
case 3:
Msg.error(this, "Recursive loop in datatype detected at " + id);
Msg.error(this, "Recursive loop in datatype detected at " + Long.toHexString(id));
return false;
}
recursionTrackingOffsetToLoopCount.put(id, count);
@ -155,39 +155,39 @@ public class DWARFDataTypeImporter {
// Fall back to creating a new Ghidra DataType from the info in the DIEA.
switch (diea.getTag()) {
case DWARFTag.DW_TAG_pointer_type:
case DWARFTag.DW_TAG_reference_type:
case DWARFTag.DW_TAG_rvalue_reference_type:
case DW_TAG_pointer_type:
case DW_TAG_reference_type:
case DW_TAG_rvalue_reference_type:
result = makeDataTypeForPointer(diea);
break;
case DWARFTag.DW_TAG_ptr_to_member_type:
case DW_TAG_ptr_to_member_type:
result = makeDataTypeForPtrToMemberType(diea);
break;
case DWARFTag.DW_TAG_base_type:
case DW_TAG_base_type:
result = makeDataTypeForBaseType(diea);
break;
case DWARFTag.DW_TAG_typedef:
case DW_TAG_typedef:
result = makeDataTypeForTypedef(diea);
break;
case DWARFTag.DW_TAG_unspecified_type:
case DW_TAG_unspecified_type:
result = makeDataTypeForUnspecifiedType(diea);
break;
case DWARFTag.DW_TAG_const_type:
case DWARFTag.DW_TAG_volatile_type:
case DWARFTag.DW_TAG_restrict_type:
case DWARFTag.DW_TAG_shared_type:
case DWARFTag.DW_TAG_APPLE_ptrauth_type:
case DW_TAG_const_type:
case DW_TAG_volatile_type:
case DW_TAG_restrict_type:
case DW_TAG_shared_type:
case DW_TAG_APPLE_ptrauth_type:
result = makeDataTypeForConst(diea);
break;
case DWARFTag.DW_TAG_enumeration_type:
case DW_TAG_enumeration_type:
result = makeDataTypeForEnum(diea);
break;
case DWARFTag.DW_TAG_array_type:
case DW_TAG_array_type:
result = makeDataTypeForArray(diea);
break;
case DWARFTag.DW_TAG_structure_type:
case DWARFTag.DW_TAG_class_type:
case DWARFTag.DW_TAG_union_type:
case DW_TAG_structure_type:
case DW_TAG_class_type:
case DW_TAG_union_type:
result = makeDataTypeForStruct(diea);
// push partial datatype info into currentTypes mapping to handle
@ -197,10 +197,10 @@ public class DWARFDataTypeImporter {
finishStruct(diea, result);
break;
case DWARFTag.DW_TAG_subroutine_type:
case DW_TAG_subroutine_type:
result = makeDataTypeForFunctionDefinition(diea, true);
break;
case DWARFTag.DW_TAG_subprogram:
case DW_TAG_subprogram:
result = makeDataTypeForFunctionDefinition(diea, false);
break;
default:
@ -287,12 +287,23 @@ public class DWARFDataTypeImporter {
String paramName = paramDIEA.getName();
DWARFDataType paramDT = getDataType(paramDIEA.getTypeRef(), null);
if (paramDT == null || paramDT.dataType.getLength() <= 0) {
DataType dt = fixupDataTypeInconsistencies(paramDT);
if (dt == null && DWARFUtil.isPointerDataType(paramDIEA.getTypeRef())) {
// Hack to handle Golang self-referencing func defs.
Msg.error(this,
"Error resolving parameter data type, probable recursive definition, replacing with void*: " +
dni.getName());
Msg.debug(this, "Problem funcDef: " + diea.toString());
Msg.debug(this, "Problem param: " + paramDIEA);
dt = dwarfDTM.getPtrTo(dwarfDTM.getVoidType());
}
if (dt == null || dt.getLength() <= 0) {
Msg.error(this, "Bad function parameter type for " + dni.asCategoryPath());
return null;
}
ParameterDefinition pd = new ParameterDefinitionImpl(paramName, paramDT.dataType, null);
ParameterDefinition pd = new ParameterDefinitionImpl(paramName, dt, null);
params.add(pd);
foundThisParam |= DWARFUtil.isThisParam(paramDIEA);
@ -304,7 +315,7 @@ public class DWARFDataTypeImporter {
funcDef.setNoReturn(diea.getBool(DW_AT_noreturn, false));
funcDef.setArguments(params.toArray(new ParameterDefinition[params.size()]));
if (!diea.getChildren(DWARFTag.DW_TAG_unspecified_parameters).isEmpty()) {
if (!diea.getChildren(DW_TAG_unspecified_parameters).isEmpty()) {
funcDef.setVarArgs(true);
}
@ -329,7 +340,17 @@ public class DWARFDataTypeImporter {
updateMapping(origPD.getDataType(), newPD.getDataType());
}
return new DWARFDataType(funcDef, dni, diea.getOffset());
DataType dtToAdd = funcDef;
if (diea.hasAttribute(DW_AT_byte_size)) {
// if the funcdef has a bytesize attribute, we should convert this data type to a ptr
long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1);
if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) {
ptrSize = -1;// use default pointer size
}
dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize);
}
return new DWARFDataType(dtToAdd, dni, diea.getOffset());
}
/**
@ -357,12 +378,38 @@ public class DWARFDataTypeImporter {
"Warning: Base type bit size and bit offset not currently handled for data type %s, DIE %s"
.formatted(dni.toString(), diea.getHexOffset()));
}
boolean explictSize = false;
if (diea.hasAttribute(DW_AT_go_kind)) {
long goKindInt = diea.getLong(DW_AT_go_kind, 0);
GoKind kind = GoKind.parseByte((byte) goKindInt);
explictSize = isExplictSizedGolangType(kind);
}
DataType dt =
dwarfDTM.getBaseType(dni.getOriginalName(), dwarfSize, dwarfEncoding, isBigEndian);
DataType dt = dwarfDTM.getBaseType(dni.getOriginalName(), dwarfSize, dwarfEncoding,
isBigEndian, explictSize);
return new DWARFDataType(dt, dni, diea.getOffset());
}
private boolean isExplictSizedGolangType(GoKind kind) {
switch (kind) {
case Int8:
case Int16:
case Int32:
case Int64:
case Uint8:
case Uint16:
case Uint32:
case Uint64:
case Float32:
case Float64:
case Complex64:
case Complex128:
return true;
default:
return false;
}
}
/**
* Simple passthru, returns whatever type this "const" modifier applies to.
* <p>
@ -396,7 +443,7 @@ public class DWARFDataTypeImporter {
private DWARFDataType makeDataTypeForEnum(DIEAggregate diea) {
DWARFNameInfo dni = prog.getName(diea);
int enumSize = (int) diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, -1);
int enumSize = (int) diea.getUnsignedLong(DW_AT_byte_size, -1);
// in addition to byte_size, enums can have encoding (signed/unsigned) and a basetype, which
// itself might have a signed/unsigned encoding.
// Which attributes are present varies wildly between versions and vendors, so seems
@ -435,12 +482,12 @@ public class DWARFDataTypeImporter {
private void populateStubEnum(Enum enumDT, DIEAggregate diea, boolean defaultSignedness) {
// NOTE: gcc tends to emit values without an explicit signedness. The caller
// can specify a default signedness, but this should probably always be unsigned.
for (DebugInfoEntry childEntry : diea.getChildren(DWARFTag.DW_TAG_enumerator)) {
for (DebugInfoEntry childEntry : diea.getChildren(DW_TAG_enumerator)) {
DIEAggregate childDIEA = prog.getAggregate(childEntry);
String valueName = childDIEA.getName();
DWARFNumericAttribute enumValAttr = childDIEA
.getAttribute(DWARFAttribute.DW_AT_const_value, DWARFNumericAttribute.class);
.getAttribute(DW_AT_const_value, DWARFNumericAttribute.class);
if (enumValAttr != null) {
long enumVal = enumValAttr.getValueWithSignednessHint(defaultSignedness);
@ -531,15 +578,15 @@ public class DWARFDataTypeImporter {
DWARFNameInfo dni = prog.getName(diea);
long structSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, 0);
long structSize = diea.getUnsignedLong(DW_AT_byte_size, 0);
long origStructSize = structSize;
if (isStructTooBigForGhidra(structSize)) {
Msg.error(this, "Large DWARF structure encountered, substituting empty struct for " +
dni + ", size: " + Long.toString(structSize) + " at DIE " + diea.getHexOffset());
structSize = 0;
}
boolean isUnion = diea.getTag() == DWARFTag.DW_TAG_union_type;
boolean isDecl = diea.getBool(DWARFAttribute.DW_AT_declaration, false);
boolean isUnion = diea.getTag() == DW_TAG_union_type;
boolean isDecl = diea.getBool(DW_AT_declaration, false);
DataType struct =
isUnion ? new UnionDataType(dni.getParentCP(), dni.getName(), dataTypeManager)
@ -617,19 +664,19 @@ public class DWARFDataTypeImporter {
*/
private void populateStubUnion(DWARFDataType ddt, DIEAggregate diea)
throws IOException, DWARFExpressionException {
long unionSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, -1);
long unionSize = diea.getUnsignedLong(DW_AT_byte_size, -1);
UnionDataType union = (UnionDataType) ddt.dataType;
for (DebugInfoEntry childEntry : diea.getChildren(DWARFTag.DW_TAG_member)) {
for (DebugInfoEntry childEntry : diea.getChildren(DW_TAG_member)) {
DIEAggregate childDIEA = prog.getAggregate(childEntry);
// skip static member vars as they do not have storage in the structure
// C does not allow static member vars in unions
if (childDIEA.hasAttribute(DWARFAttribute.DW_AT_external)) {
if (childDIEA.hasAttribute(DW_AT_external)) {
continue;
}
int bitSize = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_size, -1);
int bitSize = childDIEA.parseInt(DW_AT_bit_size, -1);
boolean isBitField = bitSize != -1;
String memberName = childDIEA.getName();
@ -644,13 +691,14 @@ public class DWARFDataTypeImporter {
continue;
}
DataType dt = fixupDataTypeInconsistencies(childDT);
String memberComment = null;
if (childDT.dataType instanceof Dynamic ||
childDT.dataType instanceof FactoryDataType) {
memberComment = "Unsupported dynamic size data type: " + childDT.dataType;
childDT.dataType = Undefined.getUndefinedDataType(1);
if (dt instanceof Dynamic ||
dt instanceof FactoryDataType) {
memberComment = "Unsupported dynamic size data type: " + dt;
dt = Undefined.getUndefinedDataType(1);
}
int dtLen = childDT.dataType.getLength();
int dtLen = dt.getLength();
if (unionSize != -1 && !isBitField && dtLen > unionSize) {
// if we can, ensure that the member being added to the union isn't larger
// than what DWARF specifies.
@ -658,23 +706,23 @@ public class DWARFDataTypeImporter {
if (dtLen > 1) {
// replace problematic datatype with 1 byte undefined placeholder
memberComment =
"Data type larger than union's declared size: " + childDT.dataType;
childDT.dataType = Undefined.getUndefinedDataType(1);
"Data type larger than union's declared size: " + dt;
dt = Undefined.getUndefinedDataType(1);
}
else {
// can't do any fancy replacement, just add warning to union's description
DWARFUtil.appendDescription(union, memberDesc("Missing member",
"data type larger than union", memberName, childDT, -1, bitSize, -1), "\n");
"data type larger than union", memberName, dt, -1, bitSize, -1), "\n");
continue;
}
}
if (isBitField) {
if (!BitFieldDataType.isValidBaseDataType(childDT.dataType)) {
if (!BitFieldDataType.isValidBaseDataType(dt)) {
DWARFUtil.appendDescription(union,
memberDesc("Missing member",
"Bad data type for bitfield: " + childDT.dataType.getName(), memberName,
childDT, -1, bitSize, -1),
"Bad data type for bitfield: " + dt.getName(), memberName,
dt, -1, bitSize, -1),
"\n");
continue;
}
@ -682,7 +730,7 @@ public class DWARFDataTypeImporter {
// DWARF has attributes (DWARFAttribute.DW_AT_data_bit_offset, DWARFAttribute.DW_AT_bit_offset)
// that specify the bit_offset of the field in the union. We don't use them.
try {
union.addBitField(childDT.dataType, bitSize, memberName, memberComment);
union.addBitField(dt, bitSize, memberName, memberComment);
}
catch (InvalidDataTypeException e) {
Msg.error(this,
@ -690,17 +738,17 @@ public class DWARFDataTypeImporter {
union.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() +
"], skipping: " + e.getMessage());
DWARFUtil.appendDescription(union, memberDesc("Missing member ",
"Failed to add bitfield", memberName, childDT, -1, bitSize, -1), "\n");
"Failed to add bitfield", memberName, dt, -1, bitSize, -1), "\n");
}
}
else {
// just a normal field
try {
DataTypeComponent dataTypeComponent =
union.add(childDT.dataType, memberName, memberComment);
union.add(dt, memberName, memberComment);
// adding a member to a composite can cause a clone() of the datatype instance, so
// update the instance mapping to keep track of the new instance.
updateMapping(childDT.dataType, dataTypeComponent.getDataType());
updateMapping(dt, dataTypeComponent.getDataType());
}
catch (IllegalArgumentException exc) {
Msg.error(this,
@ -731,7 +779,7 @@ public class DWARFDataTypeImporter {
") is larger than DWARF value (" + unionSize + ")", "\n");
}
if (importOptions.isTryPackStructs()) {
packCompositeIfPossible(ddt);
DWARFUtil.packCompositeIfPossible((Composite) ddt.dataType, dataTypeManager);
}
}
@ -747,7 +795,7 @@ public class DWARFDataTypeImporter {
StructureDataType structure = (StructureDataType) ddt.dataType;
long structSize = diea.getUnsignedLong(DWARFAttribute.DW_AT_byte_size, 0);
long structSize = diea.getUnsignedLong(DW_AT_byte_size, 0);
if (isStructTooBigForGhidra(structSize)) {
return;
}
@ -756,58 +804,14 @@ public class DWARFDataTypeImporter {
// location can conflict with the first member field's offset.
// This means that member fields will be successfully added and the field
// that represents the base class will fail in these cases.
populateStubStruct_worker(ddt, structure, diea, DWARFTag.DW_TAG_member);
populateStubStruct_worker(ddt, structure, diea, DWARFTag.DW_TAG_inheritance);
populateStubStruct_worker(ddt, structure, diea, DW_TAG_member);
populateStubStruct_worker(ddt, structure, diea, DW_TAG_inheritance);
removeUneededStructMemberShrinkage(structure);
if (importOptions.isTryPackStructs()) {
packCompositeIfPossible(ddt);
DWARFUtil.packCompositeIfPossible((Composite) ddt.dataType, dataTypeManager);
}
}
private void packCompositeIfPossible(DWARFDataType ddt) {
Composite original = (Composite) ddt.dataType;
if (original.isZeroLength() || original.getNumComponents() == 0) {
// don't try to pack empty structs, this would throw off conflicthandler logic.
// also don't pack sized structs with no fields because when packed down to 0 bytes they
// cause errors when used as a param type
return;
}
Composite copy = (Composite) original.copy(dataTypeManager);
copy.setToDefaultPacking();
if (copy.getLength() != original.getLength()) {
// so far, typically because trailing zero-len flex array caused toolchain to
// bump struct size to next alignment value in a way that doesn't mesh with ghidra's
// logic
return; // fail
}
DataTypeComponent[] preComps = original.getDefinedComponents();
DataTypeComponent[] postComps = copy.getDefinedComponents();
if (preComps.length != postComps.length) {
return; // fail
}
for (int index = 0; index < preComps.length; index++) {
DataTypeComponent preDTC = preComps[index];
DataTypeComponent postDTC = postComps[index];
if (preDTC.getOffset() != postDTC.getOffset() ||
preDTC.getLength() != postDTC.getLength() ||
preDTC.isBitFieldComponent() != postDTC.isBitFieldComponent()) {
return; // fail
}
if (preDTC.isBitFieldComponent()) {
BitFieldDataType preBFDT = (BitFieldDataType) preDTC.getDataType();
BitFieldDataType postBFDT = (BitFieldDataType) postDTC.getDataType();
if (preBFDT.getBitOffset() != postBFDT.getBitOffset() ||
preBFDT.getBitSize() != postBFDT.getBitSize()) {
return; // fail
}
}
}
original.setToDefaultPacking();
}
/**
* Restore structure fields to their regular size (if there is room) to ensure
* future DataType equiv and comparisons are successful.
@ -867,11 +871,11 @@ public class DWARFDataTypeImporter {
DIEAggregate childDIEA = prog.getAggregate(childEntry);
// skip static member vars as they do not have storage in the structure
if (childDIEA.hasAttribute(DWARFAttribute.DW_AT_external)) {
if (childDIEA.hasAttribute(DW_AT_external)) {
continue;
}
int bitSize = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_size, -1);
int bitSize = childDIEA.parseInt(DW_AT_bit_size, -1);
boolean isBitField = bitSize != -1;
DWARFDataType childDT = getDataType(childDIEA.getTypeRef(), null);
@ -880,6 +884,7 @@ public class DWARFDataTypeImporter {
"Failed to get data type for struct field: " + childDIEA.getHexOffset());
continue;
}
DataType dt = fixupDataTypeInconsistencies(childDT);
String memberName = childDIEA.getName();
@ -888,8 +893,8 @@ public class DWARFDataTypeImporter {
if (memberName == null) {
// If the member is an inheritance type, then set the name
// to be the name of the data type
if (childDIEA.getTag() == DWARFTag.DW_TAG_inheritance) {
memberName = "super_" + childDT.dataType.getName();
if (childDIEA.getTag() == DW_TAG_inheritance) {
memberName = "super_" + dt.getName();
}
else {
memberName = "field_" + structure.getNumDefinedComponents();
@ -897,17 +902,16 @@ public class DWARFDataTypeImporter {
}
boolean hasMemberOffset =
childDIEA.hasAttribute(DWARFAttribute.DW_AT_data_member_location);
childDIEA.hasAttribute(DW_AT_data_member_location);
int memberOffset = 0;
if (hasMemberOffset) {
try {
memberOffset = childDIEA.parseDataMemberOffset(
DWARFAttribute.DW_AT_data_member_location, 0);
memberOffset = childDIEA.parseDataMemberOffset(DW_AT_data_member_location, 0);
}
catch (DWARFExpressionException e) {
DWARFUtil.appendDescription(structure, memberDesc("Missing member",
"failed to parse location", memberName, childDT, -1, bitSize, -1), "\n");
"failed to parse location", memberName, dt, -1, bitSize, -1), "\n");
continue;
}
}
@ -922,30 +926,29 @@ public class DWARFDataTypeImporter {
//}
if (isBitField) {
if (!BitFieldDataType.isValidBaseDataType(childDT.dataType)) {
if (!BitFieldDataType.isValidBaseDataType(dt)) {
DWARFUtil.appendDescription(structure,
memberDesc("Missing member",
"Bad data type for bitfield: " + childDT.dataType.getName(), memberName,
childDT, -1, bitSize, -1),
memberDesc("Missing member", "Bad data type for bitfield: " + dt.getName(),
memberName, dt, -1, bitSize, -1),
"\n");
continue;
}
int containerLen;
if (hasMemberOffset) {
int byteSize = childDIEA.parseInt(DWARFAttribute.DW_AT_byte_size, -1);
containerLen = byteSize <= 0 ? childDT.dataType.getLength() : byteSize;
int byteSize = childDIEA.parseInt(DW_AT_byte_size, -1);
containerLen = byteSize <= 0 ? dt.getLength() : byteSize;
}
else {
containerLen = structure.getLength();
}
int containerBitLen = containerLen * 8;
int bitOffset = childDIEA.parseInt(DWARFAttribute.DW_AT_data_bit_offset, -1);
int bitOffset = childDIEA.parseInt(DW_AT_data_bit_offset, -1);
int ghidraBitOffset;
if (bitOffset == -1) {
// try to fall back to previous dwarf version's bit_offset attribute that has slightly different info
bitOffset = childDIEA.parseInt(DWARFAttribute.DW_AT_bit_offset, -1);
bitOffset = childDIEA.parseInt(DW_AT_bit_offset, -1);
// convert DWARF bit offset value to Ghidra bit offset
ghidraBitOffset = containerBitLen - bitOffset - bitSize;
@ -961,7 +964,7 @@ public class DWARFDataTypeImporter {
if (bitOffset < 0 || ghidraBitOffset < 0 || ghidraBitOffset >= containerBitLen) {
DWARFUtil.appendDescription(structure, memberDesc("Missing member",
"bad bitOffset", memberName, childDT, memberOffset, bitSize, bitOffset),
"bad bitOffset", memberName, dt, memberOffset, bitSize, bitOffset),
"\n");
continue;
}
@ -970,7 +973,7 @@ public class DWARFDataTypeImporter {
// TODO: need safety checks here to make sure that using insertAt() doesn't
// modify the struct
structure.insertBitFieldAt(memberOffset, containerLen, ghidraBitOffset,
childDT.dataType, bitSize, memberName, null);
dt, bitSize, memberName, null);
}
catch (InvalidDataTypeException e) {
Msg.error(this,
@ -978,23 +981,23 @@ public class DWARFDataTypeImporter {
structure.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() +
"], skipping: " + e.getMessage());
DWARFUtil.appendDescription(structure,
memberDesc("Missing member ", "Failed to add bitfield", memberName, childDT,
memberDesc("Missing member ", "Failed to add bitfield", memberName, dt,
memberOffset, bitSize, bitOffset),
"\n");
}
}
else {
String memberComment = null;
boolean isDynamicSizedType = (childDT.dataType instanceof Dynamic ||
childDT.dataType instanceof FactoryDataType);
boolean isDynamicSizedType =
(dt instanceof Dynamic || dt instanceof FactoryDataType);
if (isDynamicSizedType) {
memberComment = "Unsupported dynamic size data type: " + childDT.dataType;
childDT.dataType = Undefined.getUndefinedDataType(1);
memberComment = "Unsupported dynamic size data type: " + dt;
dt = Undefined.getUndefinedDataType(1);
}
int childLength = getUnpaddedDataTypeLength(childDT.dataType);
int childLength = getUnpaddedDataTypeLength(dt);
if (memberOffset + childLength > structure.getLength()) {
DWARFUtil.appendDescription(structure, memberDesc("Missing member",
"exceeds parent struct len", memberName, childDT, memberOffset, -1, -1),
"exceeds parent struct len", memberName, dt, memberOffset, -1, -1),
"\n");
continue;
@ -1002,16 +1005,16 @@ public class DWARFDataTypeImporter {
try {
DataTypeComponent dtc;
if (DataTypeComponent.usesZeroLengthComponent(childDT.dataType)) {
if (DataTypeComponent.usesZeroLengthComponent(dt)) {
if (!isUndefinedOrZeroLenAtOffset(structure, memberOffset)) {
DWARFUtil.appendDescription(structure, memberDesc("Missing member",
"conflicting member at same offset", memberName, childDT,
"conflicting member at same offset", memberName, dt,
memberOffset, -1, -1), "\n");
continue;
}
// use insertAt for zero len members to allow multiple at same offset
dtc =
structure.insertAtOffset(memberOffset, childDT.dataType, 0, memberName,
structure.insertAtOffset(memberOffset, dt, 0, memberName,
memberComment);
}
else {
@ -1023,18 +1026,18 @@ public class DWARFDataTypeImporter {
DWARFUtil.appendDescription(structure,
memberDesc("Missing member",
"conflict with " + existingDTC.getFieldName(),
memberName, childDT, memberOffset, -1, -1),
memberName, dt, memberOffset, -1, -1),
"\n");
}
continue;
}
dtc = structure.replace(ordinalToReplace, childDT.dataType, childLength,
memberName, memberComment);
dtc = structure.replace(ordinalToReplace, dt, childLength, memberName,
memberComment);
}
// struct.replaceAtOffset() and insertAtOffset() clones the childDT, which will mess up our
// identity based mapping in currentImplDataTypeToDDT.
// Update the mapping to prevent that.
updateMapping(childDT.dataType, dtc.getDataType());
updateMapping(dt, dtc.getDataType());
}
catch (IllegalArgumentException exc) {
Msg.error(this,
@ -1042,7 +1045,7 @@ public class DWARFDataTypeImporter {
structure.getDataTypePath() + "[DWARF DIE " + diea.getHexOffset() +
"], skipping: " + exc.getMessage());
DWARFUtil.appendDescription(structure, memberDesc("Missing member ", "",
memberName, childDT, memberOffset, -1, -1), "\n");
memberName, dt, memberOffset, -1, -1), "\n");
}
}
}
@ -1074,14 +1077,25 @@ public class DWARFDataTypeImporter {
}
private static String memberDesc(String prefix, String errorStr, String memberName,
DWARFDataType ddt, int memberOffset, int bitSize, int bitOffset) {
DataType dt, int memberOffset, int bitSize, int bitOffset) {
return (!StringUtils.isBlank(prefix) ? prefix + " " : "") + memberName + " : " +
ddt.dataType.getName() + (bitSize != -1 ? ":" + bitSize : "") + " at offset " +
dt.getName() + (bitSize != -1 ? ":" + bitSize : "") + " at offset " +
(memberOffset != -1 ? "0x" + Long.toHexString(memberOffset) : "unknown") +
(bitOffset != -1 ? ":" + bitOffset : "") +
(!StringUtils.isBlank(errorStr) ? " [" + errorStr + "]" : "");
}
private DataType fixupDataTypeInconsistencies(DWARFDataType ddt) {
if (ddt == null) {
return null;
}
DataType result = ddt.dataType;
if (result instanceof FunctionDefinition) {
result = dwarfDTM.getPtrTo(result);
}
return result;
}
/**
* Creates a Ghidra {@link ArrayDataType}.
* <p>
@ -1102,6 +1116,14 @@ public class DWARFDataTypeImporter {
if (self != null) {
return self;
}
DataType elementDT = fixupDataTypeInconsistencies(elementType);
long explictArraySize = diea.getUnsignedLong(DW_AT_byte_size, -1);
if (elementType.dataType.isZeroLength() || explictArraySize == 0) {
// don't bother checking range info, we are going to force a zero-element array
DataType zeroLenArray = new ArrayDataType(elementDT, 0, -1, dataTypeManager);
return new DWARFDataType(zeroLenArray, null, diea.getOffset());
}
// Build a list of the defined dimensions for this array type.
// The first element in the DWARF dimension list would be where a wild-card (-1 length)
@ -1142,19 +1164,19 @@ public class DWARFDataTypeImporter {
}
else if (numElements > Integer.MAX_VALUE) {
Msg.error(this, "Bad value [" + numElements + "] for array's size in DIE: " +
diea.getOffset() + ", forcing to 1");
diea.getHexOffset() + ", forcing to 1");
numElements = 1;
}
dimensions.add((int) numElements);
}
DataType dt = elementType.dataType;
DataType dt = elementDT;
for (int i = dimensions.size() - 1; i >= 0; i--) {
int numElements = dimensions.get(i);
ArrayDataType subArray =
new ArrayDataType(dt, numElements, -1, dataTypeManager);
if (dt == elementType.dataType) {
if (dt == elementDT) {
updateMapping(dt, subArray.getDataType());
}
dt = subArray;
@ -1191,8 +1213,7 @@ public class DWARFDataTypeImporter {
throws IOException, DWARFExpressionException {
DWARFDataType refdDT = getDataType(diea.getTypeRef(), voidDDT);
int byteSize = diea.parseInt(DWARFAttribute.DW_AT_byte_size,
diea.getCompilationUnit().getPointerSize());
int byteSize = diea.parseInt(DW_AT_byte_size, diea.getCompilationUnit().getPointerSize());
// do a second query to see if there was a recursive loop in the call above back
// to this datatype that resulted in this datatype being created.
@ -1225,8 +1246,7 @@ public class DWARFDataTypeImporter {
return null;
}
int byteSize = diea.parseInt(DWARFAttribute.DW_AT_byte_size,
diea.getCompilationUnit().getPointerSize());
int byteSize = diea.parseInt(DW_AT_byte_size, diea.getCompilationUnit().getPointerSize());
DataType offsetType = dwarfDTM.getOffsetType(byteSize);
// create a typedef to the offsetType and put containing type and var type info in the typedef name.
@ -1282,6 +1302,12 @@ public class DWARFDataTypeImporter {
boolean typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict(
typedefDNI.asDataTypePath().getPath(), refdDT.dataType.getPathName());
if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT &&
ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) {
// hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare
typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict(
typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName());
}
if (typedefWithSameName) {
if (importOptions.isElideTypedefsWithSameName()) {
@ -1340,7 +1366,7 @@ public class DWARFDataTypeImporter {
@Override
public String toString() {
return dataType.getName() + " | " + (dni != null ? dni.toString() : "na") + " | " +
hexOffsets();
hexOffsets() + " | zerolen: " + dataType.isZeroLength();
}
public String hexOffsets() {

View file

@ -79,15 +79,13 @@ public class DWARFDataTypeManager {
* @param prog {@link DWARFProgram} that holds the Ghidra {@link Program} being imported.
* @param dataTypeManager {@link DataTypeManager} of the Ghidra Program.
* @param builtInDTM {@link DataTypeManager} with built-in data types.
* @param importSummary {@link DWARFImportSummary} where summary information will be stored
* during the import session.
*/
public DWARFDataTypeManager(DWARFProgram prog, DataTypeManager dataTypeManager,
DataTypeManager builtInDTM, DWARFImportSummary importSummary) {
public DWARFDataTypeManager(DWARFProgram prog, DataTypeManager dataTypeManager) {
this.prog = prog;
this.dataTypeManager = dataTypeManager;
this.builtInDTM = builtInDTM;
this.importSummary = importSummary;
this.builtInDTM = BuiltInDataTypeManager.getDataTypeManager();
this.importSummary = prog.getImportSummary();
this.importOptions = prog.getImportOptions();
initBaseDataTypes();
}
@ -122,8 +120,7 @@ public class DWARFDataTypeManager {
// This does slow us down a little bit but this makes the GUI responsive to the user.
Swing.runNow(Dummy.runnable());
DWARFDataTypeImporter ddtImporter =
new DWARFDataTypeImporter(prog, this, prog.getImportOptions());
DWARFDataTypeImporter ddtImporter = new DWARFDataTypeImporter(prog, this);
// Convert the DWARF DIE record into a Ghidra DataType (probably impls)
DWARFDataType pre = ddtImporter.getDataType(diea, null);
@ -255,6 +252,15 @@ public class DWARFDataTypeManager {
return null;
}
public DataType getDataTypeForVariable(DIEAggregate diea) {
DataType type = getDataType(diea, getVoidType());
if (type instanceof FunctionDefinition) {
type = getPtrTo(type);
}
return type;
}
/**
* Returns a pointer to the specified data type.
*
@ -265,6 +271,10 @@ public class DWARFDataTypeManager {
return dataTypeManager.getPointer(dt);
}
public DataType getPtrTo(DataType dt, int ptrSize) {
return dataTypeManager.getPointer(dt, ptrSize);
}
/**
* Iterate all {@link DataType}s that match the CategoryPath / name given
* in the {@link DataTypePath} parameter, including "conflict" datatypes
@ -372,10 +382,15 @@ public class DWARFDataTypeManager {
* @param dwarfSize
* @param dwarfEncoding
* @param isBigEndian
* @param isExplictSize boolean flag, if true the returned data type will not be linked to
* the dataOrganization's compiler specified data types (eg. if type is something like int32_t,
* the returned type should never change size, even if the dataOrg changes). If false,
* the returned type will be linked to the dataOrg's compiler specified data types if possible,
* except for data types that have a name that include a bitsize in the name, such as "int64_t".
* @return
*/
public DataType getBaseType(String name, int dwarfSize, int dwarfEncoding,
boolean isBigEndian) {
boolean isBigEndian, boolean isExplictSize) {
DataType dt = null;
String mangledName = null;
@ -398,11 +413,11 @@ public class DWARFDataTypeManager {
// may be duplicated across different float types. Lookup by name is preferred.
// May need to add name lookup capability to AbstractFloatDataType
case DWARFEncoding.DW_ATE_float -> AbstractFloatDataType.getFloatDataType(dwarfSize,
getCorrectDTMForFixedLengthTypes(name, dwarfSize));
getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize));
case DWARFEncoding.DW_ATE_signed -> AbstractIntegerDataType.getSignedDataType(dwarfSize,
getCorrectDTMForFixedLengthTypes(name, dwarfSize));
getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize));
case DWARFEncoding.DW_ATE_unsigned -> AbstractIntegerDataType.getUnsignedDataType(
dwarfSize, getCorrectDTMForFixedLengthTypes(name, dwarfSize));
dwarfSize, getCorrectDTMForFixedLengthTypes(name, dwarfSize, isExplictSize));
case DWARFEncoding.DW_ATE_signed_char -> baseDataTypeChar;
case DWARFEncoding.DW_ATE_unsigned_char -> baseDataTypeUchar;
case DWARFEncoding.DW_ATE_UTF -> findMatchingDataTypeBySize(baseDataTypeChars,
@ -430,16 +445,18 @@ public class DWARFDataTypeManager {
}
private DataTypeManager getCorrectDTMForFixedLengthTypes(String name, int dwarfSize) {
private DataTypeManager getCorrectDTMForFixedLengthTypes(String name, int dwarfSize,
boolean predeterminedHasExplictSize) {
// If the requested name of the base type appears to have a bitsize string
// embedded in it, this chunk of code will switch between using the normal DTM
// to using a null DTM to force the Abstract(Integer|Float)DataType helper method to
// create compiler independent data types that don't change size when the architecture is
// changed.
int typenameExplicitSize;
boolean usedFixedSizeType = importOptions.isSpecialCaseSizedBaseTypes() &&
boolean usedFixedSizeType = predeterminedHasExplictSize ||
(importOptions.isSpecialCaseSizedBaseTypes() &&
(typenameExplicitSize = getExplicitSizeFromTypeName(name)) != -1 &&
typenameExplicitSize / 8 == dwarfSize;
typenameExplicitSize / 8 == dwarfSize);
return usedFixedSizeType ? null : dataTypeManager;
}
@ -706,13 +723,14 @@ public class DWARFDataTypeManager {
*/
private FunctionDefinitionDataType createFunctionDefinitionDataType(DIEAggregate diea,
DWARFNameInfo dni) {
DataType returnDataType = getDataType(diea.getTypeRef(), baseDataTypeVoid);
DataType returnDataType = getDataTypeForVariable(diea.getTypeRef());
boolean foundThisParam = false;
List<ParameterDefinition> params = new ArrayList<>();
for (DIEAggregate paramDIEA : diea.getFunctionParamList()) {
String paramName = paramDIEA.getName();
DataType paramDT = getDataType(paramDIEA.getTypeRef(), null);
DataType paramDT = getDataTypeForVariable(paramDIEA.getTypeRef());
if (paramDT == null || paramDT.getLength() <= 0) {
Msg.error(this,
"Bad function parameter type for function " + dni.asCategoryPath() +
@ -777,4 +795,5 @@ public class DWARFDataTypeManager {
return -1;
}
}

View file

@ -0,0 +1,361 @@
/* ###
* 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.dwarf4.next;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.*;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_unspecified_parameters;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import java.io.IOException;
import ghidra.app.util.bin.format.dwarf4.*;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
/**
* Represents a function that was read from DWARF information.
*/
public class DWARFFunction {
public enum CommitMode {
SKIP, FORMAL, STORAGE,
}
public DIEAggregate diea;
public DWARFNameInfo name;
public Namespace namespace;
public Address address;
public Address highAddress;
public long frameBase; // TODO: change this to preserve the func's frameBase expr instead of value
public GenericCallingConvention callingConvention;
public PrototypeModel prototypeModel;
public DWARFVariable retval;
public List<DWARFVariable> params = new ArrayList<>();
public boolean varArg;
public List<DWARFVariable> localVars = new ArrayList<>();
// We keep track of local var errors here because local variables aren't added
// to the local's list if they are problematic
public boolean localVarErrors;
public CommitMode signatureCommitMode = CommitMode.STORAGE;
public boolean noReturn;
public DWARFSourceInfo sourceInfo;
public boolean isExternal;
/**
* Create a function instance from the information found in the specified DIEA.
*
* @param diea DW_TAG_subprogram {@link DIEAggregate}
* @return new {@link DWARFFunction}, or null if invalid DWARF information
* @throws IOException if error accessing attribute values
* @throws DWARFExpressionException if error accessing attribute values
*/
public static DWARFFunction read(DIEAggregate diea)
throws IOException, DWARFExpressionException {
if (isBadSubprogramDef(diea)) {
return null;
}
DWARFProgram prog = diea.getProgram();
DWARFDataTypeManager dwarfDTM = prog.getDwarfDTM();
Address funcAddr = prog.getCodeAddress(diea.getLowPC(0));
DWARFFunction dfunc = new DWARFFunction(diea, prog.getName(diea), funcAddr);
dfunc.namespace = dfunc.name.getParentNamespace(prog.getGhidraProgram());
dfunc.sourceInfo = DWARFSourceInfo.create(diea);
dfunc.highAddress =
diea.hasAttribute(DW_AT_high_pc) ? prog.getCodeAddress(diea.getHighPC()) : null;
// Check if the function is an external function
dfunc.isExternal = diea.getBool(DW_AT_external, false);
dfunc.noReturn = diea.getBool(DW_AT_noreturn, false);
// Retrieve the frame base if it exists
DWARFLocation frameLoc = null;
if (diea.hasAttribute(DW_AT_frame_base)) {
List<DWARFLocation> frameBase = diea.getAsLocation(DW_AT_frame_base, dfunc.getRange());
// get the framebase register, find where the frame is finally setup.
frameLoc = DWARFLocation.getTopLocation(frameBase, dfunc.address.getOffset());
if (frameLoc != null) {
dfunc.frameBase = (int) diea.evaluateLocation(frameLoc);
}
}
dfunc.retval =
DWARFVariable.fromDataType(dfunc, dwarfDTM.getDataTypeForVariable(diea.getTypeRef()));
int paramOrdinal = 0;
for (DIEAggregate paramDIEA : diea.getFunctionParamList()) {
DWARFVariable param = DWARFVariable.readParameter(paramDIEA, dfunc, paramOrdinal++);
dfunc.params.add(param);
}
dfunc.varArg = !diea.getChildren(DW_TAG_unspecified_parameters).isEmpty();
return dfunc;
}
private DWARFFunction(DIEAggregate diea, DWARFNameInfo dni, Address address) {
this.diea = diea;
this.name = dni;
this.address = address;
}
public DWARFProgram getProgram() {
return diea.getProgram();
}
public DWARFRange getRange() {
return new DWARFRange(address.getOffset(),
highAddress != null ? highAddress.getOffset() : address.getOffset() + 1);
}
public String getCallingConventionName() {
return prototypeModel != null
? prototypeModel.getName()
: callingConvention != null
? callingConvention.getDeclarationName()
: null;
}
/**
* Returns the DWARFVariable that starts at the specified stack offset.
*
* @param offset stack offset
* @return local variable that starts at offset, or null if not present
*/
public DWARFVariable getLocalVarByOffset(long offset) {
for (DWARFVariable localVar : localVars) {
if (localVar.isStackStorage() && localVar.getStackOffset() == offset) {
return localVar;
}
}
return null;
}
/**
* Returns true if the specified stack offset is within the function's local variable
* storage area.
*
* @param offset stack offset to test
* @return true if stack offset is within this function's local variable area
*/
public boolean isInLocalVarStorageArea(long offset) {
boolean paramsHavePositiveOffset = diea.getProgram().stackGrowsNegative();
return (paramsHavePositiveOffset && offset < 0) ||
(!paramsHavePositiveOffset && offset >= 0);
}
public boolean hasConflictWithParamStorage(DWARFVariable dvar) throws InvalidInputException {
if (dvar.lexicalOffset != 0) {
return false;
}
VariableStorage storage = dvar.getVariableStorage();
for (DWARFVariable param : params) {
VariableStorage paramStorage = param.getVariableStorage();
if (paramStorage.intersects(storage)) {
return true;
}
}
return false;
}
public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar, Function gfunc)
throws InvalidInputException {
VariableStorage newVarStorage = dvar.getVariableStorage();
for (Variable existingVar : gfunc.getAllVariables()) {
if (existingVar.getFirstUseOffset() == dvar.lexicalOffset &&
existingVar.getVariableStorage().intersects(newVarStorage)) {
if ((existingVar instanceof LocalVariable) &&
Undefined.isUndefined(existingVar.getDataType())) {
continue;
}
return true;
}
}
return false;
}
public List<String> getAllParamNames() {
return params.stream()
.filter(dvar -> !dvar.name.isAnon())
.map(dvar -> dvar.name.getName())
.collect(Collectors.toList());
}
public List<String> getAllLocalVariableNames() {
return localVars.stream()
.filter(dvar -> !dvar.name.isAnon())
.map(dvar -> dvar.name.getName())
.collect(Collectors.toList());
}
public List<String> getExistingLocalVariableNames(Function gfunc) {
return Arrays.stream(gfunc.getLocalVariables())
.filter(var -> var.getName() != null && !Undefined.isUndefined(var.getDataType()))
.map(var -> var.getName())
.collect(Collectors.toList());
}
public List<String> getNonParamSymbolNames(Function gfunc) {
SymbolIterator symbols = gfunc.getProgram().getSymbolTable().getSymbols(gfunc);
return StreamSupport.stream(symbols.spliterator(), false)
.filter(symbol -> symbol.getSymbolType() != SymbolType.PARAMETER)
.map(Symbol::getName)
.collect(Collectors.toList());
}
/**
* Returns this function's parameters as a list of {@link Parameter} instances.
*
* @param includeStorageDetail boolean flag, if true storage information will be included, if
* false, VariableStorage.UNASSIGNED_STORAGE will be used
* @param program Ghidra program that contains the parameter
* @return list of Parameters
* @throws InvalidInputException
*/
public List<Parameter> getParameters(boolean includeStorageDetail)
throws InvalidInputException {
List<Parameter> result = new ArrayList<>();
for (DWARFVariable dvar : params) {
result.add(dvar.asParameter(includeStorageDetail, getProgram().getGhidraProgram()));
}
return result;
}
/**
* Returns a {@link FunctionDefinition} that reflects this function's information.
*
* @param prog {@link DWARFProgram} that contains this function
* @return {@link FunctionDefinition} that reflects this function's information
*/
public FunctionDefinition asFuncDef() {
List<ParameterDefinition> funcDefParams = new ArrayList<>();
for (DWARFVariable param : params) {
funcDefParams.add(param.asParameterDef());
}
FunctionDefinitionDataType funcDef =
new FunctionDefinitionDataType(name.getParentCP(), name.getName(),
getProgram().getGhidraProgram().getDataTypeManager());
funcDef.setReturnType(retval.type);
funcDef.setArguments(funcDefParams.toArray(ParameterDefinition[]::new));
funcDef.setGenericCallingConvention(
Objects.requireNonNullElse(callingConvention, GenericCallingConvention.unknown));
funcDef.setVarArgs(varArg);
DWARFSourceInfo sourceInfo = null;
if (getProgram().getImportOptions().isOutputSourceLocationInfo() &&
(sourceInfo = DWARFSourceInfo.create(diea)) != null) {
funcDef.setComment(sourceInfo.getDescriptionStr());
}
return funcDef;
}
public void commitLocalVariable(DWARFVariable dvar, Function gfunc) {
VariableStorage varStorage = null;
try {
varStorage = dvar.getVariableStorage();
if (hasConflictWithParamStorage(dvar)) {
appendComment(gfunc.getEntryPoint(), CodeUnit.PLATE_COMMENT,
"Local variable %s[%s] conflicts with parameter, skipped.".formatted(
dvar.getDeclInfoString(), varStorage),
"\n");
return;
}
if (hasConflictWithExistingLocalVariableStorage(dvar, gfunc)) {
appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT,
"Local omitted variable %s[%s] scope starts here".formatted(
dvar.getDeclInfoString(), varStorage),
"; ");
return;
}
NameDeduper nameDeduper = new NameDeduper();
nameDeduper.addReservedNames(getAllLocalVariableNames());
nameDeduper.addUsedNames(getAllParamNames());
nameDeduper.addUsedNames(getExistingLocalVariableNames(gfunc));
Variable var = dvar.asLocalVariable();
String origName = var.getName();
String newName = nameDeduper.getUniqueName(origName);
if (newName != null) {
try {
var.setName(newName, null);
}
catch (DuplicateNameException | InvalidInputException e) {
// can't happen
}
var.setComment("Original name: " + origName);
}
VariableUtilities.checkVariableConflict(gfunc, var, varStorage, true);
gfunc.addLocalVariable(var, SourceType.IMPORTED);
}
catch (InvalidInputException | DuplicateNameException e) {
appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT,
"Local omitted variable %s[%s] scope starts here".formatted(
dvar.getDeclInfoString(),
varStorage != null ? varStorage.toString() : "UNKNOWN"),
"; ");
}
}
@Override
public String toString() {
return String.format(
"DWARFFunction [\n\tdni=%s,\n\taddress=%s,\n\tparams=%s,\n\tsourceInfo=%s,\n\tlocalVarErrors=%s,\n\tretval=%s\n]",
name, address, params, sourceInfo, localVarErrors, retval);
}
private static boolean isBadSubprogramDef(DIEAggregate diea) {
if (diea.isDanglingDeclaration() || !diea.hasAttribute(DW_AT_low_pc)) {
return true;
}
// fetch the low_pc attribute directly instead of calling diea.getLowPc() to avoid
// any fixups applied by lower level code
DWARFNumericAttribute attr =
diea.getAttribute(DW_AT_low_pc, DWARFNumericAttribute.class);
if (attr != null && attr.getUnsignedValue() == 0) {
return true;
}
return false;
}
private void appendComment(Address address, int commentType, String comment, String sep) {
DWARFUtil.appendComment(getProgram().getGhidraProgram(), address, commentType, "", comment,
sep);
}
}

View file

@ -15,10 +15,11 @@
*/
package ghidra.app.util.bin.format.dwarf4.next;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.io.IOException;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.util.bin.format.dwarf4.DWARFException;
import ghidra.program.model.data.*;
@ -37,19 +38,11 @@ public class DWARFParser {
private DWARFProgram prog;
private DWARFDataTypeManager dwarfDTM;
private TaskMonitor monitor;
private DWARFImportOptions importOptions;
private DWARFImportSummary importSummary = new DWARFImportSummary();
public DWARFParser(DWARFProgram prog, DataTypeManager builtInDTM, TaskMonitor monitor) {
public DWARFParser(DWARFProgram prog, TaskMonitor monitor) {
this.prog = prog;
this.monitor = monitor;
this.importOptions = prog.getImportOptions();
this.dwarfDTM = new DWARFDataTypeManager(prog, prog.getGhidraProgram().getDataTypeManager(),
builtInDTM, importSummary);
}
public DWARFImportOptions getImportOptions() {
return importOptions;
this.dwarfDTM = prog.getDwarfDTM();
}
/**
@ -193,8 +186,10 @@ public class DWARFParser {
monitor.setIndeterminate(false);
monitor.setShowProgressValue(true);
long start_ts = System.currentTimeMillis();
DWARFImportOptions importOptions = prog.getImportOptions();
DWARFImportSummary importSummary = prog.getImportSummary();
long start_ts = System.currentTimeMillis();
if (importOptions.isImportDataTypes()) {
dwarfDTM.importAllDataTypes(monitor);
prog.getGhidraProgram().flushEvents();
@ -203,8 +198,7 @@ public class DWARFParser {
if (importOptions.isImportFuncs()) {
long funcstart_ts = System.currentTimeMillis();
DWARFFunctionImporter dfi =
new DWARFFunctionImporter(prog, dwarfDTM, importOptions, importSummary, monitor);
DWARFFunctionImporter dfi = new DWARFFunctionImporter(prog, monitor);
dfi.importFunctions();
importSummary.funcsElapsedMS = System.currentTimeMillis() - funcstart_ts;
}

View file

@ -31,6 +31,8 @@ import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.dwarf4.external.ExternalDebugInfo;
import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.*;
import ghidra.app.util.opinion.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Program;
@ -47,9 +49,13 @@ import ghidra.util.task.TaskMonitor;
*/
public class DWARFProgram implements Closeable {
public static final String DWARF_ROOT_NAME = "DWARF";
public static final CategoryPath DWARF_ROOT_CATPATH = CategoryPath.ROOT.extend(DWARF_ROOT_NAME);
public static final CategoryPath UNCAT_CATPATH = DWARF_ROOT_CATPATH.extend("_UNCATEGORIZED_");
public static final int DEFAULT_NAME_LENGTH_CUTOFF = SymbolUtilities.MAX_SYMBOL_NAME_LENGTH;
public static final int MAX_NAME_LENGTH_CUTOFF = SymbolUtilities.MAX_SYMBOL_NAME_LENGTH;
public static final int MIN_NAME_LENGTH_CUTOFF = 20;
private static final int NAME_HASH_REPLACEMENT_SIZE = 8 + 2 + 2;
private static final String ELLIPSES_STR = "...";
@ -61,7 +67,7 @@ public class DWARFProgram implements Closeable {
* program sections, or their compressed "z" versions.
* <p>
* If the program is a MachO binary (ie. Mac), it must have a ".dSYM" directory co-located next to the
* original binary file on the native filesystem. (ie. outside of Ghidra). See the DSymSectionProvider
* original binary file on the native filesystem. (lie. outside of Ghidra). See the DSymSectionProvider
* for more info.
* <p>
* @param program {@link Program} to test
@ -114,10 +120,9 @@ public class DWARFProgram implements Closeable {
private final Program program;
private DWARFImportOptions importOptions;
private DWARFNameInfo rootDNI =
DWARFNameInfo.createRoot(new CategoryPath(CategoryPath.ROOT, DWARF_ROOT_NAME));
private DWARFNameInfo unCatDataTypeRoot = DWARFNameInfo.createRoot(
new CategoryPath(rootDNI.getOrganizationalCategoryPath(), "_UNCATEGORIZED_"));
private DWARFImportSummary importSummary;
private DWARFNameInfo rootDNI = DWARFNameInfo.createRoot(DWARF_ROOT_CATPATH);
private DWARFNameInfo unCatDataTypeRoot = DWARFNameInfo.createRoot(UNCAT_CATPATH);
private DWARFSectionProvider sectionProvider;
private StringTable debugStrings;
@ -173,6 +178,12 @@ public class DWARFProgram implements Closeable {
*/
private ListValuedMap<Long, DIEAggregate> typeReferers = new ArrayListValuedHashMap<>();
private final DWARFDataTypeManager dwarfDTM;
private final boolean stackGrowsNegative;
private final Map<Object, Object> opaqueProps = new HashMap<>();
/**
* Main constructor for DWARFProgram.
* <p>
@ -213,13 +224,15 @@ public class DWARFProgram implements Closeable {
this.program = program;
this.sectionProvider = sectionProvider;
this.importOptions = importOptions;
this.importSummary = new DWARFImportSummary();
this.nameLengthCutoffSize = Math.max(MIN_NAME_LENGTH_CUTOFF,
Math.min(importOptions.getNameLengthCutoff(), MAX_NAME_LENGTH_CUTOFF));
this.dwarfDTM = new DWARFDataTypeManager(this, program.getDataTypeManager());
this.stackGrowsNegative = program.getCompilerSpec().stackGrowsNegative();
monitor.setMessage("Reading DWARF debug string table");
this.debugStrings = StringTable.readStringTable(
sectionProvider.getSectionAsByteProvider(DWARFSectionNames.DEBUG_STR, monitor));
// Msg.info(this, "Read DWARF debug string table, " + debugStrings.getByteCount() + " bytes.");
this.attributeFactory = new DWARFAttributeFactory(this);
@ -249,7 +262,7 @@ public class DWARFProgram implements Closeable {
@Override
public void close() throws IOException {
sectionProvider = null;
sectionProvider.close();
compUnits.clear();
debugAbbrBR = null;
debugInfoBR = null;
@ -265,10 +278,18 @@ public class DWARFProgram implements Closeable {
return importOptions;
}
public DWARFImportSummary getImportSummary() {
return importSummary;
}
public Program getGhidraProgram() {
return program;
}
public DWARFDataTypeManager getDwarfDTM() {
return dwarfDTM;
}
public boolean isBigEndian() {
return program.getLanguage().isBigEndian();
}
@ -443,6 +464,7 @@ public class DWARFProgram implements Closeable {
String origName = isAnon ? null : name;
String workingName = ensureSafeNameLength(name);
workingName = fixupSpecialMeaningCharacters(workingName);
DWARFNameInfo result =
parentDNI.createChild(origName, workingName, DWARFUtil.getSymbolTypeFromDIE(diea));
@ -548,6 +570,16 @@ public class DWARFProgram implements Closeable {
return strs;
}
private String fixupSpecialMeaningCharacters(String s) {
// golang specific hacks:
// "\u00B7" -> "."
// "\u2215" -> "/"
if (s.contains("\u00B7") || s.contains("\u2215")) {
s = s.replaceAll("\u00B7", ".").replaceAll("\u2215", "/");
}
return s;
}
public DWARFNameInfo getName(DIEAggregate diea) {
DWARFNameInfo dni = lookupDNIByOffset(diea.getOffset());
if (dni == null) {
@ -717,6 +749,10 @@ public class DWARFProgram implements Closeable {
return debugStrings;
}
public AddressSpace getStackSpace() {
return program.getAddressFactory().getStackSpace();
}
public DWARFAttributeFactory getAttributeFactory() {
return attributeFactory;
}
@ -1011,4 +1047,29 @@ public class DWARFProgram implements Closeable {
public long getProgramBaseAddressFixup() {
return programBaseAddressFixup;
}
public Address getCodeAddress(Number offset) {
return program.getAddressFactory()
.getDefaultAddressSpace()
.getAddress(offset.longValue(), true);
}
public Address getDataAddress(Number offset) {
return program.getAddressFactory()
.getDefaultAddressSpace()
.getAddress(offset.longValue(), true);
}
public boolean stackGrowsNegative() {
return stackGrowsNegative;
}
public <T> T getOpaqueProperty(Object key, T defaultValue, Class<T> valueClass) {
Object obj = opaqueProps.get(key);
return obj != null && valueClass.isInstance(obj) ? valueClass.cast(obj) : defaultValue;
}
public void setOpaqueProperty(Object key, Object value) {
opaqueProps.put(key, value);
}
}

View file

@ -15,16 +15,18 @@
*/
package ghidra.app.util.bin.format.dwarf4.next;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.IOException;
import java.io.InputStream;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.lang.*;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
@ -38,20 +40,6 @@ public class DWARFRegisterMappingsManager {
private static Map<LanguageID, DWARFRegisterMappings> cache = new HashMap<>();
/**
* Returns true if the specified {@link LanguageDescription} has DWARF
* register mappings.
*
* @param langDesc The {@link LanguageDescription} to test
* @return true if the language has a DWARF register mapping specified
* @throws IOException if there was an error in the language LDEF file.
*/
public static boolean hasDWARFRegisterMapping(LanguageDescription langDesc) throws IOException {
return (langDesc instanceof SleighLanguageDescription) &&
getDWARFRegisterMappingFileNameFromLangDesc(
(SleighLanguageDescription) langDesc) != null;
}
/**
* Returns true if the specified {@link Language} has DWARF register
* mappings.
@ -61,7 +49,7 @@ public class DWARFRegisterMappingsManager {
* @throws IOException if there was an error in the language LDEF file.
*/
public static boolean hasDWARFRegisterMapping(Language lang) throws IOException {
return hasDWARFRegisterMapping(lang.getLanguageDescription());
return DWARFUtil.getLanguageExternalFile(lang, DWARF_REGISTER_MAPPING_NAME) != null;
}
/**
@ -84,51 +72,6 @@ public class DWARFRegisterMappingsManager {
return result;
}
/*
* Returns the DWARF register mapping file specified in the lang's definition, or
* null if it does not exist.
*/
private static String getDWARFRegisterMappingFileNameFromLangDesc(
SleighLanguageDescription langDesc) throws IOException {
List<String> dwarfSpecFilename = langDesc.getExternalNames(DWARF_REGISTER_MAPPING_NAME);
if (dwarfSpecFilename == null) {
return null;
}
if (dwarfSpecFilename.size() > 1) {
throw new IOException("Multiple DWARF register mappings found for language " +
langDesc.getLanguageID() + ": " + dwarfSpecFilename.toString());
}
return dwarfSpecFilename.get(0);
}
/**
* Returns {@link ResourceFile} that should contain the specified language's
* DWARF register mapping, never null.
*
* @param lang {@link Language} to find the mapping file for.
* @return {@link ResourceFile} of where the mapping file should be, never
* null.
* @throws IOException if not a Sleigh language or no mapping specified or
* multiple mappings specified.
*/
public static ResourceFile getDWARFRegisterMappingFileFor(Language lang) throws IOException {
LanguageDescription langDesc = lang.getLanguageDescription();
if (!(langDesc instanceof SleighLanguageDescription)) {
throw new IOException("Not a Sleigh Language: " + lang.getLanguageID());
}
SleighLanguageDescription sld = (SleighLanguageDescription) langDesc;
ResourceFile defsFile = sld.getDefsFile();
ResourceFile parentFile = defsFile.getParentFile();
String dwarfSpecFilename = getDWARFRegisterMappingFileNameFromLangDesc(sld);
if (dwarfSpecFilename == null) {
throw new IOException("No DWARF register mapping information found for language " +
lang.getLanguageID().getIdAsString());
}
ResourceFile dwarfFile = new ResourceFile(parentFile, dwarfSpecFilename);
return dwarfFile;
}
/**
* Finds the DWARF register mapping information file specified in the
* specified language's LDEF file and returns a new
@ -149,7 +92,12 @@ public class DWARFRegisterMappingsManager {
* in the register mapping data.
*/
public static DWARFRegisterMappings readMappingForLang(Language lang) throws IOException {
ResourceFile dwarfFile = getDWARFRegisterMappingFileFor(lang);
ResourceFile dwarfFile =
DWARFUtil.getLanguageExternalFile(lang, DWARF_REGISTER_MAPPING_NAME);
if (dwarfFile == null) {
throw new IOException("Missing DWARF register mapping file for language " +
lang.getLanguageID().getIdAsString());
}
if (!dwarfFile.exists()) {
throw new IOException("Missing DWARF register mapping file " + dwarfFile +
" for language " + lang.getLanguageID().getIdAsString());

View file

@ -15,9 +15,12 @@
*/
package ghidra.app.util.bin.format.dwarf4.next;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.DW_AT_decl_line;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag.DW_TAG_formal_parameter;
import ghidra.app.util.bin.format.dwarf4.DIEAggregate;
import ghidra.app.util.bin.format.dwarf4.DebugInfoEntry;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute;
/**
* Small class to hold the filename and line number info values from
@ -34,12 +37,23 @@ public class DWARFSourceInfo {
* @return new {@link DWARFSourceInfo} with filename:linenum info, or null if no info present in DIEA.
*/
public static DWARFSourceInfo create(DIEAggregate diea) {
String file = diea.getSourceFile();
int lineNum = (int) diea.getUnsignedLong(DWARFAttribute.DW_AT_decl_line, -1);
DIEAggregate currentDIEA = diea;
String file = null;
while (currentDIEA != null && (file = currentDIEA.getSourceFile()) == null) {
currentDIEA = currentDIEA.getParent();
}
if (file == null) {
return null;
}
DWARFNumericAttribute declLineAttr = diea.findAttributeInChildren(DW_AT_decl_line,
DW_TAG_formal_parameter, DWARFNumericAttribute.class); // TODO: what other children might have a line number attribute?
if (declLineAttr == null) {
return null;
}
return (file != null && lineNum != -1)
? new DWARFSourceInfo(file, lineNum)
: null;
int lineNum = (int) declLineAttr.getUnsignedValue();
return new DWARFSourceInfo(file, lineNum);
}
/**

View file

@ -0,0 +1,552 @@
/* ###
* 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.dwarf4.next;
import static ghidra.app.util.bin.format.dwarf4.encoding.DWARFAttribute.*;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.format.dwarf4.*;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFTag;
import ghidra.app.util.bin.format.dwarf4.expression.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.SymbolType;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
/**
* Represents a function parameter, local variable, or global variable.
*/
public class DWARFVariable {
/**
* Creates an unnamed, storage-less {@link DWARFVariable} from a DataType.
*
* @param dt {@link DataType} of the variable
* @return new {@link DWARFVariable}, never null
*/
public static DWARFVariable fromDataType(DWARFFunction dfunc, DataType dt) {
return new DWARFVariable(dfunc.getProgram(), dfunc, dt);
}
/**
* Reads a parameter.
*
* @param diea {@link DIEAggregate} DW_TAG_formal_parameter
* @param dfunc {@link DWARFFunction} that this parameter is attached to
* @param paramOrdinal
* @return new parameter, never null, possibly without storage info
* @throws IOException if error
*/
public static DWARFVariable readParameter(DIEAggregate diea, DWARFFunction dfunc,
int paramOrdinal) {
DWARFVariable dvar = new DWARFVariable(dfunc, diea);
dvar.isOutputParameter = diea.getBool(DW_AT_variable_parameter, false);
dvar.isThis = paramOrdinal == 0 && DWARFUtil.isThisParam(diea);
dvar.readParamStorage(diea);
return dvar;
}
/**
* Reads a local variable.
*
* @param diea {@link DIEAggregate} DW_TAG_variable
* @param dfunc {@link DWARFFunction} that this local var belongs to
* @return new DWARFVariable that represents a local var, or <strong>null</strong> if
* error reading storage info
*/
public static DWARFVariable readLocalVariable(DIEAggregate diea, DWARFFunction dfunc,
long offsetFromFuncStart) {
// local variable without location information is useless, so return null in those cases
DWARFVariable dvar = new DWARFVariable(dfunc, diea);
dvar.lexicalOffset = offsetFromFuncStart;
return dvar.readLocalVariableStorage(diea) ? dvar : null;
}
/**
* Reads a static/global variable.
*
* @param diea {@link DIEAggregate} DW_TAG_variable
* @return new {@link DWARFVariable} that represents the global variable, or
* <strong>null</strong> if error reading storage info
*/
public static DWARFVariable readGlobalVariable(DIEAggregate diea) {
DWARFVariable dvar = new DWARFVariable(null, diea);
SymbolType globalVarSymbolType = null; // TODO: need better symbol type, nothing matches static global var
dvar.name = dvar.name.replaceType(globalVarSymbolType);
return dvar.readGlobalStorage(diea) ? dvar : null;
}
private final DWARFProgram program;
private final DWARFFunction dfunc;
public DWARFNameInfo name;
public DataType type;
public long lexicalOffset; // offset inside function where variable storage becomes valid
public boolean isOutputParameter; // changes to parameter value escape back to the calling location
public boolean isExternal;
public boolean isThis;
public DWARFSourceInfo sourceInfo;
private List<Varnode> storage = new ArrayList<>();
private Varnode stackStorage; // any stack storage is forced to be last in the storage list
private String comment;
private DWARFVariable(DWARFProgram program, DWARFFunction dfunc, DataType type) {
this.program = program;
this.dfunc = dfunc;
this.type = type;
}
private DWARFVariable(DWARFFunction dfunc, DIEAggregate diea) {
this.program = diea.getProgram();
this.dfunc = dfunc;
this.name = program.getName(diea);
this.type = program.getDwarfDTM().getDataTypeForVariable(diea.getTypeRef());
this.isExternal = diea.getBool(DW_AT_external, false);
this.sourceInfo = DWARFSourceInfo.create(diea);
}
/**
* Assign storage for this variable in a ram data location.
*
* @param offset address offset
* @param prog {@link DWARFProgram}
*/
public void setRamStorage(long offset) {
clearStorage();
addRamStorage(offset);
}
public void addRamStorage(long offset) {
storage.add(new Varnode(program.getDataAddress(offset), type.getLength()));
}
/**
* Assign storage for this variable at a stack offset.
*
* @param offset stack offset
* @param prog {@link DWARFProgram}
*/
public void setStackStorage(long offset) {
clearStorage();
addStackStorage(offset, type.getLength());
}
public void addStackStorage(long offset, int length) {
if (stackStorage == null) {
stackStorage = new Varnode(program.getStackSpace().getAddress(offset), length);
}
else {
if (stackStorage.getOffset() + stackStorage.getSize() > offset) {
throw new IllegalArgumentException("Overlaps previous stack allocation");
}
stackStorage = new Varnode(program.getStackSpace().getAddress(stackStorage.getOffset()),
(int) (offset - stackStorage.getOffset() + length));
}
}
/**
* Assign storage for this variable via a list of registers.
*
* @param registers registers that contain the data
* @param prog {@link DWARFProgram}
*/
public void setRegisterStorage(List<Register> registers) {
clearStorage();
addRegisterStorage(registers);
}
public void addRegisterStorage(List<Register> registers) {
List<Varnode> varnodes =
DWARFUtil.convertRegisterListToVarnodeStorage(registers, type.getLength());
storage.addAll(varnodes);
}
/**
* @return true if this variable is stored on the stack
*/
public boolean isStackStorage() {
return storage.isEmpty() && stackStorage != null;
}
/**
* If this is a stack variable, return its stack offset.
*
* @return its stack offset
*/
public long getStackOffset() {
if (!isStackStorage()) {
throw new IllegalArgumentException();
}
return stackStorage.getOffset();
}
public String getToolTip() {
return """
<html>Built In Data Types<br>
&nbsp;&nbsp;%s
""".formatted("DEFAULT_DATA_ORG_DESCRIPTION");
}
/**
* @return true if this variable's storage is in ram
*/
public boolean isRamStorage() {
return storage.size() == 1 && storage.get(0).isAddress();
}
/**
* If this is a static/global variable, stored at a ram address, return it's
* ram address.
*
* @return address of where this variable is stored, null if not ram address
*/
public Address getRamAddress() {
return isRamStorage() ? storage.get(0).getAddress() : null;
}
public boolean isMissingStorage() {
return storage.isEmpty() && stackStorage == null;
}
public boolean isZeroByte() {
return DWARFUtil.isZeroByteDataType(type);
}
public boolean isVoidType() {
return DWARFUtil.isVoid(type);
}
public boolean isEmptyArray() {
return DWARFUtil.isEmptyArray(type);
}
public boolean isLocationValidOnEntry() {
return lexicalOffset == 0;
}
public void clearStorage() {
storage.clear();
stackStorage = null;
}
private boolean readParamStorage(DIEAggregate diea) {
try {
if (DataTypeComponent.usesZeroLengthComponent(type)) {
Msg.warn(this, "DWARF: zero-length function parameter %s:%s in %s@%s".formatted(
name.getName(), type.getName(), dfunc.name.getName(), dfunc.address));
return false;
}
DWARFLocation topLocation = DWARFLocation.getTopLocation(
diea.getAsLocation(DW_AT_location, dfunc.getRange()), dfunc.address.getOffset());
if (topLocation == null) {
return false;
}
return readStorage(diea, topLocation);
}
catch (IOException e) {
diea.getProgram().getImportSummary().exprReadError++;
return false;
}
}
private boolean readLocalVariableStorage(DIEAggregate diea) {
try {
DWARFLocation location = DWARFLocation
.getFirstLocation(diea.getAsLocation(DW_AT_location, dfunc.getRange()));
if (location == null) {
return false;
}
if (lexicalOffset == 0) {
// Don't override the lexical block's start offset (if set)
// with the address from the dwarf range. This gives slightly better results with
// test binaries in the decompiler, but might be wrong for other toolchains.
// If it causes problems, always use the address from the location's range.
lexicalOffset = location.getRange().getFrom() - dfunc.address.getOffset();
}
return readStorage(diea, location);
}
catch (IOException e) {
diea.getProgram().getImportSummary().exprReadError++;
return false;
}
}
private boolean readGlobalStorage(DIEAggregate diea) {
DWARFProgram prog = diea.getProgram();
try {
DWARFLocation location = DWARFLocation
.getFirstLocation(diea.getAsLocation(DW_AT_location, DWARFRange.EMPTY));
if (location == null) {
return false;
}
DWARFExpressionEvaluator exprEvaluator =
DWARFExpressionEvaluator.create(diea.getHeadFragment());
DWARFExpression expr = exprEvaluator.readExpr(location.getLocation());
exprEvaluator.evaluate(expr);
if (exprEvaluator.getRawLastRegister() != -1) {
Msg.warn(this, "DWARF: bad location for global variable %s: %s"
.formatted(getDeclInfoString(), expr.toString()));
return false;
}
long res = exprEvaluator.pop();
if (res == 0) {
// If the expression evaluated to a static address of '0'.
// This case is probably caused by relocation fixups not being applied to the
// .debug_info section.
prog.getImportSummary().relocationErrorVarDefs.add("%s:%s".formatted(
name.getNamespacePath().asFormattedString(), type.getPathName()));
return false;
}
setRamStorage(res + prog.getProgramBaseAddressFixup());
return true;
}
catch (DWARFExpressionException | UnsupportedOperationException
| IndexOutOfBoundsException | IOException ex) {
prog.getImportSummary().exprReadError++;
return false;
}
}
private boolean readStorage(DIEAggregate diea, DWARFLocation location) {
if (location == null) {
return false;
}
lexicalOffset = location.getRange().getFrom() - dfunc.address.getOffset();
DWARFProgram prog = diea.getProgram();
DWARFImportSummary importSummary = prog.getImportSummary();
try {
DWARFExpressionEvaluator exprEvaluator =
DWARFExpressionEvaluator.create(diea.getHeadFragment());
exprEvaluator.setFrameBase(dfunc.frameBase);
DWARFExpression expr = exprEvaluator.readExpr(location.getLocation());
exprEvaluator.evaluate(expr);
long res = exprEvaluator.pop();
// check expression eval result. Use early return for errors, leaving storage unset.
// Success return is at bottom of if/else chain of checks.
if (exprEvaluator.isDwarfStackValue()) {
// result is a value (not a location) left on the expr stack, which is not supported
importSummary.varDWARFExpressionValue++;
return false;
}
if (exprEvaluator.useUnknownRegister()) {
// This is a deref of a register (excluding the stack pointer)
// If the offset of the deref was 0, we can cheese it into a ghidra register location
// by changing the datatype to a pointer-to-original-datatype, otherwise
// its not usable in ghidra
if (!exprEvaluator.isRegisterLocation()) {
importSummary.varDynamicRegisterError++;
return false;
}
type = prog.getDwarfDTM().getPtrTo(type);
setRegisterStorage(List.of(exprEvaluator.getLastRegister()));
}
else if (exprEvaluator.isStackRelative()) {
if (exprEvaluator.isDeref()) {
type = prog.getDwarfDTM().getPtrTo(type);
}
setStackStorage(res);
}
else if (exprEvaluator.isRegisterLocation()) {
// The DWARF expression evaluated to a simple register. If we have a mapping
// for it in the "processor.dwarf" register mapping file, try to create
// a variable, otherwise log the unknown register for later logging.
Register reg = exprEvaluator.getLastRegister();
if (reg == null) {
// The DWARF register did not have a mapping to a Ghidra register, so
// log it to be displayed in an error summary at end of import phase.
importSummary.unknownRegistersEncountered
.add(exprEvaluator.getRawLastRegister());
return false;
}
if ((type.getLength() > reg.getMinimumByteSize())) {
importSummary.varFitError++;
Msg.warn(this,
"%s %s [%s, size=%d] for function %s@%s can not fit into specified register %s, size=%d, skipping. DWARF DIE: %s"
.formatted(getVarTypeName(diea), name.getName(), type.getName(),
type.getLength(), dfunc.name.getName(), dfunc.address,
reg.getName(), reg.getMinimumByteSize(), diea.getHexOffset()));
return false;
}
setRegisterStorage(List.of(reg));
}
else if (exprEvaluator.getRawLastRegister() == -1 && res != 0) {
// static global variable location
setRamStorage(res);
}
else {
Msg.error(this,
"%s location error for function %s@%s, %s: %s, DWARF DIE: %s, unsupported location information."
.formatted(getVarTypeName(diea), dfunc.name.getName(), dfunc.address,
name.getName(),
DWARFExpression.exprToString(location.getLocation(), diea),
diea.getHexOffset()));
return false;
}
return true;
}
catch (DWARFExpressionException | UnsupportedOperationException
| IndexOutOfBoundsException ex) {
importSummary.exprReadError++;
return false;
}
}
private String getVarTypeName(DIEAggregate diea) {
return diea.getTag() == DWARFTag.DW_TAG_formal_parameter ? "Parameter" : "Variable";
}
public int getStorageSize() {
return getVarnodes().stream().mapToInt(Varnode::getSize).sum();
}
private Varnode[] getVarnodesAsArray() {
Varnode[] result = new Varnode[storage.size() + (stackStorage != null ? 1 : 0)];
storage.toArray(result);
if (stackStorage != null) {
result[storage.size()] = stackStorage;
}
return result;
}
public List<Varnode> getVarnodes() {
List<Varnode> tmp = new ArrayList<>(storage);
if (stackStorage != null) {
tmp.add(stackStorage);
}
return tmp;
}
public void setVarnodes(List<Varnode> newStorage) {
clearStorage();
this.storage = newStorage;
Varnode lastNode = !storage.isEmpty() ? storage.get(storage.size() - 1) : null;
if (lastNode != null && DWARFUtil.isStackVarnode(lastNode)) {
stackStorage = lastNode;
storage.remove(storage.size() - 1);
}
}
public VariableStorage getVariableStorage() throws InvalidInputException {
Varnode[] varnodes = getVarnodesAsArray();
return varnodes.length != 0
? new VariableStorage(program.getGhidraProgram(), varnodes)
: VariableStorage.UNASSIGNED_STORAGE;
}
public Variable asLocalVariable() throws InvalidInputException {
int firstUseOffset = !isStackStorage() ? (int) lexicalOffset : 0;
LocalVariableImpl result = new LocalVariableImpl(name.getName(), firstUseOffset, type,
getVariableStorage(), program.getGhidraProgram());
result.setComment(comment);
return result;
}
public Parameter asParameter(boolean includeStorageDetail, Program program)
throws InvalidInputException {
VariableStorage paramStorage = !isMissingStorage() && includeStorageDetail
? getVariableStorage()
: VariableStorage.UNASSIGNED_STORAGE;
// try to allow ghidra to autoname param instead of using our autogenerated version
String newName = name.isAnon() ? null : name.getName();
ParameterImpl result =
new ParameterImpl(newName, Parameter.UNASSIGNED_ORDINAL, type, paramStorage, true,
program, SourceType.IMPORTED);
result.setComment(getParamComment());
return result;
}
private String getParamComment() {
if (!isOutputParameter) {
return comment;
}
return Objects.requireNonNullElse(comment, "") + "(Output Parameter)";
}
public ParameterDefinition asParameterDef() {
return new ParameterDefinitionImpl(name.getOriginalName(), type, getParamComment());
}
public Parameter asReturnParameter(boolean includeStorageDetail)
throws InvalidInputException {
VariableStorage storage = isVoidType()
? VariableStorage.VOID_STORAGE
: !isMissingStorage() && includeStorageDetail
? getVariableStorage()
: VariableStorage.UNASSIGNED_STORAGE;
return new ReturnParameterImpl(type, storage, true, program.getGhidraProgram());
}
public void appendComment(String prefix, String comment, String sep) {
if (comment == null || comment.isEmpty()) {
comment = "";
}
else {
comment += sep;
}
this.comment += prefix + comment;
}
public String getDeclInfoString() {
return "%s:%s".formatted(name.getName(), type.getDisplayName());
}
@Override
public String toString() {
try {
return "DWARFVariable [\n\t\tdni=%s,\n\t\ttype=%s,\n\t\tstorage=%s,\n\t\tisOutputParameter=%s\n\t]"
.formatted(name, type, getVariableStorage().toString(), isOutputParameter);
}
catch (InvalidInputException e) {
return "";
}
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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.dwarf4.next;
import java.util.*;
/**
* Helper for allocating unique string names.
* <p>
* "Reserved names" are names that will be used by later calls to the de-duper.
* <p>
* "Used names" are names that are already allocated and are in use.
* <p>
* Reserved names only prevent re-use of a name when a name is being generated because of a
* collision with a "used name".
*/
public class NameDeduper {
private final Set<String> usedNames = new HashSet<>();
private final Set<String> reservedNames = new HashSet<>();
/**
* Create a new name de-duper.
*
*/
public NameDeduper() {
// empty
}
/**
* Add names to the the de-duper that have already been used.
*
* @param alreadyUsedNames
*/
public void addUsedNames(Collection<String> alreadyUsedNames) {
usedNames.addAll(alreadyUsedNames);
}
/**
* Add names to the de-duper that will be used in a future call. These names do not block
* calls to confirm that a name is unique, but instead prevent the name from being used
* when an auto-generated name is created.
*
* @param additionalReservedNames
*/
public void addReservedNames(Collection<String> additionalReservedNames) {
reservedNames.addAll(additionalReservedNames);
}
/**
* Returns true if the specified name hasn't been allocated yet.
*
* @param name
* @return
*/
public boolean isUniqueName(String name) {
return name == null || !usedNames.contains(name);
}
/**
* Confirms that the specified name is unique, or returns a generated name that is unique.
*
* @param name name to test
* @return {@code null} if specified name is already unique (and marks the specified name as
* used), or returns a new, unique generated name
*/
public String getUniqueName(String name) {
if (name == null || usedNames.add(name)) {
return null;
}
String original = name;
int tryNum = 0;
while (usedNames.contains(name) || reservedNames.contains(name)) {
name = String.format("%s_%d", original, ++tryNum);
}
usedNames.add(name);
return name;
}
}

View file

@ -21,6 +21,7 @@ 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 ghidra.app.util.bin.BinaryReader;
@ -29,6 +30,7 @@ import ghidra.app.util.bin.format.elf.info.ElfInfoItem;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program;
import ghidra.program.model.util.CodeUnitInsertionException;
@ -57,7 +59,6 @@ public class GoBuildInfo implements ElfInfoItem {
private static final int FLAG_ENDIAN = (1 << 0);
private static final int FLAG_INLINE_STRING = (1 << 1);
/**
* Reads a GoBuildInfo ".go.buildinfo" section from the specified Program, if present.
*
@ -65,9 +66,20 @@ public class GoBuildInfo implements ElfInfoItem {
* @return new {@link GoBuildInfo} section, if present, null if missing or error
*/
public static GoBuildInfo fromProgram(Program program) {
ItemWithAddress<GoBuildInfo> wrappedItem = findBuildInfo(program);
return wrappedItem != null ? wrappedItem.item() : null;
}
public static ItemWithAddress<GoBuildInfo> findBuildInfo(Program program) {
// try as if binary is ELF
ItemWithAddress<GoBuildInfo> wrappedItem =
ElfInfoItem.readItemFromSection(program, SECTION_NAME, GoBuildInfo::read);
return wrappedItem != null ? wrappedItem.item() : null;
if (wrappedItem == null) {
// if not present, try common PE location for buildinfo, using "ElfInfoItem" logic
// even though this might be a PE binary, cause it doesn't matter
wrappedItem = ElfInfoItem.readItemFromSection(program, ".data", GoBuildInfo::read);
}
return wrappedItem;
}
/**
@ -95,6 +107,26 @@ public class GoBuildInfo implements ElfInfoItem {
return readStringInfo(reader, inlineStr, program, pointerSize);
}
/**
* Probes the specified InputStream and returns true if it starts with a go buildinfo magic
* signature.
*
* @param is InputStream
* @return true if starts with buildinfo magic signature
*/
public static boolean isPresent(InputStream is) {
try {
byte[] buffer = new byte[GO_BUILDINF_MAGIC.length];
int bytesRead = is.read(buffer);
return bytesRead == GO_BUILDINF_MAGIC.length &&
Arrays.equals(buffer, GO_BUILDINF_MAGIC);
}
catch (IOException e) {
// fall thru
}
return false;
}
private final int pointerSize;
private final Endian endian;
private final String version; // golang compiler version
@ -154,7 +186,8 @@ public class GoBuildInfo implements ElfInfoItem {
try {
StructureDataType struct = toStructure(program.getDataTypeManager());
if (struct != null) {
program.getListing().createData(address, struct);
DataUtilities.createData(program, address, struct, -1, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
}
}
catch (CodeUnitInsertionException e) {
@ -163,7 +196,7 @@ public class GoBuildInfo implements ElfInfoItem {
}
public void decorateProgramInfo(Options props) {
props.setString("Golang go version", getVersion());
GoVer.setProgramPropertiesWithOriginalVersionString(props, getVersion());
props.setString("Golang app path", getPath());
if (getModuleInfo() != null) {
getModuleInfo()
@ -185,7 +218,7 @@ public class GoBuildInfo implements ElfInfoItem {
StructureDataType toStructure(DataTypeManager dtm) {
StructureDataType result =
new StructureDataType(GolangElfInfoProducer.GO_CATEGORYPATH, "GoBuildInfo", 0, dtm);
new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildInfo", 0, dtm);
result.add(new ArrayDataType(ASCII, 14, -1, dtm), "magic", "\\xff Go buildinf:");
result.add(BYTE, "ptrSize", null);
result.add(BYTE, "flags", null);
@ -211,9 +244,9 @@ public class GoBuildInfo implements ElfInfoItem {
reader.setPointerIndex(32 /* static start of inline strings */);
versionString = reader.readNext(GoBuildInfo::varlenString);
byte[] modelStringBytes = reader.readNext(GoBuildInfo::varlenBytes);
byte[] moduleStringBytes = reader.readNext(GoBuildInfo::varlenBytes);
moduleString = extractModuleString(modelStringBytes);
moduleString = extractModuleString(moduleStringBytes);
}
else {
reader.setPointerIndex(16 /* static start of 2 string pointers */);

View file

@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import ghidra.program.model.data.CategoryPath;
public class GoConstants {
public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang");
}

View file

@ -0,0 +1,321 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
/**
* Utility class to fix Golang function parameter storage
*/
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
*/
public static void fixupFunction(Function func)
throws DuplicateNameException, InvalidInputException {
Program program = func.getProgram();
GoVer goVersion = GoVer.fromProgramProperties(program);
fixupFunction(func, goVersion);
}
public static void fixupFunction(Function func, GoVer goVersion)
throws DuplicateNameException, InvalidInputException {
Program program = func.getProgram();
GoParamStorageAllocator storageAllocator = new GoParamStorageAllocator(program, goVersion);
if (isGolangAbi0Func(func)) {
// Some (typically lower level) functions in the binary will be marked with a
// symbol that ends in the string "abi0".
// Throw away all registers and force stack allocation for everything
storageAllocator.setAbi0Mode();
}
fixupFunction(func, storageAllocator);
}
private static void fixupFunction(Function func, GoParamStorageAllocator storageAllocator)
throws DuplicateNameException, InvalidInputException {
List<ParameterImpl> spillVars = new ArrayList<>();
Program program = func.getProgram();
// for each parameter in the function's param list, calculate custom storage for it
List<ParameterImpl> newParams = new ArrayList<>();
for (Parameter oldParam : func.getParameters()) {
DataType dt = oldParam.getFormalDataType();
ParameterImpl newParam = null;
List<Register> regStorage = storageAllocator.getRegistersFor(dt);
if (regStorage != null && !regStorage.isEmpty()) {
newParam = updateParamWithCustomRegisterStorage(oldParam, regStorage);
spillVars.add(newParam);
if (dt instanceof Structure &&
newParam.getVariableStorage().size() != dt.getLength()) {
Msg.warn(GoFunctionFixup.class,
"Known storage allocation problem: func %s@%s param %s register allocation for structs missing inter-field padding."
.formatted(func.getName(), func.getEntryPoint(),
newParam.toString()));
}
}
else {
newParam = updateParamWithStackStorage(oldParam, storageAllocator);
}
newParams.add(newParam);
}
// prepare for calculating return result custom storage
storageAllocator.alignStack();
storageAllocator.resetRegAllocation();
DataType returnDT = func.getReturnType();
List<LocalVariable> returnResultAliasVars = new ArrayList<>();
ReturnParameterImpl returnParam = returnDT != null
? updateReturn(func, storageAllocator, returnResultAliasVars)
: null;
storageAllocator.alignStack();
if (returnParam == null && newParams.isEmpty()) {
// its better to do nothing than lock the signature down
return;
}
// Update the function in Ghidra
func.updateFunction(null, returnParam, newParams, FunctionUpdateType.CUSTOM_STORAGE, true,
SourceType.USER_DEFINED);
// Remove any old local vars that are in the callers stack instead of in the local stack area
for (Variable localVar : func.getLocalVariables()) {
if (localVar.isStackVariable() &&
!isInLocalVarStorageArea(func, localVar.getStackOffset())) {
func.removeVariable(localVar);
}
}
// For any parameters that were passed as registers, the golang caller pre-allocates
// space on the stack for the parameter value to be used when the register is overwritten.
// Ghidra decompilation results are improved if those storage locations are covered
// by variables that we create artificially.
for (ParameterImpl param : spillVars) {
DataType paramDT = param.getFormalDataType();
long stackOffset = storageAllocator.getStackAllocation(paramDT);
Varnode stackVarnode =
new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset),
paramDT.getLength());
VariableStorage varStorage = new VariableStorage(program, List.of(stackVarnode));
LocalVariableImpl localVar =
new LocalVariableImpl(param.getName() + "-spill", 0, paramDT, varStorage, program);
// TODO: needs more thought
func.addLocalVariable(localVar, SourceType.USER_DEFINED);
}
for (LocalVariable returnResultAliasVar : returnResultAliasVars) {
func.addLocalVariable(returnResultAliasVar, SourceType.USER_DEFINED);
}
}
/**
* 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
*/
public static DataType makeEmptyArrayDataType(DataType dt) {
StructureDataType struct = new StructureDataType(dt.getCategoryPath(),
"empty_" + dt.getName(), 0, dt.getDataTypeManager());
struct.setToDefaultPacking();
return struct;
}
private static ParameterImpl updateParamWithCustomRegisterStorage(Parameter oldParam,
List<Register> regStorage) throws InvalidInputException {
Program program = oldParam.getProgram();
DataType dt = oldParam.getDataType();
List<Varnode> varnodes =
DWARFUtil.convertRegisterListToVarnodeStorage(regStorage, dt.getLength());
VariableStorage varStorage =
new VariableStorage(program, varnodes.toArray(Varnode[]::new));
ParameterImpl newParam =
new ParameterImpl(oldParam.getName(), Parameter.UNASSIGNED_ORDINAL, dt,
varStorage, true, program, SourceType.USER_DEFINED);
return newParam;
}
private static ParameterImpl updateParamWithStackStorage(Parameter oldParam,
GoParamStorageAllocator storageAllocator) throws InvalidInputException {
DataType dt = oldParam.getDataType();
Program program = oldParam.getProgram();
if (!DWARFUtil.isZeroByteDataType(dt)) {
long stackOffset = storageAllocator.getStackAllocation(dt);
return new ParameterImpl(oldParam.getName(), dt, (int) stackOffset, program);
}
else {
if (DWARFUtil.isEmptyArray(dt)) {
dt = makeEmptyArrayDataType(dt);
}
Address zerobaseAddress = GolangSymbolAnalyzer.getZerobaseAddress(program);
return new ParameterImpl(oldParam.getName(), dt, zerobaseAddress, program,
SourceType.USER_DEFINED);
}
}
private static ReturnParameterImpl updateReturn(Function func,
GoParamStorageAllocator storageAllocator, List<LocalVariable> returnResultAliasVars)
throws InvalidInputException {
Program program = func.getProgram();
DataTypeManager dtm = program.getDataTypeManager();
DataType returnDT = func.getReturnType();
List<Varnode> varnodes = new ArrayList<>();
if (returnDT == null || Undefined.isUndefined(returnDT) || DWARFUtil.isVoid(returnDT)) {
return null;
}
// status refactoring return result storage calc to use new GoFunctionMultiReturn
// class to embed ordinal order in data type so that original data type and calc info
// can be recreated.
GoFunctionMultiReturn multiReturn;
if ((multiReturn =
GoFunctionMultiReturn.fromStructure(returnDT, dtm, storageAllocator)) != null) {
// allocate storage for individual elements of the struct because they were
// originally separate return values.
// Also turn off endianness fixups in the registers that are fetched
// because we will do it manually
returnDT = multiReturn.getStruct();
for (DataTypeComponent dtc : multiReturn.getNormalStorageComponents()) {
allocateReturnStorage(program, dtc.getFieldName() + "-return-result-alias",
dtc.getDataType(), storageAllocator, varnodes, returnResultAliasVars,
false);
}
for (DataTypeComponent dtc : multiReturn.getStackStorageComponents()) {
allocateReturnStorage(program, dtc.getFieldName() + "-return-result-alias",
dtc.getDataType(), storageAllocator, varnodes, returnResultAliasVars,
false);
}
if (!program.getMemory().isBigEndian()) {
reverseNonStackStorageLocations(varnodes);
}
}
else if (DWARFUtil.isZeroByteDataType(returnDT)) {
if (DWARFUtil.isEmptyArray(returnDT)) {
returnDT = makeEmptyArrayDataType(returnDT);
}
varnodes.add(new Varnode(GolangSymbolAnalyzer.getZerobaseAddress(program), 1));
}
else {
allocateReturnStorage(program, "return-value-alias-variable", returnDT,
storageAllocator, varnodes, returnResultAliasVars, true);
}
if (varnodes.isEmpty()) {
return null;
}
VariableStorage varStorage =
new VariableStorage(program, varnodes.toArray(Varnode[]::new));
return new ReturnParameterImpl(returnDT, varStorage, true, program);
}
private static void allocateReturnStorage(Program program, String name_unused, DataType dt,
GoParamStorageAllocator storageAllocator, List<Varnode> varnodes,
List<LocalVariable> returnResultAliasVars, boolean allowEndianFixups)
throws InvalidInputException {
List<Register> regStorage = storageAllocator.getRegistersFor(dt, allowEndianFixups);
if (regStorage != null && !regStorage.isEmpty()) {
varnodes.addAll(
DWARFUtil.convertRegisterListToVarnodeStorage(regStorage, dt.getLength()));
}
else {
if (!DWARFUtil.isZeroByteDataType(dt)) {
long stackOffset = storageAllocator.getStackAllocation(dt);
varnodes.add(
new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset),
dt.getLength()));
// when the return value is on the stack, the decompiler's output is improved
// when the function has something at the stack location
LocalVariableImpl returnAliasLocalVar = new LocalVariableImpl(name_unused, dt,
(int) stackOffset, program, SourceType.USER_DEFINED);
returnResultAliasVars.add(returnAliasLocalVar);
}
}
}
public static boolean isGolangAbi0Func(Function func) {
Address funcAddr = func.getEntryPoint();
for (Symbol symbol : func.getProgram().getSymbolTable().getSymbolsAsIterator(funcAddr)) {
if (symbol.getSymbolType() == SymbolType.LABEL) {
String labelName = symbol.getName();
if (labelName.endsWith("abi0")) {
return true;
}
}
}
return false;
}
public static boolean isInLocalVarStorageArea(Function func, long stackOffset) {
Program program = func.getProgram();
boolean paramsHavePositiveOffset = program.getCompilerSpec().stackGrowsNegative();
return (paramsHavePositiveOffset && stackOffset < 0) ||
(!paramsHavePositiveOffset && stackOffset >= 0);
}
/**
* Invert the order of the any register storage locations to match the decompiler's logic
* for assigning storage to structs that varies on endianness.
* <p>
* Only valid for storage scheme that has all register storages listed first / contiguous.
*
* @param varnodes
*/
public static void reverseNonStackStorageLocations(List<Varnode> varnodes) {
int regStorageCount;
for (regStorageCount = 0; regStorageCount < varnodes.size(); regStorageCount++) {
if (DWARFUtil.isStackVarnode(varnodes.get(regStorageCount))) {
break;
}
}
List<Varnode> regStorageList = new ArrayList<>(varnodes.subList(0, regStorageCount));
for (int i = 0; i < regStorageList.size(); i++) {
varnodes.set(i, regStorageList.get(regStorageList.size() - 1 - i));
}
}
}

View file

@ -0,0 +1,207 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.app.util.bin.format.dwarf4.next.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
import ghidra.program.model.lang.Register;
/**
* Handles creating a Ghidra structure to represent multiple return values returned from a golang
* function.
* <p>
* Assigning custom storage for the return value is complicated by:
* <ul>
* <li>golang storage allocations depend on the formal ordering of the return values
* <li>stack storage must be last in a list of varnodes
* <li>the decompiler maps a structure's contents to the list of varnodes in an endian-dependent
* manner.
* </ul>
* To meet these complications, the structure's layout is modified to put all items that were
* marked as being stack parameters to either the front or back of the structure.
* <p>
* To allow this artificial structure to adjusted by the user and reused at some later time
* to re-calculate the correct storage, the items in the structure are tagged with the original
* ordinal of that item as a text comment of each structure field, so that the correct ordering
* of items can be re-created when needed.
* <p>
* If the structure layout is modified to conform to an arch's requirements, the structure's
* name will be modified to include that arch's description at the end (eg. "_x86_64")
*/
public class GoFunctionMultiReturn {
public static final String MULTIVALUE_RETURNTYPE_SUFFIX = "_multivalue_return_type";
private static final String ORDINAL_PREFIX = "ordinal: ";
// match a substring that is "ordinal: NN", marking the number portion as group 1
private static final Pattern ORDINAL_REGEX =
Pattern.compile(".*" + ORDINAL_PREFIX + "([\\d]+)[^\\d]*");
public static boolean isMultiReturnDataType(DataType dt) {
return dt instanceof Structure && dt.getName().endsWith(MULTIVALUE_RETURNTYPE_SUFFIX);
}
public static GoFunctionMultiReturn fromStructure(DataType dt, DataTypeManager dtm,
GoParamStorageAllocator storageAllocator) {
return isMultiReturnDataType(dt)
? new GoFunctionMultiReturn((Structure) dt, dtm, storageAllocator)
: null;
}
private Structure struct;
private List<DataTypeComponent> normalStorageComponents = new ArrayList<>();
private List<DataTypeComponent> stackStorageComponents = new ArrayList<>();
public GoFunctionMultiReturn(List<DWARFVariable> returnParams, DWARFFunction dfunc,
DataTypeManager dtm, GoParamStorageAllocator storageAllocator) {
Structure newStruct = mkStruct(dfunc.name.getParentCP(), dfunc.name.getName(), dtm);
int ordinalNum = 0;
for (DWARFVariable dvar : returnParams) {
newStruct.add(dvar.type, dvar.name.getName(), ORDINAL_PREFIX + ordinalNum);
ordinalNum++;
}
regenerateMultireturnStruct(newStruct, dtm, storageAllocator);
}
public GoFunctionMultiReturn(CategoryPath categoryPath, String funcName, List<DataType> types,
DataTypeManager dtm, GoParamStorageAllocator storageAllocator) {
Structure newStruct = mkStruct(categoryPath, funcName, dtm);
int ordinalNum = 0;
for (DataType dt : types) {
newStruct.add(dt, "~r%d".formatted(ordinalNum), ORDINAL_PREFIX + ordinalNum);
ordinalNum++;
}
regenerateMultireturnStruct(newStruct, dtm, storageAllocator);
}
private static Structure mkStruct(CategoryPath cp, String baseName, DataTypeManager dtm) {
String structName = baseName + MULTIVALUE_RETURNTYPE_SUFFIX;
Structure newStruct = new StructureDataType(cp, structName, 0, dtm);
newStruct.setPackingEnabled(true);
newStruct.setExplicitPackingValue(1);
newStruct.setDescription("Artificial data type to hold a function's return values");
return newStruct;
}
public GoFunctionMultiReturn(Structure struct, DataTypeManager dtm,
GoParamStorageAllocator storageAllocator) {
regenerateMultireturnStruct(struct, dtm, storageAllocator);
}
public Structure getStruct() {
return struct;
}
public List<DataTypeComponent> getNormalStorageComponents() {
return normalStorageComponents;
}
public List<DataTypeComponent> getStackStorageComponents() {
return stackStorageComponents;
}
private record StackComponentInfo(DataTypeComponent dtc, int ordinal, String comment) {}
private void regenerateMultireturnStruct(Structure struct, DataTypeManager dtm,
GoParamStorageAllocator storageAllocator) {
if (storageAllocator == null) {
this.struct = struct;
for (DataTypeComponent dtc : getComponentsInOriginalOrder(struct)) {
stackStorageComponents.add(dtc);
}
return;
}
Structure adjustedStruct =
new StructureDataType(
struct.getCategoryPath(), getBasename(struct.getName()) +
MULTIVALUE_RETURNTYPE_SUFFIX + "_" + storageAllocator.getArchDescription(),
0, dtm);
adjustedStruct.setPackingEnabled(true);
adjustedStruct.setExplicitPackingValue(1);
storageAllocator = storageAllocator.clone();
List<StackComponentInfo> stackResults = new ArrayList<>();
int compNum = 0;
for (DataTypeComponent dtc : getComponentsInOriginalOrder(struct)) {
List<Register> regs = storageAllocator.getRegistersFor(dtc.getDataType());
if (regs == null || regs.isEmpty()) {
long stackOffset = storageAllocator.getStackAllocation(dtc.getDataType());
String comment = "stack[%d] %s%d".formatted(stackOffset, ORDINAL_PREFIX, compNum);
stackResults.add(new StackComponentInfo(dtc, compNum, comment));
}
else {
String comment = "%s %s%d".formatted(regs, ORDINAL_PREFIX, compNum);
DataTypeComponent newDTC =
adjustedStruct.add(dtc.getDataType(), dtc.getFieldName(), comment);
normalStorageComponents.add(newDTC);
}
compNum++;
}
// add the stack items to the struct last or first, depending on endianness
for (int i = 0; i < stackResults.size(); i++) {
StackComponentInfo sci = stackResults.get(i);
DataTypeComponent dtc = sci.dtc;
DataTypeComponent newDTC;
if (storageAllocator.isBigEndian()) {
newDTC = adjustedStruct.add(dtc.getDataType(), dtc.getFieldName(), sci.comment);
}
else {
newDTC =
adjustedStruct.insert(i, dtc.getDataType(), -1, dtc.getFieldName(),
sci.comment);
}
stackStorageComponents.add(newDTC);
}
boolean isEquiv = DWARFDataTypeConflictHandler.INSTANCE.resolveConflict(adjustedStruct,
struct) == ConflictResult.USE_EXISTING;
this.struct = isEquiv ? struct : adjustedStruct;
}
private static String getBasename(String structName) {
int i = structName.indexOf(MULTIVALUE_RETURNTYPE_SUFFIX);
return i > 0 ? structName.substring(0, i) : structName;
}
private static int getOrdinalNumber(DataTypeComponent dtc) {
String comment = Objects.requireNonNullElse(dtc.getComment(), "");
Matcher m = ORDINAL_REGEX.matcher(comment);
try {
return m.matches() ? Integer.parseInt(m.group(1)) : -1;
}
catch (NumberFormatException nfe) {
return -1;
}
}
private static List<DataTypeComponent> getComponentsInOriginalOrder(Structure struct) {
List<DataTypeComponent> dtcs = new ArrayList<>(List.of(struct.getDefinedComponents()));
Collections.sort(dtcs,
(dtc1, dtc2) -> Integer.compare(getOrdinalNumber(dtc1), getOrdinalNumber(dtc2)));
return dtcs;
}
}

View file

@ -0,0 +1,271 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.*;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.util.NumericUtilities;
/**
* Logic and helper for allocating storage for a function's parameters and return value.
* <p>
* Not threadsafe.
*/
public class GoParamStorageAllocator {
private static final int INTREG = 0;
private static final int FLOATREG = 1;
private List<List<Register>> regs;
private int[] nextReg = new int[2];
private GoRegisterInfo callspecInfo;
private long stackOffset;
private boolean isBigEndian;
private PrototypeModel abiInternalCallingConvention;
private PrototypeModel abi0CallingConvention;
private String archDescription;
/**
* Creates a new golang function call storage allocator for the specified Ghidra Language.
* <p>
* See {@link GoRegisterInfoManager#getRegisterInfoForLang(Language, GoVer)}
*
* @param program {@link Program}
* @param goVersion version of go used to create the program
*/
public GoParamStorageAllocator(Program program, GoVer goVersion) {
Language lang = program.getLanguage();
this.callspecInfo =
GoRegisterInfoManager.getInstance().getRegisterInfoForLang(lang, goVersion);
this.stackOffset = callspecInfo.getStackInitialOffset();
this.regs = List.of(callspecInfo.getIntRegisters(), callspecInfo.getFloatRegisters());
this.isBigEndian = lang.isBigEndian();
this.abiInternalCallingConvention =
program.getFunctionManager().getCallingConvention("abi-internal");
this.abi0CallingConvention = program.getFunctionManager().getCallingConvention("abi0");
this.archDescription =
"%s_%d".formatted(lang.getLanguageDescription().getProcessor().toString(),
lang.getLanguageDescription().getSize());
}
private GoParamStorageAllocator(List<List<Register>> regs, int[] nextReg,
GoRegisterInfo callspecInfo, long stackOffset, boolean isBigEndian,
PrototypeModel abiInternalCallingConvention, PrototypeModel abi0CallingConvention,
String archDescription) {
this.regs = List.of(regs.get(INTREG), regs.get(FLOATREG));
this.nextReg = new int[] { nextReg[INTREG], nextReg[FLOATREG] };
this.callspecInfo = callspecInfo;
this.stackOffset = stackOffset;
this.isBigEndian = isBigEndian;
this.abiInternalCallingConvention = abiInternalCallingConvention;
this.abi0CallingConvention = abi0CallingConvention;
this.archDescription = archDescription;
}
@Override
public GoParamStorageAllocator clone() {
return new GoParamStorageAllocator(regs, nextReg, callspecInfo, stackOffset, isBigEndian,
abiInternalCallingConvention, abi0CallingConvention, archDescription);
}
public PrototypeModel getAbi0CallingConvention() {
return abi0CallingConvention;
}
public PrototypeModel getAbiInternalCallingConvention() {
return abiInternalCallingConvention;
}
public String getArchDescription() {
return archDescription;
}
public boolean isBigEndian() {
return isBigEndian;
}
public void resetRegAllocation() {
nextReg[INTREG] = 0;
nextReg[FLOATREG] = 0;
}
private boolean allocateReg(int count, int regType, DataType dt, List<Register> result) {
int newNextReg = nextReg[regType] + count;
if (newNextReg > regs.get(regType).size()) {
return false;
}
int remainingSize = dt.getLength();
for (int regNum = nextReg[regType]; regNum < newNextReg; regNum++) {
Register reg = getBestFitRegister(regs.get(regType).get(regNum), remainingSize);
remainingSize -= reg.getMinimumByteSize();
result.add(reg);
}
nextReg[regType] = newNextReg;
return true;
}
private Register getBestFitRegister(Register reg, int size) {
while (reg.getMinimumByteSize() > size && reg.hasChildren()) {
reg = reg.getChildRegisters().get(0);
}
return reg;
}
private int[] saveRegAllocation() {
return new int[] { nextReg[INTREG], nextReg[FLOATREG] };
}
private void restoreRegAllocation(int[] savedNextReg) {
nextReg[INTREG] = savedNextReg[INTREG];
nextReg[FLOATREG] = savedNextReg[FLOATREG];
}
public void setAbi0Mode() {
regs = List.of(List.of(), List.of());
}
public boolean isAbi0Mode() {
return regs.get(INTREG).isEmpty() && regs.get(FLOATREG).isEmpty();
}
/**
* Returns a list of {@link Register registers} that will successfully store the specified
* data type, as well as marking those registers as used and unavailable.
*
* @param dt {@link DataType} to allocate register space for
* @return list of {@link Register registers}, possibly empty if the data type was zero-length,
* possibly null if the data type is not compatible with register storage
*/
public List<Register> getRegistersFor(DataType dt) {
return getRegistersFor(dt, true);
}
/**
* Returns a list of {@link Register registers} that will successfully store the specified
* data type, as well as marking those registers as used and unavailable.
*
* @param dt {@link DataType} to allocate register space for
* @param allowEndianFixups boolean flag, if true the result (if it contains more than a single
* location) will automatically be adjusted in little endian programs to match how storage
* varnodes are laid-out, if false the result will not be adjusted
* @return list of {@link Register registers}, possibly empty if the data type was zero-length,
* possibly null if the data type is not compatible with register storage
*/
public List<Register> getRegistersFor(DataType dt, boolean allowEndianFixups) {
int[] saveRegAllocation = saveRegAllocation();
List<Register> result = new ArrayList<>();
if (!countRegistersFor(dt, result)) {
restoreRegAllocation(saveRegAllocation);
return null;
}
if (allowEndianFixups && !isBigEndian && result.size() > 1) {
Collections.reverse(result);
}
return new ArrayList<>(result);
}
/**
* Returns the stack offset that should be used to store the data type on the stack, as well
* as marking that stack area as used and unavailable.
*
* @param dt {@link DataType} to allocate stack space for
* @return offset in stack where the data item will be located
*/
public long getStackAllocation(DataType dt) {
if (dt.isZeroLength()) {
return stackOffset;
}
alignStackFor(dt);
long result = stackOffset;
stackOffset += dt.getLength();
return result;
}
public long getStackOffset() {
return stackOffset;
}
public void setStackOffset(long newStackOffset) {
this.stackOffset = newStackOffset;
}
public void alignStackFor(DataType dt) {
int alignmentSize = callspecInfo.getAlignmentForType(dt);
stackOffset = NumericUtilities.getUnsignedAlignedValue(stackOffset, alignmentSize);
}
public void alignStack() {
stackOffset =
NumericUtilities.getUnsignedAlignedValue(stackOffset, callspecInfo.getMaxAlign());
}
private boolean countRegistersFor(DataType dt, List<Register> result) {
if (DWARFUtil.isZeroByteDataType(dt)) {
return false;
}
if (dt instanceof TypeDef typedefDT) {
dt = typedefDT.getBaseDataType();
}
if (dt instanceof Pointer) {
return allocateReg(1, INTREG, dt, result);
}
if (GoRegisterInfo.isIntType(dt)) {
int size = dt.getLength();
int intRegSize = callspecInfo.getIntRegisterSize();
if (size <= intRegSize * 2) {
return allocateReg(
(int) (NumericUtilities.getUnsignedAlignedValue(size, intRegSize) / intRegSize),
INTREG, dt, result);
}
}
if (dt instanceof AbstractFloatDataType) {
return allocateReg(1, FLOATREG, dt, result);
}
if (dt instanceof Array array) {
int numElements = array.getNumElements();
if (numElements == 0) {
return true;
}
if (numElements == 1 && countRegistersFor(array.getDataType(), result)) {
return true;
}
return false;
}
if (dt instanceof Structure struct) {
DataTypeComponent prevDTC = null;
for (DataTypeComponent dtc : struct.getDefinedComponents()) {
int padding = prevDTC != null ? dtc.getOffset() - prevDTC.getOffset() : 0;
if (padding != 0) {
}
if (!countRegistersFor(dtc.getDataType(), result)) {
return false;
}
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.List;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.lang.Register;
/**
* Immutable information about registers, alignment sizes, etc needed to allocate storage
* for parameters during a function call.
* <p>
*/
public class GoRegisterInfo {
private List<Register> intRegisters;
private List<Register> floatRegisters;
private int stackInitialOffset;
private int maxAlign; // 4 or 8
private Register currentGoroutineRegister; // always points to g
private Register zeroRegister; // always contains a zero value
GoRegisterInfo(List<Register> intRegisters, List<Register> floatRegisters,
int stackInitialOffset, int maxAlign, Register currentGoroutineRegister,
Register zeroRegister) {
this.intRegisters = intRegisters;
this.floatRegisters = floatRegisters;
this.stackInitialOffset = stackInitialOffset;
this.maxAlign = maxAlign;
this.currentGoroutineRegister = currentGoroutineRegister;
this.zeroRegister = zeroRegister;
}
public int getIntRegisterSize() {
return maxAlign; // TODO: HACK: ?????
}
public int getMaxAlign() {
return maxAlign;
}
public Register getCurrentGoroutineRegister() {
return currentGoroutineRegister;
}
public Register getZeroRegister() {
return zeroRegister;
}
public List<Register> getIntRegisters() {
return intRegisters;
}
public List<Register> getFloatRegisters() {
return floatRegisters;
}
public int getStackInitialOffset() {
return stackInitialOffset;
}
public int getAlignmentForType(DataType dt) {
while (dt instanceof TypeDef || dt instanceof Array) {
if (dt instanceof TypeDef) {
dt = ((TypeDef) dt).getBaseDataType();
}
if (dt instanceof Array) {
dt = ((Array) dt).getDataType();
}
}
if (isIntType(dt) && isIntrinsicSize(dt.getLength())) {
return Math.min(maxAlign, dt.getLength());
}
if (dt instanceof Complex8DataType /* golang complex64 */ ) {
return 4;
}
if (dt instanceof AbstractFloatDataType) {
return Math.min(maxAlign, dt.getLength());
}
return maxAlign;
}
static boolean isIntType(DataType dt) {
return dt instanceof AbstractIntegerDataType || dt instanceof WideCharDataType ||
dt instanceof WideChar16DataType || dt instanceof WideChar32DataType ||
dt instanceof Enum || dt instanceof BooleanDataType;
}
static boolean isIntrinsicSize(int size) {
return Integer.bitCount(size) == 1;
}
}

View file

@ -0,0 +1,221 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.program.model.lang.*;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
/**
* XML config file format:
* <pre>
* &lt;golang>
* &lt;register_info versions="V1_17,V1_18">
* &lt;int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
* &lt;float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
* &lt;stack initialoffset="8" maxalign="8"/>
* &lt;current_goroutine register="R14"/>
* &lt;zero_register register="XMM15"/>
* &lt;/register_info>
* &lt;register_info versions="V1_2">
* ...
* &lt;/register_info>
* &lt;/golang>
* </pre>
*/
public class GoRegisterInfoManager {
private static final String REGISTER_INFO_EXTERNAL_NAME = "Golang.register.info.file";
private static class SingletonHolder {
private static GoRegisterInfoManager instance = new GoRegisterInfoManager();
}
public static GoRegisterInfoManager getInstance() {
return SingletonHolder.instance;
}
private Map<LanguageID, Map<GoVer, GoRegisterInfo>> cache = new HashMap<>();
/**
* Returns a {@link GoRegisterInfo} instance for the specified {@link Language}.
* <p>
* If the language didn't define golang register info, a generic/empty instance will be
* returned that forces all parameters to be stack allocated.
*
* @param lang {@link Language}
* @param goVersion
* @return {@link GoRegisterInfo}, never null
*/
public synchronized GoRegisterInfo getRegisterInfoForLang(Language lang, GoVer goVersion) {
Map<GoVer, GoRegisterInfo> perVersionRegInfos =
cache.computeIfAbsent(lang.getLanguageID(), (key) -> loadRegisterInfo(lang));
GoRegisterInfo registerInfo = perVersionRegInfos.get(goVersion);
if (registerInfo == null) {
registerInfo = getDefault(lang);
perVersionRegInfos.put(goVersion, registerInfo);
int goSize = lang.getInstructionAlignment();
Msg.warn(this, "Missing Golang register info for: " + lang.getLanguageID() +
", defaulting to abi0, size=" + goSize);
}
return registerInfo;
}
private Map<GoVer, GoRegisterInfo> loadRegisterInfo(Language lang) {
try {
ResourceFile f = DWARFUtil.getLanguageExternalFile(lang, REGISTER_INFO_EXTERNAL_NAME);
if (f != null) {
return read(f, lang);
}
Msg.warn(GoRegisterInfoManager.class,
"Missing Golang register info file for: %s".formatted(lang.getLanguageID()));
}
catch (IOException e) {
Msg.warn(GoRegisterInfoManager.class, "Failed to read Golang register info file",
e);
}
return new HashMap<>();
}
//-------------------------------------------------------------------------------------------
private Map<GoVer, GoRegisterInfo> read(ResourceFile f, Language lang)
throws IOException {
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
try (InputStream fis = f.getInputStream()) {
Document doc = sax.build(fis);
Element rootElem = doc.getRootElement();
return readFrom(rootElem, lang);
}
catch (JDOMException | IOException e) {
Msg.error(GoRegisterInfo.class, "Bad Golang register info file " + f, e);
throw new IOException("Failed to read Golang register info file " + f, e);
}
}
@SuppressWarnings("unchecked")
public Map<GoVer, GoRegisterInfo> readFrom(Element rootElem, Language lang)
throws IOException {
Map<GoVer, GoRegisterInfo> result = new HashMap<>();
for (Element regInfoElem : (List<Element>) rootElem.getChildren("register_info")) {
Map<GoVer, GoRegisterInfo> registerInfos = readRegInfoElement(regInfoElem, lang);
result.putAll(registerInfos);
}
return result;
}
private Map<GoVer, GoRegisterInfo> readRegInfoElement(Element regInfoElem, Language lang)
throws IOException {
Set<GoVer> validGoVersions =
parseValidGoVersionsStr(XmlUtilities.requireStringAttr(regInfoElem, "versions"));
Element intRegsElem = regInfoElem.getChild("int_registers");
Element floatRegsElem = regInfoElem.getChild("float_registers");
Element stackElem = regInfoElem.getChild("stack");
Element goRoutineElem = regInfoElem.getChild("current_goroutine");
Element zeroRegElem = regInfoElem.getChild("zero_register");
if (intRegsElem == null || floatRegsElem == null || stackElem == null ||
goRoutineElem == null || zeroRegElem == null) {
throw new IOException("Bad format");
}
List<Register> intRegs = parseRegListStr(intRegsElem.getAttributeValue("list"), lang);
List<Register> floatRegs = parseRegListStr(floatRegsElem.getAttributeValue("list"), lang);
int stackInitialOffset =
XmlUtilities.parseBoundedIntAttr(stackElem, "initialoffset", 0, Integer.MAX_VALUE);
int maxAlign =
XmlUtilities.parseBoundedIntAttr(stackElem, "maxalign", 1, Integer.MAX_VALUE);
Register currentGoRoutineReg =
parseRegStr(goRoutineElem.getAttributeValue("register"), lang);
Register zeroReg = parseRegStr(zeroRegElem.getAttributeValue("register"), lang);
GoRegisterInfo registerInfo =
new GoRegisterInfo(intRegs, floatRegs, stackInitialOffset, maxAlign,
currentGoRoutineReg, zeroReg);
Map<GoVer, GoRegisterInfo> result = new HashMap<>();
for (GoVer goVer : validGoVersions) {
result.put(goVer, registerInfo);
}
return result;
}
private GoRegisterInfo getDefault(Language lang) {
int goSize = lang.getInstructionAlignment();
return new GoRegisterInfo(List.of(), List.of(), goSize, goSize, null, null);
}
private List<Register> parseRegListStr(String s, Language lang) throws IOException {
List<Register> result = new ArrayList<>();
for (String regName : s.split(",")) {
regName = regName.trim();
if (regName.isEmpty()) {
continue;
}
Register register = parseRegStr(regName, lang);
if (register != null) {
result.add(register);
}
}
return result;
}
private Register parseRegStr(String regName, Language lang) throws IOException {
if (regName == null || regName.isBlank()) {
return null;
}
Register register = lang.getRegister(regName);
if (register == null) {
throw new IOException("Unknown register: " + regName);
}
return register;
}
private Set<GoVer> parseValidGoVersionsStr(String s) throws IOException {
if (s.trim().equalsIgnoreCase("all")) {
EnumSet<GoVer> allVers = EnumSet.allOf(GoVer.class);
allVers.remove(GoVer.UNKNOWN);
return allVers;
}
EnumSet<GoVer> result = EnumSet.noneOf(GoVer.class);
for (String verStr : s.split(",")) {
verStr = verStr.trim();
if (verStr.isEmpty()) {
continue;
}
try {
GoVer ver = GoVer.valueOf(verStr);
result.add(ver);
}
catch (IllegalArgumentException e) {
throw new IOException("Unknown go version: " + verStr);
}
}
return result;
}
}

View file

@ -15,6 +15,9 @@
*/
package ghidra.app.util.bin.format.golang;
import ghidra.framework.options.Options;
import ghidra.program.model.listing.Program;
/**
* Golang version numbers
*/
@ -88,4 +91,16 @@ public enum GoVer {
}
return UNKNOWN;
}
public static final String GOLANG_VERSION_PROPERTY_NAME = "Golang go version";
public static GoVer fromProgramProperties(Program program) {
Options props = program.getOptions(Program.PROGRAM_INFO);
String verStr = props.getString(GOLANG_VERSION_PROPERTY_NAME, null);
return verStr != null ? parse(verStr) : UNKNOWN;
}
public static void setProgramPropertiesWithOriginalVersionString(Options props, String s) {
props.setString(GOLANG_VERSION_PROPERTY_NAME, s);
}
}

View file

@ -0,0 +1,352 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.plugin.core.analysis.GolangSymbolAnalyzer;
import ghidra.app.util.bin.format.dwarf4.DIEAggregate;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage;
import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup;
import ghidra.app.util.bin.format.dwarf4.next.*;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.SymbolType;
import ghidra.util.Msg;
import ghidra.util.classfinder.ExtensionPointProperties;
import ghidra.util.exception.DuplicateNameException;
/**
* Fixups for golang functions.
* <p>
* Fixes storage of parameters to match the go callspec and modifies parameter lists to match
* Ghidra's capabilities.
* <p>
* Special characters used by golang in symbol names are fixed up in
* DWARFProgram.fixupSpecialMeaningCharacters():
* <li>"\u00B7" (middle dot) -> "."
* <li>"\u2215" (weird slash) -> "/"
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY)
public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
public static final CategoryPath GOLANG_API_EXPORT =
new CategoryPath(CategoryPath.ROOT, "GolangAPIExport");
public static boolean isGolangFunction(DWARFFunction dfunc) {
DIEAggregate diea = dfunc.diea;
int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage();
if (cuLang != DWARFSourceLanguage.DW_LANG_Go) {
return false;
}
// sanity check: gofuncs always have a void return type in dwarf
if (!dfunc.retval.type.isEquivalent(VoidDataType.dataType)) {
return false;
}
return true;
}
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
if (!isGolangFunction(dfunc)) {
return;
}
GoVer goVersion = getGolangVersion(dfunc);
if (goVersion == GoVer.UNKNOWN) {
return;
}
DataTypeManager dtm = gfunc.getProgram().getDataTypeManager();
GoParamStorageAllocator storageAllocator =
new GoParamStorageAllocator(gfunc.getProgram(), goVersion);
if (GoFunctionFixup.isGolangAbi0Func(gfunc)) {
// Some (typically lower level) functions in the binary will be marked with a
// symbol that ends in the string "abi0".
// Throw away all registers and force stack allocation for everything
storageAllocator.setAbi0Mode();
dfunc.prototypeModel = storageAllocator.getAbi0CallingConvention();
}
else {
dfunc.prototypeModel = storageAllocator.getAbiInternalCallingConvention();
}
GoFunctionMultiReturn multiReturnInfo = fixupFormalFuncDef(dfunc, storageAllocator, dtm);
fixupCustomStorage(dfunc, gfunc, storageAllocator, dtm, multiReturnInfo);
}
private GoFunctionMultiReturn fixupFormalFuncDef(DWARFFunction dfunc,
GoParamStorageAllocator storageAllocator, DataTypeManager dtm) {
// Go funcs can have multiple return values, which are marked up in dwarf as parameters with
// a special boolean flag. Unnamed return values typically have a "~r0", "~r1", etc name
// auto-assigned.
// Pull them out of the param list and create a structure to hold them as the return value
// They also need to be sorted so that stack storage items appear last, after register items.
List<DWARFVariable> realParams = new ArrayList<>();
List<DWARFVariable> returnParams = new ArrayList<>();
for (DWARFVariable dvar : dfunc.params) {
if (dvar.isOutputParameter) {
returnParams.add(dvar);
}
else {
realParams.add(dvar);
}
}
DataType returnType = VoidDataType.dataType;
GoFunctionMultiReturn multiReturn = null;
if (returnParams.size() == 1) {
returnType = returnParams.get(0).type;
}
else if (returnParams.size() > 1) {
multiReturn = new GoFunctionMultiReturn(returnParams, dfunc, dtm, storageAllocator);
returnType = multiReturn.getStruct();
}
dfunc.retval = DWARFVariable.fromDataType(dfunc, returnType);
dfunc.params = realParams;
dfunc.varArg = false; // golang varargs are implemented via slice parameter, so this is always false
return multiReturn;
}
private void fixupCustomStorage(DWARFFunction dfunc, Function gfunc,
GoParamStorageAllocator storageAllocator, DataTypeManager dtm,
GoFunctionMultiReturn multiReturn) {
//
// This method implements the pseudo-code in
// https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md.
//
// Allocate custom storage for each parameter
List<DWARFVariable> spillVars = new ArrayList<>();
for (DWARFVariable dvar : dfunc.params) {
List<Register> regStorage = storageAllocator.getRegistersFor(dvar.type);
if (regStorage != null && !regStorage.isEmpty()) {
dvar.setRegisterStorage(regStorage);
spillVars.add(dvar);
if (dvar.type instanceof Structure &&
dvar.getStorageSize() != dvar.type.getLength()) {
Msg.warn(GoFunctionFixup.class,
"Known storage allocation problem: func %s@%s param %s register allocation for structs missing inter-field padding."
.formatted(dfunc.name.getName(), dfunc.address,
dvar.name.getName()));
}
}
else {
if (!dvar.isZeroByte()) {
long stackOffset = storageAllocator.getStackAllocation(dvar.type);
dvar.setStackStorage(stackOffset);
}
else {
if (dvar.isEmptyArray()) {
dvar.type = GoFunctionFixup.makeEmptyArrayDataType(dvar.type);
}
Address zerobaseAddress = getZerobaseAddress(dfunc);
dvar.setRamStorage(zerobaseAddress.getOffset());
}
}
}
storageAllocator.alignStack();
storageAllocator.resetRegAllocation();
// Allocate custom storage for the return value
if (!dfunc.retval.isZeroByte()) {
dfunc.retval.clearStorage();
if (multiReturn != null) {
// allocate storage for individual elements of the struct because they were
// originally separate return values.
// Also turn off endianness fixups in the registers that are fetched
// because we will do it manually
for (DataTypeComponent dtc : multiReturn.getNormalStorageComponents()) {
allocateReturnStorage(dfunc, dfunc.retval,
dtc.getFieldName() + "-return-result-alias", dtc.getDataType(),
storageAllocator, false);
}
// do items marked as "stack" last (because their order was modified to match
// the decompiler's expectations for storage layout)
for (DataTypeComponent dtc : multiReturn.getStackStorageComponents()) {
allocateReturnStorage(dfunc, dfunc.retval,
dtc.getFieldName() + "-return-result-alias", dtc.getDataType(),
storageAllocator, false);
}
Program program = gfunc.getProgram();
if (!program.getMemory().isBigEndian()) {
// revserse the ordering of the storage varnodes when little-endian
List<Varnode> varnodes = dfunc.retval.getVarnodes();
GoFunctionFixup.reverseNonStackStorageLocations(varnodes);
dfunc.retval.setVarnodes(varnodes);
}
}
else {
allocateReturnStorage(dfunc, dfunc.retval, "return-value-alias-variable",
dfunc.retval.type, storageAllocator, true);
}
}
else {
if (dfunc.retval.isEmptyArray()) {
dfunc.retval.type = GoFunctionFixup.makeEmptyArrayDataType(dfunc.retval.type);
}
if (!dfunc.retval.isVoidType()) {
dfunc.retval.setRamStorage(getZerobaseAddress(dfunc).getOffset());
}
}
storageAllocator.alignStack();
// For any parameters that were passed as registers, the golang caller pre-allocates
// space on the stack for the parameter value to be used when the register is overwritten.
// Ghidra decompilation results are improved if those storage locations are covered
// by variables that we create artificially.
for (DWARFVariable dvar : spillVars) {
DWARFVariable spill = DWARFVariable.fromDataType(dfunc, dvar.type);
String paramName = dvar.name.getName() + "-spill";
spill.name = dvar.name.replaceName(paramName, paramName);
spill.setStackStorage(storageAllocator.getStackAllocation(spill.type));
dfunc.localVars.add(spill);
}
// Override "localVarErrors" because we are pretty sure go's dwarf output is
// trustworthy now that we've over-written everything.
// See SanityCheckDWARFFunctionFixup
dfunc.localVarErrors = false;
dfunc.signatureCommitMode = CommitMode.STORAGE;
}
private void allocateReturnStorage(DWARFFunction dfunc, DWARFVariable dvar, String name,
DataType dt, GoParamStorageAllocator storageAllocator, boolean allowEndianFixups) {
List<Register> regStorage = storageAllocator.getRegistersFor(dt, allowEndianFixups);
if (regStorage != null && !regStorage.isEmpty()) {
dvar.addRegisterStorage(regStorage);
}
else {
if (!DWARFUtil.isZeroByteDataType(dt)) {
long stackOffset = storageAllocator.getStackAllocation(dt);
dvar.addStackStorage(stackOffset, dt.getLength());
dfunc.localVars.add(createReturnResultAliasVar(dfunc, dt, name, stackOffset));
}
}
}
private DWARFVariable createReturnResultAliasVar(DWARFFunction dfunc, DataType dataType,
String name, long stackOffset) {
DWARFVariable returnResultVar =
DWARFVariable.fromDataType(dfunc, dataType);
returnResultVar.name = dfunc.name.createChild(name, name, SymbolType.LOCAL_VAR);
returnResultVar.setStackStorage(stackOffset);
return returnResultVar;
}
// /**
// * Create a structure that holds the multiple return values from a golang func.
// * <p>
// * The contents of the structure may not be in the same order as the formal declaration,
// * but instead are ordered to make custom varnode storage work.
// * <p>
// * Because stack varnodes must be placed in a certain order of storage, items that are
// * stack based are tagged with a text comment "stack" to allow storage to be correctly
// * recalculated later.
// *
// * @param returnParams
// * @param dfunc
// * @param dtm
// * @param storageAllocator
// * @return
// */
// public static Structure createStructForReturnValues(List<DWARFVariable> returnParams,
// DWARFFunction dfunc, DataTypeManager dtm,
// GoParamStorageAllocator storageAllocator) {
//
// String returnStructName = dfunc.name.getName() + MULTIVALUE_RETURNTYPE_SUFFIX;
// DWARFNameInfo structDNI = dfunc.name.replaceName(returnStructName, returnStructName);
// Structure struct =
// new StructureDataType(structDNI.getParentCP(), structDNI.getName(), 0, dtm);
// struct.setPackingEnabled(true);
// struct.setExplicitPackingValue(1);
//
// storageAllocator = storageAllocator.clone();
// List<DWARFVariable> stackResults = new ArrayList<>();
// // TODO: zero-length items also need to be segregated at the end of the struct
// for (DWARFVariable dvar : returnParams) {
// List<Register> regs = storageAllocator.getRegistersFor(dvar.type);
// if (regs == null || regs.isEmpty()) {
// stackResults.add(dvar);
// }
// else {
// struct.add(dvar.type, dvar.name.getName(), regs.toString());
// }
// }
//
// boolean be = dfunc.getProgram().isBigEndian();
//
// // add these to the struct last or first, depending on endianness
// for (int i = 0; i < stackResults.size(); i++) {
// DWARFVariable dvar = stackResults.get(i);
// if (be) {
// struct.add(dvar.type, dvar.name.getName(), "stack");
// }
// else {
// struct.insert(i, dvar.type, -1, dvar.name.getName(), "stack");
// }
// }
//
// return struct;
// }
private void exportOrigFuncDef(DWARFFunction dfunc, DataTypeManager dtm) {
try {
FunctionDefinition funcDef = dfunc.asFuncDef();
funcDef.setCategoryPath(GOLANG_API_EXPORT);
dtm.addDataType(funcDef, DataTypeConflictHandler.KEEP_HANDLER);
}
catch (DuplicateNameException e) {
// skip
}
}
private GoVer getGolangVersion(DWARFFunction dfunc) {
DWARFProgram dprog = dfunc.getProgram();
GoVer ver = dprog.getOpaqueProperty(GoVer.class, null, GoVer.class);
if (ver == null) {
GoBuildInfo goBuildInfo = GoBuildInfo.fromProgram(dprog.getGhidraProgram());
ver = goBuildInfo != null ? goBuildInfo.getVerEnum() : GoVer.UNKNOWN;
dprog.setOpaqueProperty(GoVer.class, ver);
}
return ver;
}
private static final String GOLANG_ZEROBASE_ADDR = "GOLANG_ZEROBASE_ADDR";
private Address getZerobaseAddress(DWARFFunction dfunc) {
DWARFProgram dprog = dfunc.getProgram();
Address zerobaseAddr = dprog.getOpaqueProperty(GOLANG_ZEROBASE_ADDR, null, Address.class);
if (zerobaseAddr == null) {
zerobaseAddr = GolangSymbolAnalyzer.getZerobaseAddress(dprog.getGhidraProgram());
dprog.setOpaqueProperty(GOLANG_ZEROBASE_ADDR, zerobaseAddr);
}
return zerobaseAddr;
}
}

View file

@ -22,7 +22,6 @@ import ghidra.app.util.bin.format.elf.ElfLoadHelper;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ReaderFunc;
import ghidra.app.util.bin.format.elf.info.ElfInfoProducer;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -42,8 +41,6 @@ import ghidra.util.task.TaskMonitor;
* </ul>
*/
public class GolangElfInfoProducer implements ElfInfoProducer {
public static final CategoryPath GO_CATEGORYPATH = new CategoryPath("/golang");
private static final Map<String, ReaderFunc<ElfInfoItem>> GOLANGINFO_READERS = Map.of(
GoBuildInfo.SECTION_NAME, GoBuildInfo::read,
NoteGoBuildId.SECTION_NAME, NoteGoBuildId::read);

View file

@ -79,7 +79,7 @@ public class NoteGoBuildId extends ElfNote {
@Override
public StructureDataType toStructure(DataTypeManager dtm) {
StructureDataType struct =
ElfNote.createNoteStructure(GolangElfInfoProducer.GO_CATEGORYPATH,
ElfNote.createNoteStructure(GoConstants.GOLANG_CATEGORYPATH,
"NoteGoBuildId_%d".formatted(description.length), false, nameLen, 0, dtm);
struct.add(StringUTF8DataType.dataType, description.length, "BuildId", null);
return struct;

View file

@ -0,0 +1,131 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang;
import java.util.Arrays;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.listing.Program;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
/**
* Similar to {@link NoteGoBuildId}, but re-implemented here because of the different
* serialization used in PE binaries. (the logic about the buildid payload is trivial so
* there is no worry about duplicating code)
* <p>
*
*/
public class PEGoBuildId implements ElfInfoItem {
private static final byte[] GO_BUILDID_MAGIC =
"\u00ff Go build ID: \"".getBytes(StandardCharsets.ISO_8859_1);
private static final int BUILDID_STR_LEN = 83;
public static ItemWithAddress<PEGoBuildId> findBuildId(Program program) {
ItemWithAddress<PEGoBuildId> wrappedItem = ElfInfoItem.readItemFromSection(program,
".text", PEGoBuildId::read);
return wrappedItem;
}
/**
* Attempts to read a PEGoBuildId from the specified stream.
*
* @param br BinaryReader stream (typically the beginning of the ".text" section)
* @param program_notused not used, but needed to match functional interface
* @return PEGoBuildId instance, or null if not present
*/
public static PEGoBuildId read(BinaryReader br, Program program_notused) {
try {
byte[] magic = br.readNextByteArray(GO_BUILDID_MAGIC.length);
if (!Arrays.equals(magic, GO_BUILDID_MAGIC)) {
return null;
}
String buildIdStr = br.readNextAsciiString(BUILDID_STR_LEN);
return new PEGoBuildId(buildIdStr);
}
catch (IOException e) {
// fall thru and return null
}
return null;
}
/**
* Attempts to read a PEGoBuildId from the specified InputStream (useful for early compiler
* detection before file is loaded).
*
* @param is {@link InputStream} providing access to the ".text" section of a PE binary
* @return PEGoBuildId instance, or null if not present
*/
public static PEGoBuildId read(InputStream is) {
byte[] buffer = new byte[GO_BUILDID_MAGIC.length + BUILDID_STR_LEN];
try {
int bytesRead = is.read(buffer);
if (bytesRead == buffer.length) {
ByteArrayProvider bap = new ByteArrayProvider(buffer);
return read(new BinaryReader(bap, false /* doesn't matter */), null);
}
}
catch (IOException e) {
// fall thru
}
return null;
}
private final String buildId;
public PEGoBuildId(String buildId) {
this.buildId = buildId;
}
public String getBuildId() {
return buildId;
}
@Override
public void markupProgram(Program program, Address address) {
program.getOptions(Program.PROGRAM_INFO).setString("Golang BuildId", getBuildId());
try {
StructureDataType struct = toStructure(program.getDataTypeManager());
if (struct != null) {
DataUtilities.createData(program, address, struct, -1, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
}
}
catch (CodeUnitInsertionException e) {
Msg.error(this, "Failed to markup PEGoBuildId at %s: %s".formatted(address, this));
}
}
private StructureDataType toStructure(DataTypeManager dtm) {
StructureDataType result =
new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildId", 0, dtm);
result.add(StringDataType.dataType, GO_BUILDID_MAGIC.length, "magic", null);
result.add(StringDataType.dataType, BUILDID_STR_LEN, "buildId", null);
return result;
}
}

View file

@ -0,0 +1,136 @@
/* ###
* 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.rtti;
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> {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoFuncData> context;
@FieldMapping
@EOLComment("description")
@MarkupReference("funcAddress")
private long entryoff;
@FieldMapping
@MarkupReference("nameAddress")
private long nameoff;
public Address getFuncAddress() {
return getModuledata().getText().add(entryoff);
}
public Address getNameAddress() {
return getModuledata().getFuncnametab().getArrayAddress().add(nameoff);
}
public String getName() throws IOException {
BinaryReader reader =
programContext.getReader(getModuledata().getFuncnametab().getArrayOffset() + nameoff);
return reader.readNextUtf8String();
}
public String getDescription() throws IOException {
return getName() + "@" + getFuncAddress();
}
public boolean isInline() {
return entryoff == -1 || entryoff == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG;
}
private GoModuledata getModuledata() {
return programContext.findContainingModuleByFuncData(context.getStructureStart());
}
@Override
public StructureContext<GoFuncData> getStructureContext() {
return context;
}
@Override
public String getStructureName() throws IOException {
return getName();
}
@Override
public void additionalMarkup() 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);
}
}
}
/*
struct runtime._func
Length: 40 Alignment: 4
{
uint32 entryoff
int32 nameoff
int32 args
uint32 deferreturn
uint32 pcsp
uint32 pcfile
uint32 pcln
uint32 npcdata
uint32 cuOffset
runtime.funcID funcID
runtime.funcFlag flag
uint8[1] _
uint8 nfuncdata
} pack()
*/

View file

@ -0,0 +1,52 @@
/* ###
* 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.rtti;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
@StructureMapping(structureName = "runtime.functab")
public class GoFunctabEntry {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoFunctabEntry> context;
@FieldMapping
@MarkupReference("funcAddress")
private long entryoff; // offset relative to module's .text
@FieldMapping
@MarkupReference("funcData")
private long funcoff; // offset into pclntable -> _func
public Address getFuncAddress() {
return getModuledata().getText().add(entryoff);
}
@Markup
public GoFuncData getFuncData() throws IOException {
return funcoff != 0 ? getModuledata().getFuncDataInstance(funcoff) : null;
}
private GoModuledata getModuledata() {
return programContext.findContainingModuleByFuncData(context.getStructureStart());
}
}

View file

@ -0,0 +1,42 @@
/* ###
* 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.rtti;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
@StructureMapping(structureName = "runtime.iface")
public class GoIface {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoIface> context;
@FieldMapping
@MarkupReference("itab")
long tab; // runtime.itab *
@FieldMapping
private long data;
@Markup
public GoItab getItab() throws IOException {
return programContext.readStructure(GoItab.class, tab);
}
}

View file

@ -0,0 +1,126 @@
/* ###
* 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.rtti;
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.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
@PlateComment
@StructureMapping(structureName = "runtime.itab")
public class GoItab implements StructureMarkup<GoItab> {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoItab> context;
@FieldMapping
@MarkupReference("interfaceType")
long inter; // runtime.interfacetype *
@FieldMapping
@MarkupReference("type")
long _type; // runtime._type *
@FieldMapping
long fun; // inline varlen array, specd as uintptr[1], we are treating as simple long
@Markup
public GoInterfaceType getInterfaceType() throws IOException {
return programContext.readStructure(GoInterfaceType.class, inter);
}
@Markup
public GoType getType() throws IOException {
return programContext.getGoType(_type);
}
public long getFuncCount() throws IOException {
GoInterfaceType iface = getInterfaceType();
GoSlice methods = iface.getMethodsSlice();
return Math.max(1, methods.getLen());
}
public GoSlice getFunSlice() throws IOException {
long funcCount = getFuncCount();
long funOffset = context.getStructureEnd() - programContext.getPtrSize();
return new GoSlice(funOffset, funcCount, funcCount, programContext);
}
@Override
public String getStructureName() throws IOException {
return getInterfaceType().getStructureName();
}
@Override
public StructureContext<GoItab> getStructureContext() {
return context;
}
@Override
public void additionalMarkup() throws IOException {
GoSlice funSlice = getFunSlice();
List<Address> funcAddrs = Arrays.stream(funSlice.readUIntList(programContext.getPtrSize()))
.mapToObj(offset -> programContext.getCodeAddress(offset))
.collect(Collectors.toList());
// 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);
GoSlice extraFunSlice =
funSlice.getSubSlice(1, funSlice.getLen() - 1, programContext.getPtrSize());
extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", (DataType) null,
true);
}
@Override
public String toString() {
try {
String s = "itab for " + getStructureName();
GoInterfaceType ifaceType = getInterfaceType();
String methodListString = ifaceType.getMethodListString();
if (!methodListString.isEmpty()) {
s += "\n// Methods\n" + methodListString;
}
return s;
}
catch (IOException e) {
return super.toString();
}
}
}
/*
struct runtime.itab
Length: 20 Alignment: 4
{
runtime.interfacetype * inter
runtime._type * _type
uint32 hash
uint8[4] _
uintptr[1] fun
} pack()
*/

View file

@ -0,0 +1,328 @@
/* ###
* 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.rtti;
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.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.StringUTF8DataType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* Represents a golang moduledata structure, which contains a lot of invaluable bootstrapping
* data for RTTI and function data.
*/
@StructureMapping(structureName = "runtime.moduledata")
public class GoModuledata implements StructureMarkup<GoModuledata> {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoModuledata> structureContext;
@FieldMapping
@MarkupReference
private long pcHeader; // pointer to the GoPcHeader instance, useful for bootstrapping
@FieldMapping
@MarkupReference
private long text;
@FieldMapping(fieldName = "types")
private long typesOffset;
@FieldMapping(fieldName = "etypes")
private long typesEndOffset;
@FieldMapping(fieldName = "typelinks")
private GoSlice typeLinks;
@FieldMapping
private GoSlice funcnametab; // []uint8 blob of null term strings
@FieldMapping
private GoSlice cutab; // []uint32
@FieldMapping
private GoSlice filetab; // []uint8 blob of null term strings
@FieldMapping
private GoSlice pctab; // []uint8
@FieldMapping
private GoSlice pclntable; // []uint8, shares footprint with ftab
@FieldMapping
private GoSlice ftab; // []runtime.functab, shares footprint with pclntable
@FieldMapping
private GoSlice itablinks; // []*runtime.itab (array of pointers to runtime.tab)
public GoModuledata() {
}
public boolean matchesPclntab(GoPcHeader pclntab) {
return pclntab.getTextStart().equals(getText()) &&
pclntab.getFuncnameAddress().equals(funcnametab.getArrayAddress());
}
@Markup
public GoPcHeader getPcHeader() throws IOException {
return programContext.readStructure(GoPcHeader.class, pcHeader);
}
public Address getText() {
return programContext.getCodeAddress(text);
}
public long getTypesOffset() {
return typesOffset;
}
public long getTypesEndOffset() {
return typesEndOffset;
}
public GoFuncData getFuncDataInstance(long offset) throws IOException {
return programContext.readStructure(GoFuncData.class, pclntable.getArrayOffset() + offset);
}
public boolean containsFuncDataInstance(long offset) {
return pclntable.isOffsetWithinData(offset, 1);
}
public boolean isValid() {
MemoryBlock txtBlock = programContext.getProgram().getMemory().getBlock(".text");
if (txtBlock != null && txtBlock.getStart().getOffset() != text) {
return false;
}
MemoryBlock typelinkBlock = programContext.getProgram().getMemory().getBlock(".typelink");
if (typelinkBlock != null &&
typelinkBlock.getStart().getOffset() != typeLinks.getArrayOffset()) {
return false;
}
// all these static slices should be allocated with len == cap. If not true, fail.
if (!typeLinks.isFull() || !filetab.isFull() || !pctab.isFull() || !pclntable.isFull() ||
!ftab.isFull()) {
return false;
}
return true;
}
public GoSlice getFuncnametab() {
return funcnametab;
}
@Override
public StructureContext<GoModuledata> getStructureContext() {
return structureContext;
}
@Override
public void additionalMarkup() throws IOException {
typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false);
typeLinks.markupElementReferences(4, getTypeList());
itablinks.markupArray("moduledata.itablinks", GoItab.class, true);
//cutab.markupArray("moduledata.cutab", programContext.getUint32DT(), false);
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen());
markupStringTable(filetab.getArrayAddress(), filetab.getLen());
if (ftab.getLen() > 0) {
// chop off the last entry as it is not a full entry (it just points to the address
// at the end of the text segment) and can conflict with markup of the following structs
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);
}
}
@Markup
public List<GoItab> getItabs() throws IOException {
List<GoItab> result = new ArrayList<>();
long[] itabAddrs = itablinks.readUIntList(programContext.getPtrSize());
for (long itabAddr : itabAddrs) {
GoItab itab = programContext.readStructure(GoItab.class, itabAddr);
result.add(itab);
}
return result;
}
private void markupStringTable(Address addr, long stringTableLength) {
DataType stringDT = StringUTF8DataType.dataType;
long startOfString = addr.getOffset();
long endOfStringTable = startOfString + stringTableLength;
BinaryReader reader = programContext.getReader(startOfString);
try {
while (startOfString < endOfStringTable) {
reader.readNextUtf8String(); // don't care about string, just stream position after read
long len = reader.getPointerIndex() - startOfString;
if (len > 0 && len < Integer.MAX_VALUE) {
Address stringAddr = addr.getNewAddress(startOfString);
programContext.markupAddress(stringAddr, stringDT, (int) len);
}
startOfString = reader.getPointerIndex();
}
}
catch (IOException e) {
Msg.warn(this, "Failed when marking up string table at: " + addr, e);
}
}
@Markup
public Iterator<GoType> iterateTypes() throws IOException {
return getTypeList().stream()
.map(addr -> {
try {
return programContext.getGoType(addr);
}
catch (IOException e) {
return null;
}
})
.filter(Objects::nonNull)
.iterator();
}
public List<Address> getTypeList() throws IOException {
long[] typeOffsets = typeLinks.readUIntList(4 /* always sizeof(int32) */);
Address typesBaseAddr = programContext.getDataAddress(typesOffset);
List<Address> result = Arrays.stream(typeOffsets)
.mapToObj(offset -> typesBaseAddr.add(offset))
.collect(Collectors.toList());
return result;
}
//--------------------------------------------------------------------------------------------
/**
* Returns an easily found first GoModuledata instance.
*
* @param context already initialized {@link GoRttiContext}
* @return new GoModuledata instance, or null if not found
* @throws IOException
*/
/* package */ static GoModuledata getFirstModuledata(GoRttiContext context)
throws IOException {
Program program = context.getProgram();
Symbol firstModuleDataSymbol =
SymbolUtilities.getUniqueSymbol(program, "runtime.firstmoduledata");
if (firstModuleDataSymbol == null) {
return null;
}
return context.readStructure(GoModuledata.class, firstModuleDataSymbol.getAddress());
}
/**
* Searches memory for a likely GoModuledata
*
* @param context already initialized {@link GoRttiContext}
* @param pclntabAddress address of an already found {@link GoPcHeader}
* @param pclntab the {@link GoPcHeader}
* @param range memory range to search. Will be different for different types of binaries
* @param monitor {@link TaskMonitor}
* @return new GoModuledata instance, or null if not found
* @throws IOException
*/
/* package */ static GoModuledata findFirstModule(GoRttiContext context,
Address pclntabAddress, GoPcHeader pclntab, AddressRange range, TaskMonitor monitor)
throws IOException {
if (range == null) {
return null;
}
Program program = context.getProgram();
Memory memory = program.getMemory();
int ptrSize = context.getPtrSize();
byte[] searchBytes = new byte[ptrSize];
context.getDataConverter().putValue(pclntabAddress.getOffset(), ptrSize, searchBytes, 0);
Address moduleAddr = memory.findBytes(range.getMinAddress(), range.getMaxAddress(),
searchBytes, null, true, monitor);
if (moduleAddr == null) {
return null;
}
GoModuledata moduleData = context.readStructure(GoModuledata.class, moduleAddr);
return moduleData.matchesPclntab(pclntab) ? moduleData : null;
}
}
/*
struct runtime.moduledata Length:276 Alignment:4{
runtime.pcHeader*pcHeader
[]uint8 funcnametab
[]uint32 cutab
[]uint8 filetab
[]uint8 pctab
[]uint8 pclntable
[]runtime.functab ftab
uintptr findfunctab
uintptr minpc
uintptr maxpc
uintptr text
uintptr etext
uintptr noptrdata
uintptr enoptrdata
uintptr data
uintptr edata
uintptr bss
uintptr ebss
uintptr noptrbss
uintptr enoptrbss
uintptr end
uintptr gcdata
uintptr gcbss
uintptr types
uintptr etypes
uintptr rodata
uintptr gofunc
[]runtime.textsect textsectmap
[]int32 typelinks
[]*runtime.itab itablinks
[]
runtime.ptabEntry ptab
string pluginpath
[]runtime.modulehash pkghashes
string modulename
[]runtime.modulehash modulehashes
uint8 hasmain
runtime.bitvector gcdatamask
runtime.bitvector gcbssmask map[runtime.typeOff]*runtime._type typemap
bool bad runtime.moduledata*next
}pack()*/

View file

@ -0,0 +1,147 @@
/* ###
* 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.rtti;
import java.util.EnumSet;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.DataType;
/**
* Represents a golang "name" construct, which isn't represented in go as a normal structure
* since it is full of variable length and optional fields.
* <pre>
* struct {
* byte flag;
* varint strlen;
* char[strlen] chars;
* (optional: varint tag_strlen; char [tag_strlen];)
* (optional: int32 pkgpath)
* }
* </pre>
* Because this type has variable length fields (@FieldOutput(isVariableLength=true)), there will
* be unique structure data types produced for each size combination of a GoName structure, and
* will be named "GoName_N_M", where N and M are the lengths of the variable fields [name, tag]
*/
@StructureMapping(structureName = "GoName")
public class GoName implements StructureReader<GoName>, StructureMarkup<GoName> {
public enum Flag {
EXPORTED(1 << 0),
HAS_TAG(1 << 1),
HAS_PKGPATH(1 << 2),
EMBEDDED(1 << 3);
private final int flagValue;
private Flag(int flagValue) {
this.flagValue = flagValue;
}
public boolean isSet(int value) {
return (value & flagValue) != 0;
}
public static Set<Flag> parseFlags(int b) {
EnumSet<Flag> result = EnumSet.noneOf(Flag.class);
for (Flag flag : values()) {
if (flag.isSet(b)) {
result.add(flag);
}
}
return result;
}
}
@ContextField
private StructureContext<GoName> context;
@ContextField
private GoRttiContext programContext;
@FieldOutput(dataTypeName = "byte")
@EOLComment("flagsSet")
int flags;
@FieldOutput(isVariableLength = true)
@EOLComment("fullNameString")
GoVarlenString name;
@FieldOutput(isVariableLength = true)
GoVarlenString tag;
@FieldOutput(isVariableLength = true, getter = "pkgPathDataType")
@MarkupReference("pkgPath")
long pkgPath; // uint32, nameoffset, only present if flags.HAS_PKGPATH
@Override
public void readStructure() throws IOException {
flags = context.getReader().readNextUnsignedByte();
name = programContext.readStructure(GoVarlenString.class, context.getReader());
tag = Flag.HAS_TAG.isSet(flags)
? programContext.readStructure(GoVarlenString.class, context.getReader())
: null;
pkgPath = Flag.HAS_PKGPATH.isSet(flags)
? context.getReader().readNextUnsignedInt()
: 0;
}
public String getName() {
return name.getString();
}
public String getTag() {
return tag != null ? tag.getString() : "";
}
@Markup
public GoName getPkgPath() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), pkgPath);
}
public DataType getPkgPathDataType() {
return Flag.HAS_PKGPATH.isSet(flags)
? programContext.getInt32DT()
: null;
}
public String getFullNameString() throws IOException {
GoName pkgPathName = getPkgPath();
return (pkgPathName != null ? pkgPathName.getFullNameString() + "." : "") + getName();
}
public int getFlags() {
return flags;
}
public Set<Flag> getFlagsSet() {
return Flag.parseFlags(flags);
}
@Override
public StructureContext<GoName> getStructureContext() {
return context;
}
@Override
public String getStructureName() throws IOException {
return getName();
}
}

View file

@ -0,0 +1,239 @@
/* ###
* 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.rtti;
import java.io.IOException;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.golang.GoVer;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
/**
* A low-level structure embedded in golang binaries that contains useful bootstrapping
* information.
* <p>
*
*/
@StructureMapping(structureName = "runtime.pcHeader")
public class GoPcHeader {
private static final String RUNTIME_PCLNTAB_SYMBOLNAME = "runtime.pclntab";
public static final String GOPCLNTAB_SECTION_NAME = ".gopclntab";
public static final int GO_1_2_MAGIC = 0xfffffffb;
public static final int GO_1_16_MAGIC = 0xfffffffa;
public static final int GO_1_18_MAGIC = 0xfffffff0;
/**
* Returns the {@link Address} (if present) of the go pclntab section or symbol.
*
* @param program {@link Program}
* @return {@link Address} of go pclntab, or null if not present
*/
public static Address getPclntabAddress(Program program) {
MemoryBlock pclntabBlock = program.getMemory().getBlock(GOPCLNTAB_SECTION_NAME);
if (pclntabBlock != null) {
return pclntabBlock.getStart();
}
// PE binaries have a symbol instead of a named section
Symbol pclntabSymbol = SymbolUtilities.getUniqueSymbol(program, RUNTIME_PCLNTAB_SYMBOLNAME);
return pclntabSymbol != null
? pclntabSymbol.getAddress()
: null;
}
/**
* Returns true if the specified program has an easily found pclntab
*
* @param program {@link Program}
* @return boolean true if program has a pclntab, false otherwise
*/
public static boolean hasPclntab(Program program) {
Address addr = getPclntabAddress(program);
if (addr != null) {
try (ByteProvider provider = new MemoryByteProvider(program.getMemory(), addr)) {
return isPclntab(provider);
}
catch (IOException e) {
// fall thru
}
}
return false;
}
/**
* Searches (possibly slowly) for a pclntab structure in the specified memory range, which
* is typically necessary in stripped PE binaries.
*
* @param programContext {@link GoRttiContext}
* @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
*/
public static Address findPclntabAddress(GoRttiContext programContext, AddressRange range,
TaskMonitor monitor) throws IOException {
if (range == null) {
return null;
}
// search for magic signature + padding + wildcard_minLc + ptrSize
byte[] searchBytes = new byte[/*4 + 2 + 1 + 1*/] {
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // magic signature
0, 0, // padding
0, // unknown minLc, masked
(byte) programContext.getPtrSize() // ptrSize
};
byte[] searchMask = new byte[] {
(byte) 0xf0, (byte) 0xff, (byte) 0xff, (byte) 0xf0, // magic, first byte nibble and last byte nibble is wildcard to handle either endian matching
(byte) 0xff, (byte) 0xff, // padding
0, // unknown minLc - wildcarded
(byte) 0xff // ptrSize
};
Memory memory = programContext.getProgram().getMemory();
Address pclntabAddr =
memory.findBytes(range.getMinAddress(), range.getMaxAddress(), searchBytes, searchMask,
true, TaskMonitor.DUMMY);
if (pclntabAddr == null) {
return null;
}
MemoryByteProvider bp =
new MemoryByteProvider(memory, pclntabAddr, range.getMaxAddress());
return isPclntab(bp) ? pclntabAddr : null;
}
/**
* Returns true if there is a pclntab at the current position of the specified ByteProvider.
*
* @param provider {@link ByteProvider}
* @return boolean true if the byte provider has the magic signature of a pclntab
* @throws IOException
*/
public static boolean isPclntab(ByteProvider provider) throws IOException {
byte[] header = provider.readBytes(0, 8);
// logic from pclntab.go parsePclnTab()
if (provider.length() < 16 ||
header[4] != 0 || header[5] != 0 || // pad bytes == 0
(header[6] != 1 && header[6] != 2 && header[6] != 4) || // minLc == 1,2,4
(header[7] != 4 && header[7] != 8) // ptrSize == 4,8
) {
return false;
}
return readMagic(provider) != null;
}
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoPcHeader> context;
@FieldMapping
@EOLComment("goVersion")
private int magic;
@FieldMapping
private byte ptrSize;
@FieldMapping
@MarkupReference
private long textStart; // should be same as offset of ".text"
@FieldMapping
@MarkupReference("funcnameAddress")
private long funcnameOffset;
@FieldMapping
@MarkupReference("cuAddress")
private long cuOffset;
@FieldMapping
@MarkupReference("filetabAddress")
private long filetabOffset;
@FieldMapping
@MarkupReference("pctabAddress")
private long pctabOffset;
@FieldMapping
@MarkupReference("pclnAddress")
private long pclnOffset;
public GoVer getGoVersion() {
// TODO: this might be better as a static helper method that can be used by multiple
// GoPcHeader struct versions (if necessary)
GoVer ver = switch (magic) {
case GO_1_2_MAGIC -> GoVer.V1_2;
case GO_1_16_MAGIC -> GoVer.V1_16;
case GO_1_18_MAGIC -> GoVer.V1_18;
default -> GoVer.UNKNOWN;
};
return ver;
}
public Address getTextStart() {
return programContext.getDataAddress(textStart);
}
public Address getFuncnameAddress() {
return programContext.getDataAddress(context.getStructureStart() + funcnameOffset);
}
public Address getCuAddress() {
return programContext.getDataAddress(context.getStructureStart() + cuOffset);
}
public Address getFiletabAddress() {
return programContext.getDataAddress(context.getStructureStart() + filetabOffset);
}
public Address getPctabAddress() {
return programContext.getDataAddress(context.getStructureStart() + pctabOffset);
}
public Address getPclnAddress() {
return programContext.getDataAddress(context.getStructureStart() + pclnOffset);
}
//--------------------------------------------------------------------------------------------
record GoVerEndian(GoVer goVer, Endian endian) {
GoVerEndian(GoVer goVer, boolean isLittleEndian) {
this(goVer, isLittleEndian ? Endian.LITTLE : Endian.BIG);
}
}
private static GoVerEndian readMagic(ByteProvider provider) throws IOException {
int leMagic = new BinaryReader(provider, true /* little endian */).readInt(0);
int beMagic = new BinaryReader(provider, false /* big endian */).readInt(0);
if (leMagic == GO_1_2_MAGIC || beMagic == GO_1_2_MAGIC) {
return new GoVerEndian(GoVer.V1_2, leMagic == GO_1_2_MAGIC);
}
else if (leMagic == GO_1_16_MAGIC || beMagic == GO_1_16_MAGIC) {
return new GoVerEndian(GoVer.V1_16, leMagic == GO_1_16_MAGIC);
}
else if (leMagic == GO_1_18_MAGIC || beMagic == GO_1_18_MAGIC) {
return new GoVerEndian(GoVer.V1_18, leMagic == GO_1_18_MAGIC);
}
return null;
}
}

View file

@ -0,0 +1,595 @@
/* ###
* 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.rtti;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram;
import ghidra.app.util.bin.format.golang.*;
import ghidra.app.util.bin.format.golang.rtti.types.*;
import ghidra.app.util.bin.format.golang.structmapping.ProgramContext;
import ghidra.app.util.bin.format.golang.structmapping.StructureMapping;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.app.util.opinion.PeLoader;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
/**
* {@link ProgramContext} for golang binaries.
* <p>
* When bootstrapping golang binaries, the following steps are used:
* <ul>
* <li>Find the GoBuildInfo struct. This struct is the easiest to locate, even when the binary
* is stripped. This gives us the go pointerSize (probably same as ghidra pointer size) and the
* goVersion. This struct does not rely on StructureMapping, allowing its use before a
* ProgramContext is created.
* <li>Create ProgramContext
* <li>Find the runtime.firstmoduledata structure.
* <ul>
* <li>If there are symbols, just use the symbol or named memory block.
* <li>If stripped:
* <ul>
* <li>Find the pclntab. This has a magic signature, a pointerSize, and references
* to a couple of tables that are also referenced in the moduledata structure.
* <li>Search memory for a pointer to the pclntab struct. This should be the first
* field of the moduledata structure. The values that are duplicated between the
* two structures can be compared to ensure validity.
* <li>Different binary formats (Elf vs PE) will determine which memory blocks to
* search.
* </ul>
* </ul>
* </ul>
*/
public class GoRttiContext extends ProgramContext {
/**
* Returns a new {@link GoRttiContext} for the specified program, or null if the binary
* is not a supported golang binary.
*
* @param program {@link Program}
* @param log
* @return new {@link GoRttiContext}, or null if not a golang binary
* @throws IOException
*/
public static GoRttiContext getContextFor(Program program, MessageLog log) throws IOException {
GoBuildInfo buildInfo = GoBuildInfo.fromProgram(program);
GoVer goVer;
if (buildInfo == null || (goVer = buildInfo.getVerEnum()) == GoVer.UNKNOWN) {
return null;
}
ResourceFile gdtFile =
findGolangBootstrapGDT(goVer, buildInfo.getPointerSize(), getGolangOSString(program));
if (gdtFile == null) {
Msg.error(GoRttiContext.class, "Missing golang gdt archive for " + goVer);
}
try {
return new GoRttiContext(program, buildInfo.getPointerSize(), buildInfo.getEndian(),
buildInfo.getVerEnum(), gdtFile);
}
catch (IllegalArgumentException e) {
// user deserves a warning because the binary wasn't supported
log.appendMsg(e.getMessage());
return null;
}
}
public static String getGDTFilename(GoVer goVer, int pointerSizeInBytes, String osName) {
String bitSize = pointerSizeInBytes > 0
? Integer.toString(pointerSizeInBytes * 8)
: "any";
String gdtFilename =
"golang_%d.%d_%sbit_%s.gdt".formatted(goVer.getMajor(), goVer.getMinor(),
bitSize, osName);
return gdtFilename;
}
public static String getGolangOSString(Program program) {
String loaderName = program.getExecutableFormat();
if (ElfLoader.ELF_NAME.equals(loaderName)) {
// TODO: this will require additional work to map all Golang OSs to Ghidra loader info
return "linux";
}
else if (PeLoader.PE_NAME.equals(loaderName)) {
return "win";
}
else {
return null;
}
}
/**
* Searches for a golang bootstrap gdt file that matches the specified Go version/size/OS.
* <p>
* First looks for a gdt with an exact match, then for a gdt with version/size match and
* "any" OS, and finally, a gdt that matches the version and "any" size and "any" OS.
*
* @param goVer version of Go
* @param ptrSize size of pointers
* @param osName name of OS
* @return
*/
public static ResourceFile findGolangBootstrapGDT(GoVer goVer, int ptrSize, String osName) {
ResourceFile result = null;
if (osName != null) {
result = DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, ptrSize, osName));
}
if (result == null) {
result = DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, ptrSize, "any"));
}
if (result == null) {
result =
DataTypeArchiveUtility.findArchiveFile(getGDTFilename(goVer, -1 /*any*/, "any"));
}
return result;
}
private static final CategoryPath RECOVERED_TYPES_CP = new CategoryPath("/golang-recovered");
private static final CategoryPath GOLANG_CP = GoConstants.GOLANG_CATEGORYPATH;
private static final CategoryPath VARLEN_STRUCTS_CP = GoConstants.GOLANG_CATEGORYPATH;
/**
* List of java classes that are {@link StructureMapping structure mapped}.
* <p>
* If a class isn't included in this list, it can't be used.
*/
private static final List<Class<?>> GOLANG_STRUCTMAPPED_CLASSES =
List.of(GoModuledata.class, GoName.class, GoVarlenString.class, GoSlice.class,
GoBaseType.class, GoTypeDetector.class, GoPlainType.class, GoUncommonType.class,
GoArrayType.class, GoChanType.class, GoFuncType.class, GoInterfaceType.class,
GoMapType.class, GoPointerType.class, GoSliceType.class, GoIface.class,
GoStructType.class, GoMethod.class, GoStructField.class, GoIMethod.class,
GoFunctabEntry.class, GoFuncData.class, GoItab.class, GoString.class, GoPcHeader.class);
private final BinaryReader reader;
private final GoVer goVersion;
private final int ptrSize;
private final Endian endian;
private final DataType uintptrDT;
private final DataType int32DT;
private final DataType uint32DT;
private final Map<Long, GoType> goTypes = new HashMap<>();
private final Map<String, GoType> typeNameIndex = new HashMap<>();
private final Map<Long, DataType> cachedRecoveredDataTypes = new HashMap<>();
private final List<GoModuledata> modules = new ArrayList<>();
private GoType mapGoType;
private GoType chanGoType;
public GoRttiContext(Program program, int ptrSize, Endian endian, GoVer goVersion,
ResourceFile archiveGDT) throws IOException {
super(program, archiveGDT);
this.goVersion = goVersion;
this.ptrSize = ptrSize;
this.endian = endian;
reader = super.createProgramReader();
addArchiveSearchCategoryPath(CategoryPath.ROOT, GOLANG_CP);
addProgramSearchCategoryPath(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH);
this.uintptrDT = getTypeOrDefault("uintptr", DataType.class,
AbstractIntegerDataType.getUnsignedDataType(ptrSize, getDTM()));
this.int32DT = getTypeOrDefault("int32", DataType.class,
AbstractIntegerDataType.getSignedDataType(4, null));
this.uint32DT = getTypeOrDefault("uint32", DataType.class,
AbstractIntegerDataType.getUnsignedDataType(4, null));
try {
registerStructures(GOLANG_STRUCTMAPPED_CLASSES);
}
catch (IOException e) {
if (archiveGDT == null) {
// a normal'ish situation where there isn't a .gdt for this arch/binary and there
// isn't any DWARF.
throw new IllegalArgumentException(
"Missing golang .gdt archive for %s, no fallback DWARF info, unable to extract golang RTTI info."
.formatted(goVersion));
}
// a bad situation where the data type info is corrupted
throw new IOException("Invalid or missing Golang bootstrap GDT file: %s"
.formatted(archiveGDT.getAbsolutePath()));
}
}
public GoVer getGolangVersion() {
return goVersion;
}
public GoModuledata getFirstModule() {
return modules.get(0);
}
public void addModule(GoModuledata module) {
modules.add(module);
}
public GoModuledata findContainingModule(long offset) {
for (GoModuledata module : modules) {
if (module.getTypesOffset() <= offset && offset < module.getTypesEndOffset()) {
return module;
}
}
return null;
}
public GoModuledata findContainingModuleByFuncData(long offset) {
for (GoModuledata module : modules) {
if (module.containsFuncDataInstance(offset)) {
return module;
}
}
return null;
}
@Override
public CategoryPath getDefaultVariableLengthStructCategoryPath() {
return VARLEN_STRUCTS_CP;
}
public DataType getUintptrDT() {
return uintptrDT;
}
public DataType getInt32DT() {
return int32DT;
}
public DataType getUint32DT() {
return uint32DT;
}
public Structure getGenericSliceDT() {
return getStructureDataType(GoSlice.class);
}
public GoType getMapGoType() {
return mapGoType;
}
public GoType getChanGoType() {
return chanGoType;
}
@Override
protected BinaryReader createProgramReader() {
return reader.clone();
}
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);
}
public GoType getGoType(long offset) throws IOException {
if (offset == 0) {
return null;
}
GoType goType = goTypes.get(offset);
if (goType == null) {
Class<? extends GoType> typeClass = GoType.getSpecializedTypeClass(this, offset);
goType = readStructure(typeClass, offset);
goTypes.put(offset, goType);
}
return goType;
}
public GoType getGoType(Address addr) throws IOException {
return getGoType(addr.getOffset());
}
public GoType findGoType(String typeName) {
return typeNameIndex.get(typeName);
}
public <T extends DataType> T getGhidraDataType(String goTypeName, Class<T> clazz) {
T dt = getType(goTypeName, clazz);
if (dt == null) {
GoType goType = findGoType(goTypeName);
if (goType != null) {
try {
DataType tmpDT = goType.recoverDataType();
if (clazz.isInstance(tmpDT)) {
dt = clazz.cast(tmpDT);
}
}
catch (IOException e) {
Msg.warn(this, "Failed to get Ghidra data type from go type: %s[%x]"
.formatted(goTypeName,
goType.getStructureContext().getStructureStart()));
}
}
}
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.
* <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
*/
public void exportTypesToGDT(File gdtFile, TaskMonitor monitor) throws IOException {
List<DataType> registeredStructDTs = mappingInfo.values()
.stream()
.map(smi -> {
if (smi.getStructureDataType() == null) {
return null;
}
DataType existingDT = findType(smi.getStructureName(),
List.of(DWARFProgram.DWARF_ROOT_CATPATH, DWARFProgram.UNCAT_CATPATH),
programDTM);
if (existingDT == null) {
existingDT = smi.getStructureDataType();
}
if (existingDT == null) {
Msg.warn(this, "Missing type: " + smi.getDescription());
}
return existingDT;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// Copy the data types into a tmp gdt, and then copy them again into the final gdt
// to avoid traces of the original program name as a deleted source archive link in the
// gdt data base. This method only leaves the target gdt filename + ".step1" in the db.
File tmpGDTFile = new File(gdtFile.getParentFile(), gdtFile.getName() + ".step1.gdt");
FileDataTypeManager tmpFdtm = FileDataTypeManager.createFileArchive(tmpGDTFile);
int tx = -1;
try {
tx = tmpFdtm.startTransaction("Import");
tmpFdtm.addDataTypes(registeredStructDTs, DataTypeConflictHandler.DEFAULT_HANDLER,
monitor);
moveAllDataTypesTo(tmpFdtm, DWARFProgram.DWARF_ROOT_CATPATH, GOLANG_CP);
for (SourceArchive sa : tmpFdtm.getSourceArchives()) {
tmpFdtm.removeSourceArchive(sa);
}
tmpFdtm.getRootCategory()
.removeCategory(DWARFProgram.DWARF_ROOT_CATPATH.getName(), monitor);
// TODO: could also clear out any description strings on types
}
catch (CancelledException | DuplicateNameException e) {
Msg.error(this, "Error when exporting types to file: %s".formatted(gdtFile), e);
}
finally {
if (tx != -1) {
tmpFdtm.endTransaction(tx, true);
}
}
tmpFdtm.save();
FileDataTypeManager fdtm = FileDataTypeManager.createFileArchive(gdtFile);
tx = -1;
try {
tx = fdtm.startTransaction("Import");
tmpFdtm.getAllDataTypes()
.forEachRemaining(
dt -> fdtm.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER));
for (SourceArchive sa : fdtm.getSourceArchives()) {
fdtm.removeSourceArchive(sa);
}
}
finally {
if (tx != -1) {
fdtm.endTransaction(tx, true);
}
}
fdtm.save();
tmpFdtm.close();
fdtm.close();
tmpGDTFile.delete();
}
private void moveAllDataTypesTo(DataTypeManager dtm, CategoryPath srcCP, CategoryPath destCP)
throws DuplicateNameException {
Category srcCat = dtm.getCategory(srcCP);
if (srcCat != null) {
for (DataType dt : srcCat.getDataTypes()) {
if (dt instanceof Array || dt instanceof Pointer) {
continue;
}
dt.setCategoryPath(destCP);
}
for (Category subcat : srcCat.getCategories()) {
moveAllDataTypesTo(dtm, subcat.getCategoryPath(), destCP); // flatten everything
}
}
}
public CategoryPath getRecoveredTypesCp() {
return RECOVERED_TYPES_CP;
}
public DataType getRecoveredType(GoType typ) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
DataType dt = cachedRecoveredDataTypes.get(offset);
if (dt != null) {
return dt;
}
dt = typ.recoverDataType();
cachedRecoveredDataTypes.put(offset, dt);
return dt;
}
public void cacheRecoveredDataType(GoType typ, DataType dt) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
cachedRecoveredDataTypes.put(offset, dt);
}
public DataType getCachedRecoveredDataType(GoType typ) throws IOException {
long offset = getExistingStructureAddress(typ).getOffset();
return cachedRecoveredDataTypes.get(offset);
}
/**
* 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
*/
public void recoverDataTypes(TaskMonitor monitor) throws IOException, CancelledException {
monitor.setMessage("Converting Golang types to Ghidra data types");
monitor.initialize(goTypes.size());
List<Long> typeOffsets = goTypes.keySet().stream().sorted().collect(Collectors.toList());
for (Long typeOffset : typeOffsets) {
monitor.checkCanceled();
monitor.incrementProgress(1);
GoType typ = getGoType(typeOffset);
DataType dt = typ.recoverDataType();
if (programDTM.getDataType(dt.getDataTypePath()) == null) {
programDTM.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
}
public void discoverGoTypes(TaskMonitor monitor) throws IOException, CancelledException {
GoModuledata firstModule = findFirstModuledata(monitor);
if (firstModule == null) {
return;
}
addModule(firstModule);
UnknownProgressWrappingTaskMonitor upwtm =
new UnknownProgressWrappingTaskMonitor(monitor, 50);
upwtm.setMessage("Iterating Golang RTTI types");
upwtm.initialize(0);
goTypes.clear();
Set<Long> discoveredTypes = new HashSet<>();
for (Iterator<GoType> it = firstModule.iterateTypes(); it.hasNext();) {
upwtm.checkCanceled();
upwtm.setProgress(discoveredTypes.size());
GoType type = it.next();
type.discoverGoTypes(discoveredTypes);
}
typeNameIndex.clear();
for (GoType goType : goTypes.values()) {
String typeName = goType.getBaseType().getNameString();
typeNameIndex.put(typeName, goType);
}
Msg.info(this, "Found %d golang types".formatted(goTypes.size()));
initHiddenCompilerTypes();
}
private void initHiddenCompilerTypes() {
// these structure types are what golang map and chan types actually point to.
mapGoType = findGoType("runtime.hmap");
chanGoType = findGoType("runtime.hchan");
}
private GoModuledata findFirstModuledata(TaskMonitor monitor) throws IOException {
GoModuledata result = GoModuledata.getFirstModuledata(this);
if (result == null) {
monitor.setMessage("Searching for Golang pclntab");
monitor.initialize(0);
Address pclntabAddress = GoPcHeader.getPclntabAddress(program);
if (pclntabAddress == null) {
pclntabAddress =
GoPcHeader.findPclntabAddress(this, getPclntabSearchRange(), monitor);
}
if (pclntabAddress != null) {
monitor.setMessage("Searching for Golang firstmoduledata");
monitor.initialize(0);
GoPcHeader pclntab = readStructure(GoPcHeader.class, pclntabAddress);
result = GoModuledata.findFirstModule(this, pclntabAddress, pclntab,
getModuledataSearchRange(), monitor);
}
}
if (result != null && !result.isValid()) {
throw new IOException("Invalid Golang moduledata at %s"
.formatted(result.getStructureContext().getStructureAddress()));
}
return result;
}
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());
}
}
return null;
}
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());
}
}
return null;
}
}

View file

@ -0,0 +1,271 @@
/* ###
* 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.rtti;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.BinaryReader.ReaderFunction;
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")
public class GoSlice {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoSlice> context;
@FieldMapping
private long array;
@FieldMapping
private long len;
@FieldMapping
private long cap;
public GoSlice() {
}
public GoSlice(long array, long len, long cap) {
this.array = array;
this.len = len;
this.cap = cap;
}
public GoSlice(long array, long len, long cap, GoRttiContext programContext) {
this(array, len, cap);
this.programContext = programContext;
}
/**
* Return a artificial view of a portion of this slice's contents.
*
* @param startElement
* @param elementCount
* @param elementSize
* @param programContext
* @return
*/
public GoSlice getSubSlice(long startElement, long elementCount, long elementSize) {
return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, programContext);
}
public boolean isValid(int elementSize) {
try {
Memory memory = programContext.getProgram().getMemory();
Address arrayAddr = getArrayAddress();
return memory.contains(arrayAddr) &&
memory.contains(arrayAddr.addNoWrap(len * elementSize));
}
catch (AddressOverflowException | AddressOutOfBoundsException e) {
return false;
}
}
public long getArrayOffset() {
return array;
}
public Address getArrayAddress() {
return programContext.getDataAddress(array);
}
public long getLen() {
return len;
}
public long getCap() {
return cap;
}
public boolean isFull() {
return len == cap;
}
public boolean isOffsetWithinData(long offset, int sizeofElement) {
return array <= offset && offset < array + (cap * sizeofElement);
}
/**
* Reads the content of the slice, treating each element as an instance of the specified
* structure mapped class.
*
* @param <T>
* @param clazz element type
* @return list of instances
* @throws IOException
*/
public <T> List<T> readList(Class<T> clazz) throws IOException {
return readList((reader) -> programContext.readStructure(clazz, reader));
}
/**
* 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 readFunc function that will read an instance from a BinaryReader
* @return list of instances
* @throws IOException
*/
public <T> List<T> readList(ReaderFunction<T> readFunc) throws IOException {
List<T> result = new ArrayList<>();
long elementSize = 0;
BinaryReader reader = programContext.getReader(array);
for (int i = 0; i < len; i++) {
T t = readFunc.get(reader);
result.add(t);
if (i == 0) {
elementSize = reader.getPointerIndex() - array;
}
else {
// ensure that the reader func is doing correct thing
if (elementSize > 0 && reader.getPointerIndex() != array + (i + 1) * elementSize) {
Msg.warn(this, "Bad element size when reading slice element (size: %d) at %d"
.formatted(elementSize, reader.getPointerIndex()));
elementSize = 0;
}
}
}
return result;
}
/**
* Treats this slice as a array of unsigned integers, of the specified intSize.
* <p>
* @param intSize size of integer
* @return array of longs, containing the (possibly smaller) integers contained in the slice
* @throws IOException if error reading
*/
public long[] readUIntList(int intSize) throws IOException {
BinaryReader reader = programContext.getReader(array);
return readUIntList(reader, array, intSize, (int) len);
}
/**
* Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType,
* which has elements who's type is determined by the specified structure class.
*
* @param sliceName used to label the memory location
* @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
* @throws IOException if error
*/
public void markupArray(String sliceName, Class<?> elementClazz, boolean ptr)
throws IOException {
DataType dt = programContext.getStructureDataType(elementClazz);
markupArray(sliceName, dt, ptr);
}
/**
* Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType.
*
* @param sliceName used to label the memory location
* @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
* @throws IOException if error
*/
public void markupArray(String sliceName, DataType elementType, boolean ptr)
throws IOException {
if (len == 0) {
return;
}
DataTypeManager dtm = programContext.getDTM();
if (ptr) {
elementType = new PointerDataType(elementType, programContext.getPtrSize(), dtm);
}
ArrayDataType arrayDT = new ArrayDataType(elementType, (int) cap, -1, dtm);
Address addr = programContext.getDataAddress(array);
programContext.markupAddress(addr, arrayDT);
if (sliceName != null) {
programContext.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
* @return list of element instances
* @throws IOException if error reading
*/
public <T> List<T> markupArrayElements(Class<T> clazz) throws IOException {
if (len == 0) {
return List.of();
}
List<T> elementList = readList(clazz);
programContext.markup(elementList, true);
return elementList;
}
/**
* Marks up each element of the array with an outbound reference to the corresponding address
* in the targetAddrs list.
* <p>
* Useful when marking up an array of offsets.
* <p>
* The Listing UI doesn't show the outbound reference from each element (for arrays of primitive
* types), but the target will show the inbound reference.
*
* @param elementSize size of each element in the array
* @param targetAddrs list of addresses, should be same size as this slice
* @throws IOException
*/
public void markupElementReferences(int elementSize, List<Address> targetAddrs)
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);
}
}
}
private static long[] readUIntList(BinaryReader reader, long index, int intSize, int count)
throws IOException {
long[] result = new long[count];
for (int i = 0; i < count; i++) {
long l = reader.readUnsignedValue(index, intSize);
result[i] = l;
index += intSize;
}
return result;
}
}

View file

@ -0,0 +1,50 @@
/* ###
* 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.rtti;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
@StructureMapping(structureName = "string")
public class GoString {
@ContextField
private StructureContext<GoString> context;
@FieldMapping
@MarkupReference("stringAddr")
@EOLComment("stringValue")
private long str;
@FieldMapping
private long len;
public Address getStringAddr() {
return context.getProgramContext().getDataAddress(str);
}
public long getLength() {
return len;
}
public String getStringValue() throws IOException {
BinaryReader reader = context.getProgramContext().getReader(str);
return reader.readNextUtf8String((int) len);
}
}

View file

@ -0,0 +1,86 @@
/* ###
* 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.rtti;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
/**
* A pascal-ish string, using a LEB128 value as the length of the following bytes.
* <p>
* Used mainly in lower-level RTTI structures, this class is a ghidra'ism used to parse the
* golang rtti data and does not have a counterpart in the golang src.
*/
@StructureMapping(structureName = "GoVarlenString")
public class GoVarlenString implements StructureReader<GoVarlenString> {
@ContextField
private StructureContext<GoVarlenString> context;
@FieldMapping(fieldName = "strlen")
@FieldOutput(isVariableLength = true, getter = "strlenDataType")
private int strlenLen; // store the len of the leb128, not the value of the leb128 number
@FieldMapping(fieldName = "value")
@FieldOutput(isVariableLength = true, getter = "valueDataType")
private byte[] bytes;
public GoVarlenString() {
}
@Override
public void readStructure() throws IOException {
readFrom(context.getReader());
}
private void readFrom(BinaryReader reader) throws IOException {
long startPos = reader.getPointerIndex();
int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
this.strlenLen = (int) (reader.getPointerIndex() - startPos);
this.bytes = reader.readNextByteArray(strLen);
}
public int getStrlen() {
return bytes.length;
}
public int getStrlenLen() {
return strlenLen;
}
public byte[] getBytes() {
return bytes;
}
public String getString() {
return new String(bytes, StandardCharsets.UTF_8);
}
public DataTypeInstance getStrlenDataType() {
return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen,
false);
}
public DataType getValueDataType() {
return new ArrayDataType(CharDataType.dataType, bytes.length, -1,
context.getProgramContext().getDTM());
}
}

View file

@ -0,0 +1,77 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
@StructureMapping(structureName = "runtime.arraytype")
public class GoArrayType extends GoType {
@FieldMapping
private long elem; // pointer to element type
@FieldMapping
private long slice; // pointer to slice type
@FieldMapping
private long len;
public GoArrayType() {
}
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
@Markup
public GoType getSliceType() throws IOException {
return programContext.getGoType(slice);
}
@Override
public DataType recoverDataType() throws IOException {
DataType elementDt = programContext.getRecoveredType(getElement());
DataType self = programContext.getCachedRecoveredDataType(this);
if (self != null) {
return self;
}
return new ArrayDataType(elementDt, (int) len, -1);
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
GoType elementType = getElement();
GoType sliceType = getSliceType();
if (elementType != null) {
elementType.discoverGoTypes(discoveredTypes);
}
if (sliceType != null) {
sliceType.discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,104 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiContext;
import ghidra.app.util.bin.format.golang.structmapping.*;
/**
* Represents the fundamental golang rtti type information.
* <p>
* The in-memory instance will typically be part of a specialized type structure, depending
* on the 'kind' of this type.
* <p>
* Additionally, there will be an GoUncommonType structure immediately after this type, if
* the uncommon bit is set in tflag.
* <p>
* <pre>
* struct specialized_type { basetype_struct; (various_fields)* } struct uncommon;
* </pre>
*/
@StructureMapping(structureName = "runtime._type")
public class GoBaseType {
@ContextField
private StructureContext<GoBaseType> context;
@ContextField
private GoRttiContext programContext;
@FieldMapping(signedness = Signedness.Unsigned)
private long size;
@FieldMapping
private long ptrdata;
@FieldMapping
@EOLComment("flags")
private int tflag;
@FieldMapping
@EOLComment
private int kind;
@FieldMapping
@MarkupReference("name")
private long str; // an offset relative to containing moduledata's type base addr
@FieldMapping
@MarkupReference
private long ptrToThis; // an offset relative to containing moduledata's type base addr
public long getSize() {
return size;
}
public GoKind getKind() {
return GoKind.parseByte(kind);
}
public Set<GoTypeFlag> getFlags() {
return GoTypeFlag.parseFlags(tflag);
}
public int getTflag() {
return tflag;
}
public boolean hasUncommonType() {
return GoTypeFlag.Uncommon.isSet(tflag);
}
@Markup
public GoName getName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), str);
}
public String getNameString() throws IOException {
String s = getName().getName();
return GoTypeFlag.ExtraStar.isSet(tflag) ? s.substring(1) : s;
}
@Markup
public GoType getPtrToThis() throws IOException {
return programContext.resolveTypeOff(context.getStructureStart(), ptrToThis);
}
}

View file

@ -0,0 +1,76 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
@StructureMapping(structureName = "runtime.chantype")
public class GoChanType extends GoType {
@FieldMapping
@MarkupReference("element")
private long elem;
@FieldMapping
private long dir;
public GoChanType() {
}
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
@Override
public DataType recoverDataType() throws IOException {
GoType chanGoType = programContext.getChanGoType();
if (chanGoType == null) {
// if we couldn't find the underlying/hidden runtime.hchan struct type, just return
// a void*
return programContext.getDTM().getPointer(null);
}
DataType chanDT = chanGoType.recoverDataType();
Pointer ptrChanDt = programContext.getDTM().getPointer(chanDT);
if (typ.getSize() != ptrChanDt.getLength()) {
Msg.warn(this, "Size mismatch between chan type and recovered type");
}
TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(),
getStructureName(), ptrChanDt, programContext.getDTM());
return typedef;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
GoType element = getElement();
if (element != null) {
element.discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,191 @@
/* ###
* 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.rtti.types;
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.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
@StructureMapping(structureName = "runtime.functype")
public class GoFuncType extends GoType {
@FieldMapping
private int inCount; // uint16
@FieldMapping
private int outCount; // uint16
public GoFuncType() {
}
public boolean isVarArg() {
return (outCount & 0x8000) != 0;
}
public int getInCount() {
return inCount;
}
public int getOutCount() {
return outCount & 0x7fff;
}
public int getParamCount() {
return inCount + (outCount & 0x7fff);
}
public List<Address> getParamTypeAddrs() throws IOException {
GoSlice slice = getParamListSlice();
long[] typeOffsets = slice.readUIntList(programContext.getPtrSize());
return Arrays.stream(typeOffsets)
.mapToObj(programContext::getDataAddress)
.collect(Collectors.toList());
}
private GoSlice getParamListSlice() {
int count = getParamCount();
return new GoSlice(getOffsetEndOfFullType(), count, count, programContext);
}
@Markup
public List<GoType> getParamTypes() throws IOException {
return getParamTypeAddrs().stream()
.map(addr -> {
try {
return programContext.getGoType(addr);
}
catch (IOException e) {
return null;
}
})
.collect(Collectors.toList());
}
@Override
public void additionalMarkup() throws IOException {
GoSlice slice = getParamListSlice();
slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true);
}
public String getFuncPrototypeString(String funcName) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("func");
if (funcName != null && !funcName.isBlank()) {
sb.append(" ").append(funcName);
}
sb.append("(");
List<GoType> paramTypes = getParamTypes();
List<GoType> inParamTypes = paramTypes.subList(0, inCount);
List<GoType> outParamTypes = paramTypes.subList(inCount, paramTypes.size());
for (int i = 0; i < inParamTypes.size(); i++) {
GoType paramType = inParamTypes.get(i);
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getBaseType().getNameString());
}
sb.append(")");
if (!outParamTypes.isEmpty()) {
sb.append(" (");
for (int i = 0; i < outParamTypes.size(); i++) {
GoType paramType = outParamTypes.get(i);
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getBaseType().getNameString());
}
sb.append(")");
}
return sb.toString();
}
@Override
public DataType recoverDataType() throws IOException {
String name = typ.getNameString();
DataTypeManager dtm = programContext.getDTM();
List<GoType> paramTypes = getParamTypes();
List<GoType> inParamTypes = paramTypes.subList(0, inCount);
List<GoType> outParamTypes = paramTypes.subList(inCount, paramTypes.size());
List<ParameterDefinition> params = new ArrayList<>();
for (int i = 0; i < inParamTypes.size(); i++) {
GoType paramType = inParamTypes.get(i);
DataType paramDT = paramType.recoverDataType();
params.add(new ParameterDefinitionImpl(null, paramDT, null));
}
DataType returnDT;
if (outParamTypes.size() == 0) {
returnDT = VoidDataType.dataType;
}
else if (outParamTypes.size() == 1) {
returnDT = outParamTypes.get(0).recoverDataType();
}
else {
List<DataType> paramDataTypes = recoverTypes(outParamTypes);
GoFunctionMultiReturn multiReturn = new GoFunctionMultiReturn(
programContext.getRecoveredTypesCp(), name, paramDataTypes, dtm, null);
returnDT = multiReturn.getStruct();
}
FunctionDefinitionDataType funcDef = new FunctionDefinitionDataType(
programContext.getRecoveredTypesCp(), name, dtm);
funcDef.setArguments(params.toArray(ParameterDefinition[]::new));
funcDef.setReturnType(returnDT);
return dtm.getPointer(funcDef);
}
private List<DataType> recoverTypes(List<GoType> types) throws IOException {
List<DataType> result = new ArrayList<>();
for (GoType type : types) {
result.add(type.recoverDataType());
}
return result;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
for (GoType paramType : getParamTypes()) {
if (paramType != null) {
paramType.discoverGoTypes(discoveredTypes);
}
}
return true;
}
@Override
public String toString() {
try {
return getFuncPrototypeString(null);
}
catch (IOException e) {
return super.toString();
}
}
}

View file

@ -0,0 +1,74 @@
/* ###
* 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.rtti.types;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiContext;
import ghidra.app.util.bin.format.golang.structmapping.*;
@StructureMapping(structureName = "runtime.imethod")
public class GoIMethod implements StructureMarkup<GoIMethod> {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoIMethod> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
private long name;
@FieldMapping
@MarkupReference("type")
private long ityp;
@Markup
public GoName getName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), name);
}
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : "_blank_";
}
@Markup
public GoType getType() throws IOException {
return programContext.resolveTypeOff(context.getStructureStart(), ityp);
}
@Override
public StructureContext<GoIMethod> getStructureContext() {
return context;
}
@Override
public String getStructureName() throws IOException {
return getNameString();
}
}
/*
struct runtime.imethod // Length: 8 Alignment: 4
{
runtime.nameOff name
runtime.typeOff ityp
} pack()
*/

View file

@ -0,0 +1,111 @@
/* ###
* 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.rtti.types;
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;
import ghidra.program.model.data.TypedefDataType;
@StructureMapping(structureName = "runtime.interfacetype")
public class GoInterfaceType extends GoType {
@FieldMapping
@MarkupReference("pkgPath")
private long pkgpath; // pointer to name
@FieldMapping
private GoSlice mhdr;
public GoInterfaceType() {
}
@Markup
public GoName getPkgPath() throws IOException {
return programContext.getGoName(pkgpath);
}
public String getPkgPathString() throws IOException {
GoName n = getPkgPath();
return n != null ? n.getName() : "";
}
public GoSlice getMethodsSlice() {
return mhdr;
}
public List<GoIMethod> getMethods() throws IOException {
return mhdr.readList(GoIMethod.class);
}
@Override
public void additionalMarkup() throws IOException {
mhdr.markupArray(null, GoIMethod.class, false);
mhdr.markupArrayElements(GoIMethod.class);
}
@Override
public DataType recoverDataType() throws IOException {
DataType dt = programContext.getStructureDataType(GoIface.class);
String name = typ.getNameString();
if (!dt.getName().equals(name)) {
dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt,
programContext.getDTM());
}
return dt;
}
@Override
public String getMethodListString() throws IOException {
StringBuilder sb = new StringBuilder();
for (GoIMethod imethod : getMethods()) {
if (!sb.isEmpty()) {
sb.append("\n");
}
String methodStr = imethod.getNameString();
GoType type = imethod.getType();
if (type instanceof GoFuncType funcType) {
methodStr = funcType.getFuncPrototypeString(methodStr);
}
else {
methodStr = "func %s()".formatted(methodStr);
}
sb.append(methodStr);
}
return sb.toString();
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
for (GoIMethod imethod : getMethods()) {
GoType type = imethod.getType();
if (type != null) {
type.discoverGoTypes(discoveredTypes);
}
}
return true;
}
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.rtti.types;
public enum GoKind {
invalid,
Bool, // 1
Int, // 2
Int8, // 3
Int16, // 4
Int32, // 5
Int64, // 6
Uint, // 7
Uint8, // 8
Uint16, // 9
Uint32, // 10
Uint64, // 11
Uintptr, // 12
Float32, // 13
Float64, // 14
Complex64, // 15
Complex128, // 16
Array, // 17
Chan, // 18
Func, // 19
Interface, // 20
Map, // 21
Pointer, // 22
Slice, // 23
String, // 24
Struct, // 25
UnsafePointer; // 26
public static final int KIND_MASK = (1 << 5) - 1;
public static final int GC_PROG = (1 << 6);
public static final int DIRECT_IFACE = (1 << 5);
public static GoKind parseByte(int b) {
int ordinal = b & KIND_MASK;
return Bool.ordinal() <= ordinal && ordinal <= UnsafePointer.ordinal()
? values()[ordinal]
: invalid;
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
@StructureMapping(structureName = "runtime.maptype")
public class GoMapType extends GoType {
@FieldMapping
private long key; // ptr to type
@FieldMapping
private long elem; // ptr to type
@FieldMapping
private long bucket; // ptr to type
@FieldMapping
private long hasher; // pointer to "func(Pointer, pointer) pointer"
@FieldMapping
private int keysize;
@FieldMapping
private int elemsize;
@FieldMapping
private int bucketsize;
@FieldMapping
private int flags;
public GoMapType() {
}
@Markup
public GoType getKey() throws IOException {
return programContext.getGoType(key);
}
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
@Markup
public GoType getBucket() throws IOException {
return programContext.getGoType(bucket);
}
@Override
public DataType recoverDataType() throws IOException {
GoType mapGoType = programContext.getMapGoType();
if (mapGoType == null) {
// if we couldn't find the underlying/hidden runtime.hmap struct type, just return
// a void*
return programContext.getDTM().getPointer(null);
}
DataType mapDT = mapGoType.recoverDataType();
Pointer ptrMapDt = programContext.getDTM().getPointer(mapDT);
if (typ.getSize() != ptrMapDt.getLength()) {
Msg.warn(this, "Size mismatch between map type and recovered type");
}
TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(),
getStructureName(), ptrMapDt, programContext.getDTM());
return typedef;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
GoType keyType = getKey();
GoType elemType = getElement();
GoType bucketType = getBucket();
if (keyType != null) {
keyType.discoverGoTypes(discoveredTypes);
}
if (elemType != null) {
elemType.discoverGoTypes(discoveredTypes);
}
if (bucketType != null) {
bucketType.discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,83 @@
/* ###
* 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.rtti.types;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiContext;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
@StructureMapping(structureName = "runtime.method")
public class GoMethod implements StructureMarkup<GoMethod> {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoMethod> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
private long name; // nameOff
@FieldMapping
@MarkupReference("type")
private long mtyp; // typeOff
@FieldMapping
@MarkupReference
private long ifn; // textOff
@FieldMapping
@MarkupReference
private long tfn; // textOff
@Markup
public GoName getName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), name);
}
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : "_blank_";
}
@Markup
public GoType getType() throws IOException {
return programContext.resolveTypeOff(context.getStructureStart(), mtyp);
}
@Override
public StructureContext<GoMethod> getStructureContext() {
return context;
}
@Override
public String getStructureName() throws IOException {
return getNameString();
}
public Address getIfn() {
return programContext.resolveTextOff(context.getStructureStart(), ifn);
}
public Address getTfn() {
return programContext.resolveTextOff(context.getStructureStart(), tfn);
}
}

View file

@ -0,0 +1,86 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoString;
import ghidra.app.util.bin.format.golang.structmapping.StructureMapping;
import ghidra.app.util.bin.format.golang.structmapping.StructureReader;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
/**
* WARNING: tricky code / class layout here!
* <p>
* To coerce java inheritance and structmapping features to match the layout of go rtti type structs,
* this class is constructed strangely.
*/
@StructureMapping(structureName = "runtime._type")
public class GoPlainType extends GoType implements StructureReader<GoType> {
@Override
public void readStructure() throws IOException {
this.typ = context.getProgramContext().readStructure(GoBaseType.class, context.getReader());
}
@Override
public DataType recoverDataType() throws IOException {
DataTypeManager dtm = programContext.getDTM();
DataType dt = switch (typ.getKind()) {
case Bool -> BooleanDataType.dataType;
case Float32 -> AbstractFloatDataType.getFloatDataType(32 / 8, null);
case Float64 -> AbstractFloatDataType.getFloatDataType(64 / 8, null);
case Int -> AbstractIntegerDataType.getSignedDataType(programContext.getPtrSize(), dtm);
case Int8 -> AbstractIntegerDataType.getSignedDataType(8 / 8, null);
case Int16 -> AbstractIntegerDataType.getSignedDataType(16 / 8, null);
case Int32 -> AbstractIntegerDataType.getSignedDataType(32 / 8, null);
case Int64 -> AbstractIntegerDataType.getSignedDataType(64 / 8, null);
case Uint -> AbstractIntegerDataType.getUnsignedDataType(programContext.getPtrSize(),
dtm);
case Uint8 -> AbstractIntegerDataType.getUnsignedDataType(8 / 8, null);
case Uint16 -> AbstractIntegerDataType.getUnsignedDataType(16 / 8, null);
case Uint32 -> AbstractIntegerDataType.getUnsignedDataType(32 / 8, null);
case Uint64 -> AbstractIntegerDataType.getUnsignedDataType(64 / 8, null);
case Uintptr -> AbstractIntegerDataType.getUnsignedDataType(programContext.getPtrSize(),
dtm);
case String -> programContext.getStructureDataType(GoString.class);
case UnsafePointer -> dtm.getPointer(null);
default -> null;
};
if (dt == null) {
dt = super.recoverDataType();
}
String name = typ.getNameString();
if (!dt.getName().equalsIgnoreCase(name)) {
dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, dtm);
}
if (dt.getLength() != typ.getSize()) {
Msg.warn(this,
"Recovered golang data type size mismatch: %s, %d != %d".formatted(getDebugId(),
typ.getSize(), dt.getLength()));
}
return dt;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
return super.discoverGoTypes(discoveredTypes);
}
}

View file

@ -0,0 +1,61 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.PointerDataType;
@StructureMapping(structureName = "runtime.ptrtype")
public class GoPointerType extends GoType {
@FieldMapping
@MarkupReference("element")
private long elem;
public GoPointerType() {
}
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
@Override
public DataType recoverDataType() throws IOException {
DataType elementDT = programContext.getRecoveredType(getElement());
DataType self = programContext.getCachedRecoveredDataType(this);
if (self != null) {
return self;
}
return new PointerDataType(elementDT, programContext.getDTM());
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
GoType element = getElement();
if (element != null) {
element.discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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.rtti.types;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
@StructureMapping(structureName = "runtime.slicetype")
public class GoSliceType extends GoType {
@FieldMapping
@MarkupReference("element")
private long elem;
public GoSliceType() {
}
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
@Override
public DataType recoverDataType() throws IOException {
int arrayPtrComponentIndex = 0; /* HACK, field ordinal of void* data field in slice type */
Structure genericSliceDT = programContext.getGenericSliceDT();
DataTypeComponent arrayDTC = genericSliceDT.getComponent(arrayPtrComponentIndex);
GoType elementType = getElement();
DataType elementDT = elementType.recoverDataType();
Pointer elementPtrDT = programContext.getDTM().getPointer(elementDT);
StructureDataType sliceDT =
new StructureDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), 0,
programContext.getDTM());
sliceDT.replaceWith(genericSliceDT);
sliceDT.replace(arrayPtrComponentIndex, elementPtrDT, -1, arrayDTC.getFieldName(),
arrayDTC.getComment());
return sliceDT;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
GoType elementType = getElement();
if (elementType != null) {
elementType.discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.rtti.types;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiContext;
import ghidra.app.util.bin.format.golang.structmapping.*;
@StructureMapping(structureName = "runtime.structfield")
public class GoStructField {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoStructField> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
private long name; // direct ptr to GoName
@FieldMapping
@MarkupReference("type")
private long typ; // direct ptr to GoType
@FieldMapping(fieldName = "offsetAnon" /* TODO or "anon" */)
private long offset; // offset >> 1 == actual offset, bit 0 = embedded
@Markup
public GoName getName() throws IOException {
return name != 0
? context.getProgramContext().readStructure(GoName.class, name)
: null;
}
@Markup
public GoType getType() throws IOException {
return programContext.getGoType(typ);
}
public long getOffset() {
return offset >> 1;
}
public boolean isEmbedded() {
return (offset & 0x1) != 0;
}
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : null;
}
}
/*
struct runtime.structfield
Length: 24 Alignment: 8
{
runtime.name name
runtime._type * typ
uintptr offsetAnon ---- name changed to offset in next golang ver
} pack()
*/

View file

@ -0,0 +1,161 @@
/* ###
* 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.rtti.types;
import java.util.*;
import java.io.IOException;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
@StructureMapping(structureName = "runtime.structtype")
public class GoStructType extends GoType {
@FieldMapping
@MarkupReference
private long pkgPath; // name
@FieldMapping
private GoSlice fields;
public GoStructType() {
}
@Markup
public GoName getPkgPath() throws IOException {
return programContext.getGoName(pkgPath);
}
public String getPkgPathString() throws IOException {
GoName n = getPkgPath();
return n != null ? n.getName() : "";
}
public List<GoStructField> getFields() throws IOException {
return fields.readList(GoStructField.class);
}
@Override
public void additionalMarkup() throws IOException {
super.additionalMarkup();
fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false);
fields.markupArrayElements(GoStructField.class);
}
@Override
public String getTypeDeclString() throws IOException {
String methodListStr = getMethodListString();
if (methodListStr == null || methodListStr.isEmpty()) {
methodListStr = "// No methods";
}
else {
methodListStr = "// Methods\n" + methodListStr;
}
return """
// size: %d
type %s struct {
%s
%s
}
""".formatted(
typ.getSize(),
typ.getNameString(),
getFieldListString().indent(2),
methodListStr.indent(2));
}
private String getFieldListString() throws IOException {
StringBuilder sb = new StringBuilder();
for (GoStructField field : getFields()) {
if (!sb.isEmpty()) {
sb.append("\n");
}
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));
}
return sb.toString();
}
@Override
public DataType recoverDataType() throws IOException {
StructureDataType struct = new StructureDataType(programContext.getRecoveredTypesCp(),
typ.getNameString(), (int) typ.getSize(), programContext.getDTM());
programContext.cacheRecoveredDataType(this, struct);
List<GoStructField> skippedFields = new ArrayList<>();
List<GoStructField> fieldList = getFields();
for (int i = 0; i < fieldList.size(); i++) {
GoStructField field = fieldList.get(i);
GoStructField nextField = i < fieldList.size() - 1 ? fieldList.get(i + 1) : null;
long availSpace = nextField != null
? nextField.getOffset() - field.getOffset()
: typ.getSize() - field.getOffset();
GoType fieldType = field.getType();
long fieldSize = fieldType.getBaseType().getSize();
if (fieldSize == 0) {
skippedFields.add(field);
continue;
}
try {
DataType fieldDT = programContext.getRecoveredType(fieldType);
struct.replaceAtOffset((int) field.getOffset(), fieldDT, (int) fieldSize,
field.getNameString(), null);
}
catch (IllegalArgumentException e) {
Msg.warn(this,
"Failed to add field to go recovered struct: %s".formatted(getDebugId()), e);
}
}
for (GoStructField skippedField : skippedFields) {
DataTypeComponent dtc =
struct.getDefinedComponentAtOrAfterOffset((int) skippedField.getOffset());
GoType skippedFieldType = skippedField.getType();
if (dtc != null) {
String comment = dtc.getComment();
comment = comment == null ? "" : (comment + "\n");
comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getNameString(),
skippedFieldType.getBaseType().getNameString());
dtc.setComment(comment);
}
}
DWARFUtil.packCompositeIfPossible(struct, programContext.getDTM());
return struct;
}
@Override
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!super.discoverGoTypes(discoveredTypes)) {
return false;
}
for (GoStructField field : getFields()) {
field.getType().discoverGoTypes(discoveredTypes);
}
return true;
}
}

View file

@ -0,0 +1,180 @@
/* ###
* 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.rtti.types;
import java.util.Map;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoRttiContext;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
@PlateComment()
public abstract class GoType implements StructureMarkup<GoType> {
private static final Map<GoKind, Class<? extends GoType>> specializedTypeClasses =
Map.ofEntries(
Map.entry(GoKind.Struct, GoStructType.class),
Map.entry(GoKind.Pointer, GoPointerType.class),
Map.entry(GoKind.Func, GoFuncType.class),
Map.entry(GoKind.Slice, GoSliceType.class),
Map.entry(GoKind.Array, GoArrayType.class),
Map.entry(GoKind.Chan, GoChanType.class),
Map.entry(GoKind.Map, GoMapType.class),
Map.entry(GoKind.Interface, GoInterfaceType.class));
public static Class<? extends GoType> getSpecializedTypeClass(GoRttiContext programContext,
long offset) throws IOException {
GoTypeDetector typeDetector = programContext.readStructure(GoTypeDetector.class, offset);
Class<? extends GoType> result = specializedTypeClasses.get(typeDetector.getKind());
if (result == null) {
result = GoPlainType.class;
}
return result;
}
@ContextField
protected GoRttiContext programContext;
@ContextField
protected StructureContext<GoType> context;
@FieldMapping
@Markup
@FieldOutput
protected GoBaseType typ;
public GoBaseType getBaseType() {
return typ;
}
public String getDebugId() {
return "%s@%s".formatted(
context.getMappingInfo().getDescription(),
context.getStructureAddress());
}
protected long getOffsetEndOfFullType() {
return context.getStructureEnd() +
(typ.hasUncommonType()
? programContext.getStructureMappingInfo(GoUncommonType.class)
.getStructureLength()
: 0);
}
@Markup
public GoUncommonType getUncommonType() throws IOException {
return typ.hasUncommonType()
? programContext.readStructure(GoUncommonType.class, context.getStructureEnd())
: null;
}
@Override
public StructureContext<GoType> getStructureContext() {
return context;
}
@Override
public String getStructureName() throws IOException {
return typ.getNameString();
}
@Override
public void additionalMarkup() throws IOException {
GoUncommonType uncommonType = getUncommonType();
if (uncommonType != null) {
GoSlice slice = uncommonType.getMethodsSlice();
slice.markupArray(getStructureName() + "_methods", GoMethod.class, false);
slice.markupArrayElements(GoMethod.class);
programContext.labelStructure(uncommonType, typ.getNameString() + "_" +
programContext.getStructureDataTypeName(GoUncommonType.class));
}
}
public String getMethodListString() throws IOException {
GoUncommonType uncommonType = getUncommonType();
if (uncommonType == null || uncommonType.mcount == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (GoMethod method : uncommonType.getMethods()) {
if (!sb.isEmpty()) {
sb.append("\n");
}
String methodStr = method.getNameString();
GoType type = method.getType();
if (type instanceof GoFuncType funcType) {
methodStr = funcType.getFuncPrototypeString(methodStr);
}
else {
methodStr = "func %s()".formatted(methodStr);
}
sb.append(methodStr);
}
return sb.toString();
}
protected String getTypeDeclString() throws IOException {
String s = "type " + typ.getNameString() + " " + typ.getKind();
String methodListString = getMethodListString();
if (!methodListString.isEmpty()) {
s += "\n// Methods\n" + methodListString;
}
return s;
}
@Override
public String toString() {
try {
return getTypeDeclString();
}
catch (IOException e) {
return super.toString();
}
}
/**
* Converts a golang RTTI type structure into a Ghidra data type.
*
* @return {@link DataType} that represents the golang type
* @throws IOException
*/
public DataType recoverDataType() throws IOException {
DataType dt = Undefined.getUndefinedDataType((int) typ.getSize());
return new TypedefDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), dt,
programContext.getDTM());
}
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!discoveredTypes.add(context.getStructureStart())) {
return false;
}
GoUncommonType uncommonType = getUncommonType();
if (uncommonType != null) {
for (GoMethod method : uncommonType.getMethods()) {
GoType methodType = method.getType();
if (methodType != null) {
methodType.discoverGoTypes(discoveredTypes);
}
}
}
return true;
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.rtti.types;
import ghidra.app.util.bin.format.golang.structmapping.FieldMapping;
import ghidra.app.util.bin.format.golang.structmapping.StructureMapping;
/**
* Small stub that is only used to fetch the "kind" field so that the real gotype can be detected
*/
@StructureMapping(structureName = "runtime._type")
public class GoTypeDetector {
@FieldMapping
private int kind;
public GoKind getKind() {
return GoKind.parseByte(kind);
}
}

View file

@ -0,0 +1,51 @@
/* ###
* 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.rtti.types;
import java.util.EnumSet;
import java.util.Set;
public enum GoTypeFlag {
Uncommon(1 << 0), // 1
ExtraStar(1 << 1), // 2
Named(1 << 2), // 4
RegularMemory(1 << 3); // 8
private final int value;
private GoTypeFlag(int i) {
this.value = i;
}
public int getValue() {
return value;
}
public boolean isSet(int i) {
return (i & value) != 0;
}
public static Set<GoTypeFlag> parseFlags(int b) {
EnumSet<GoTypeFlag> result = EnumSet.noneOf(GoTypeFlag.class);
for (GoTypeFlag flag : values()) {
if (flag.isSet(b)) {
result.add(flag);
}
}
return result;
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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.rtti.types;
import java.util.List;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.util.Msg;
@StructureMapping(structureName = "runtime.uncommontype")
public class GoUncommonType {
@ContextField
private GoRttiContext programContext;
@ContextField
private StructureContext<GoUncommonType> context;
@FieldMapping(fieldName = "pkgpath")
@MarkupReference("pkgPath")
@EOLComment("packagePathString")
long pkgpath_nameOff;
@FieldMapping
int mcount;
@FieldMapping
int xcount;
@FieldMapping
long moff;
@Markup
public GoName getPkgPath() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), pkgpath_nameOff);
}
public String getPackagePathString() throws IOException {
GoName pkgPath = getPkgPath();
return pkgPath != null ? pkgPath.getName() : null;
}
public GoSlice getMethodsSlice() {
return new GoSlice(context.getFieldLocation(moff), mcount, mcount, programContext);
}
public List<GoMethod> getMethods() throws IOException {
GoSlice slice = getMethodsSlice();
if (!slice.isValid(
programContext.getStructureMappingInfo(GoMethod.class).getStructureLength())) {
Msg.warn(this, "Bad uncommon method list: %s".formatted(context.getStructureAddress()));
return List.of();
}
return slice.readList(GoMethod.class);
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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 static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the tagged method should be executed after the structure has been created
* and its data read into its structure mapped fields.
*
*/
@Retention(RUNTIME)
@Target(METHOD)
public @interface AfterStructureRead {
// nothing
}

View file

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.structmapping;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the tagged field should be initialized with the value of the appropriate
* context object, either {@link ProgramContext} or {@link StructureContext}.
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface ContextField {
// nothing
}

View file

@ -0,0 +1,39 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the tagged field should have an "end-of-line" comment placed
* at each instance of the field, using the return value of a getter method for the field
* or the return value of a specified method as the string.
*/
@Retention(RUNTIME)
@Target({ FIELD })
public @interface EOLComment {
/**
* Name of a "getter" method that's return value will be converted to a string and used
* as the EOL comment
*
* @return
*/
String value() default "";
}

View file

@ -0,0 +1,66 @@
/* ###
* 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 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
*
* @param <T> the class that contains this field
*/
public record FieldContext<T> (
StructureContext<T> structureContext,
FieldMappingInfo<T> fieldInfo,
DataTypeComponent dtc,
BinaryReader reader) {
public T getStructureInstance() {
return structureContext.getStructureInstance();
}
public ProgramContext getProgramContext() {
return structureContext().getProgramContext();
}
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);
}
public Address getAddress() {
return structureContext.getStructureAddress().add(dtc.getOffset());
}
public <R> R getValue(Class<R> expectedType) throws IOException {
return fieldInfo.getValue(structureContext.getStructureInstance(), expectedType);
}
}

View file

@ -0,0 +1,50 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the tagged java field corresponds to a field in a Ghidra structure.
* <p>
* If the name of the java field does not match the Ghidra structure field name, the
* {@link #fieldName()} property can be used to manually specify the Ghidra field name.
* <p>
* The type of the tagged java field can be a java primitive, or a
* {@link StructureMapping structure mapped} class.
*
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface FieldMapping {
String fieldName() default "";
@SuppressWarnings("rawtypes")
Class<? extends FieldReadFunction> readFunc() default FieldReadFunction.class;
int length() default -1;
/**
* Override the signedness the underlying numeric field.
*
* @return
*/
Signedness signedness() default Signedness.Unspecified;
}

View file

@ -0,0 +1,327 @@
/* ###
* 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.util.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.CodeUnit;
/**
* Immutable information needed to deserialize a field in a structure mapped class.
*
* @param <T>
*/
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
*/
public static <T> FieldMappingInfo<T> createEarlyBinding(Field field, DataTypeComponent dtc,
Signedness signedness, int length) {
signedness = signedness == Signedness.Unspecified
? ReflectionHelper.getDataTypeSignedness(dtc.getDataType())
: signedness;
length = length != -1 ? length : dtc.getLength();
return new FieldMappingInfo<>(field, dtc.getFieldName(), dtc, signedness, length);
}
/**
* 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
*/
public static <T> FieldMappingInfo<T> createLateBinding(Field field, String fieldName,
Signedness signedness, int length) {
return new FieldMappingInfo<>(field, fieldName, null, signedness, length);
}
private final Field field;
private final String dtcFieldName;
private final DataTypeComponent dtc;
private final Signedness signedness;
private final int length;
private final List<FieldMarkupFunction<T>> markupFuncs = new ArrayList<>();
private FieldReadFunction<T> readerFunc;
private FieldMappingInfo(Field field, String dtcFieldName, DataTypeComponent dtc,
Signedness signedness, int length) {
this.field = field;
this.dtcFieldName = dtcFieldName;
this.dtc = dtc;
this.signedness = signedness;
this.length = length;
}
public Field getField() {
return field;
}
public String getFieldName() {
return dtcFieldName;
}
public DataTypeComponent getDtc() {
return dtc;
}
public DataTypeComponent getDtc(Structure structure) {
return dtc != null
? dtc
: findDtc(structure);
}
public DataTypeComponent findDtc(Structure struct) {
for (DataTypeComponent dtc : struct.getDefinedComponents()) {
if (dtcFieldName.equals(dtc.getFieldName())) {
return dtc;
}
}
return null;
}
public FieldReadFunction<T> getReaderFunc() {
return readerFunc;
}
public List<FieldMarkupFunction<T>> getMarkupFuncs() {
return markupFuncs;
}
public void addMarkupFunc(FieldMarkupFunction<T> func) {
markupFuncs.add(func);
}
public int getLength() {
return length;
}
public Signedness getSignedness() {
return signedness;
}
public boolean isUnsigned() {
return signedness == Signedness.Unsigned;
}
public boolean isStructureMappedType() {
return ReflectionHelper.hasStructureMapping(field.getType());
}
public <R> R getValue(T structInstance, Class<R> expectedType) throws IOException {
return ReflectionHelper.getFieldValue(structInstance, field, expectedType);
}
public void addMarkupNestedFuncs() {
Markup ma = field.getAnnotation(Markup.class);
if (ma != null) {
Class<?> fieldType = field.getType();
if (!ReflectionHelper.hasStructureMapping(fieldType)) {
throw new IllegalArgumentException("Invalid @Markup, %s is not structure mapped. %s"
.formatted(field.getType().getSimpleName(), field));
}
markupFuncs.add(this::markupNestedStructure);
}
}
public void addMarkupReferenceFunc() {
MarkupReference mufa = field.getAnnotation(MarkupReference.class);
if (mufa != null) {
markupFuncs.add(makeMarkupReferenceFunc(mufa.value()));
}
}
public void addCommentMarkupFuncs() {
Class<?> clazz = field.getDeclaringClass();
PlateComment pca = field.getAnnotation(PlateComment.class);
if (pca != null) {
Method commentGetter =
ReflectionHelper.getCommentMethod(clazz, pca.value(), field.getName());
markupFuncs.add(createCommentMarkupFunc(commentGetter, CodeUnit.PLATE_COMMENT, "\n"));
}
EOLComment eca = field.getAnnotation(EOLComment.class);
if (eca != null) {
Method commentGetter =
ReflectionHelper.getCommentMethod(clazz, eca.value(), field.getName());
markupFuncs.add(createCommentMarkupFunc(commentGetter, CodeUnit.EOL_COMMENT, ";"));
}
}
private FieldMarkupFunction<T> createCommentMarkupFunc(Method commentGetter, int commentType,
String sep) {
return context -> {
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);
}
};
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setReadFuncClass(Class<? extends FieldReadFunction> readFuncClass) {
if (readFuncClass != FieldReadFunction.class) {
this.readerFunc = ReflectionHelper.createInstance(readFuncClass, this);
return;
}
Class<?> fieldType = field.getType();
if (ReflectionHelper.isPrimitiveType(fieldType)) {
this.readerFunc = getReadPrimitiveValueFunc(fieldType);
return;
}
if (ReflectionHelper.hasStructureMapping(fieldType)) {
this.readerFunc = this::readStructureMappedTypeFunc;
return;
}
}
private FieldReadFunction<T> getReadPrimitiveValueFunc(Class<?> destClass) {
if (destClass == Long.class || destClass == Long.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
? context.reader().readNextUnsignedValue(context.fieldInfo().getLength())
: context.reader().readNextValue(context.fieldInfo().getLength());
}
if (destClass == Integer.class || destClass == Integer.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
? (int) context.reader().readNextUnsignedValue(context.fieldInfo().getLength())
: (int) context.reader().readNextValue(context.fieldInfo().getLength());
}
if (destClass == Short.class || destClass == Short.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
? (short) context.reader()
.readNextUnsignedValue(context.fieldInfo().getLength())
: (short) context.reader().readNextValue(context.fieldInfo().getLength());
}
if (destClass == Byte.class || destClass == Byte.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
? (byte) context.reader().readNextUnsignedValue(context.fieldInfo().getLength())
: (byte) context.reader().readNextValue(context.fieldInfo().getLength());
}
if (destClass == Character.class || destClass == Character.TYPE) {
return (context) -> context.fieldInfo().isUnsigned()
? (char) context.reader().readNextUnsignedValue(context.fieldInfo().getLength())
: (char) context.reader().readNextValue(context.fieldInfo().getLength());
}
return null;
}
private Object readStructureMappedTypeFunc(FieldContext<T> context) throws IOException {
return context.structureContext()
.getProgramContext()
.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.getProgramContext().markup(fieldContext.getValue(Object.class), true);
}
private FieldMarkupFunction<T> makeMarkupReferenceFunc(String getterName) {
getterName = getterName == null || getterName.isBlank() ? field.getName() : getterName;
Method getter = ReflectionHelper.requireGetter(field.getDeclaringClass(), getterName);
getter.setAccessible(true);
return (context) -> addRefToFieldWithGetter(getter, context);
}
private void addRefToFieldWithGetter(Method getterMethod, FieldContext<T> fieldContext)
throws IOException {
Object getterValue =
ReflectionHelper.callGetter(getterMethod, fieldContext.getStructureInstance());
if (getterValue != null) {
Address addr = getterValue instanceof Address getterAddr
? getterAddr
: fieldContext.getProgramContext().getExistingStructureAddress(getterValue);
if (addr != null) {
fieldContext.addReference(addr);
}
}
}
}

View file

@ -0,0 +1,22 @@
/* ###
* 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;
public interface FieldMarkupFunction<T> {
void markupField(FieldContext<T> fieldContext) throws IOException;
}

View file

@ -0,0 +1,67 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import ghidra.program.model.data.DataType;
/**
* Indicates that the tagged java field is to be included when constructing a variable length
* Ghidra structure data type.
* <p>
* Using this annotation on a field indicates that the containing Ghidra structure has
* variable length fields, and the containing class must implement the
* {@link StructureReader} interface to allow deserialization of instances of the containing class.
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface FieldOutput {
Class<? extends FieldOutputFunction> fieldOutputFunc() default FieldOutputFunction.class;
int ordinal() default -1;
int offset() default -1;
/**
* Specifies the name of a Ghidra {@link DataType} that will be used for this field when
* creating a Ghidra structure.
*
* @return
*/
String dataTypeName() default "";
/**
* 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
*/
boolean isVariableLength() default false;
/**
* Specifies a method that will return a Ghidra {@link DataType} that should be used for this
* field when creating a Ghidra structure.
*
* @return
*/
String getter() default "";
}

View file

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.structmapping;
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
* found in a Java class.
*
* @param <T>
*/
public interface FieldOutputFunction<T> {
void addFieldToStructure(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> fieldOutputInfo) throws IOException;
}

View file

@ -0,0 +1,215 @@
/* ###
* 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.Field;
import java.lang.reflect.Method;
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>
*/
public class FieldOutputInfo<T> {
private final FieldMappingInfo<T> fmi;
private final String dataTypeName;
private final int fieldOffset;
private final boolean isVariableLength;
private final int ordinal;
private FieldOutputFunction<T> outputFunc;
public FieldOutputInfo(FieldMappingInfo<T> fmi, String dataTypeName, boolean isVariableLength,
int ordinal, int fieldOffset) {
this.fmi = fmi;
this.dataTypeName = dataTypeName;
this.isVariableLength = isVariableLength;
this.ordinal = ordinal;
this.fieldOffset = fieldOffset;
}
public Field getField() {
return fmi.getField();
}
public int getOrdinal() {
return ordinal;
}
public boolean isVariableLength() {
return isVariableLength;
}
public FieldOutputFunction<T> getOutputFunc() {
return outputFunc;
}
/**
* Returns the value of this java field.
*
* @param <R>
* @param structInstance object containing the field
* @param expectedType expected class of the value
* @return value of the field
* @throws IOException
*/
public <R> R getValue(T structInstance, Class<R> expectedType) throws IOException {
try {
Object val = fmi.getField().get(structInstance);
if (expectedType.isInstance(val)) {
return expectedType.cast(val);
}
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new IOException(e);
}
return null;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setOutputFuncClass(Class<? extends FieldOutputFunction> funcClass,
String getterName) {
if (funcClass != FieldOutputFunction.class) {
this.outputFunc = ReflectionHelper.createInstance(funcClass, this);
return;
}
if (getterName != null && !getterName.isBlank()) {
Method getter =
ReflectionHelper.requireGetter(fmi.getField().getDeclaringClass(), getterName);
getter.setAccessible(true);
this.outputFunc =
(context, structure, unused) -> outputFuncWithGetter(getter, context, structure);
return;
}
if (dataTypeName != null && !dataTypeName.isBlank()) {
this.outputFunc = this::dataTypeNameOutputFunc;
return;
}
Class<?> fieldType = fmi.getField().getType();
if (ReflectionHelper.isPrimitiveType(fieldType)) {
this.outputFunc = this::primitiveOutputFunc;
return;
}
if (fieldType.isArray() && ReflectionHelper.isPrimitiveType(fieldType.getComponentType())) {
this.outputFunc = this::arrayOutputFunc;
return;
}
if (ReflectionHelper.hasStructureMapping(fieldType)) {
this.outputFunc = this::nestedStructureOutputFunc;
return;
}
throw new IllegalArgumentException("Invalid FieldOutput " + fmi.getField());
}
private void preAddField(Structure structure) throws IOException {
if (fieldOffset >= 0) {
int currentOffset = getStructLength(structure);
if (currentOffset > fieldOffset) {
throw new IOException("Invalid field offset %d, structure is already %d"
.formatted(fieldOffset, currentOffset));
}
if (currentOffset < fieldOffset) {
structure.add(Undefined.getUndefinedDataType(fieldOffset - currentOffset), -1);
}
}
}
private void outputFuncWithGetter(Method getter, StructureContext<T> context,
Structure structure) throws IOException {
Object getterResult =
ReflectionHelper.callGetter(getter, context.getStructureInstance(), Object.class);
if (getterResult == null) {
//do nothing
}
else if (getterResult instanceof DataType dt) {
preAddField(structure);
structure.add(dt, fmi.getFieldName(), null);
}
else if (getterResult instanceof DataTypeInstance dti) {
preAddField(structure);
structure.add(dti.getDataType(), dti.getLength(), fmi.getFieldName(), null);
}
else {
throw new IOException("Bad result type for FieldOutput.getter: %s"
.formatted(getterResult.getClass().getSimpleName()));
}
}
private void dataTypeNameOutputFunc(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> foi) throws IOException {
DataType dt = context.getProgramContext().getType(dataTypeName, DataType.class);
if (dt == null) {
throw new IOException(
"Missing data type %s for field %s".formatted(dataTypeName, fmi.getFieldName()));
}
preAddField(structure);
if (dt instanceof Dynamic) {
throw new IOException("Invalid dynamic sized data type %s for field %s"
.formatted(dt.getName(), fmi.getFieldName()));
}
structure.add(dt, fmi.getFieldName(), null);
}
private void primitiveOutputFunc(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> foi) throws IOException {
DataType dt = ReflectionHelper.getPrimitiveOutputDataType(fmi.getField().getType(),
fmi.getLength(), fmi.getSignedness(), context.getProgramContext());
preAddField(structure);
structure.add(dt, fmi.getFieldName(), null);
}
private void arrayOutputFunc(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> foi) throws IOException {
// only outputs array of primitive value
Object fieldValue = foi.getValue(context.getStructureInstance(), Object.class);
DataType dt = ReflectionHelper.getArrayOutputDataType(fieldValue, fmi.getField().getType(),
fmi.getLength(), fmi.getSignedness(), context.getProgramContext());
preAddField(structure);
structure.add(dt, fmi.getFieldName(), null);
}
private void nestedStructureOutputFunc(StructureContext<T> context, Structure structure,
FieldOutputInfo<T> foi) throws IOException {
Object nestedStruct = foi.getValue(context.getStructureInstance(), Object.class);
if (nestedStruct == null) {
return;
}
StructureContext<?> nestedStructContext =
context.getProgramContext().getExistingStructureContext(nestedStruct);
if (nestedStructContext == null) {
throw new IOException(
"Missing StructureContext for " + nestedStruct.getClass().getSimpleName());
}
DataType nestedStructDT = nestedStructContext.getStructureDataType();
preAddField(structure);
structure.add(nestedStructDT, fmi.getFieldName(), null);
}
private static int getStructLength(Structure struct) {
return struct.isZeroLength() ? 0 : struct.getLength();
}
}

View file

@ -0,0 +1,24 @@
/* ###
* 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;
@FunctionalInterface
public interface FieldReadFunction<T> {
Object get(FieldContext<T> context) throws IOException;
}

View file

@ -0,0 +1,36 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the objects in the return value of the tagged method should be recursively
* marked up, or if a tagged field, the object in the field should be recursively marked up.
* <p>
* The return value can be an object with {@link StructureMapping} info, or a
* collection / iterator of those objects.
*/
@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface Markup {
// nothing
}

View file

@ -0,0 +1,42 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import ghidra.program.model.address.Address;
/**
* Indicates that the <b>target</b> of the tagged field should be decorated in Ghidra as
* receiving a data reference from the location of the field.
* <p>
* The tagged field must have a 'getter' method that returns something that can be converted
* to an {@link Address}. This can either be an actual Address return value, or a return value
* that is an instance of an object that can be mapped to an address in the program.
* <p>
* The name of the 'getter' method can be overridden by providing a string that directly specifies
* the getter method name, or its sans-"get" name (eg. for getter method getXyz(), "getXyz", or
* "Xyz" are equally valid)
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface MarkupReference {
String value() default "";
}

View file

@ -0,0 +1,40 @@
/* ###
* 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 static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Indicates that the tagged class or field should have an plate comment placed
* before each instance of the object or field, using the return value of the field's
* getter method, or if a class, the object's "toString()" method.
*/
@Retention(RUNTIME)
@Target({ FIELD, TYPE })
public @interface PlateComment {
/**
* Name of a "getter" method that's return value will be converted to a string and used
* as the comment
*
* @return
*/
String value() default "";
}

View file

@ -0,0 +1,406 @@
/* ###
* 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 generic.jar.ResourceFile;
import ghidra.app.util.bin.BinaryReader;
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.
* <p>
* To use the full might and majesty of StructureMapping(tm), a ProgramContext must be created. It
* must be able to {@link #addArchiveSearchCategoryPath(CategoryPath...) find}
* ({@link #addProgramSearchCategoryPath(CategoryPath...) more find}) the Ghidra structure data
* types being used, and it must {@link #registerStructure(Class) know} about all classes that are
* going to participate during deserialization and markup.
* <p>
* Structure mapped classes can receive a reference to the specific ProgramContext type that
* created them by declaring a {@code ProgramContext} field, and tagging it with
* the @{@link ContextField} annotation:
*
* <pre>
* class MyProgramContext extends ProgramContext {
* public MyProgramContext() {
* ...
* registerStructure(MyDataType.class);
* }
* public void foo() { ... }
* }
*
* &#64;StructureMapping(structureName = "mydatatype")
* class MyDataType {
*
* &#64;ContextField
* private MyProgramContext myProgramContext;
*
* &#64;ContextField
* private StructureContext&lt;MyDataType&gt; context;
*
* &#64;FieldMapping
* private long someField;
*
* void bar() {
* context.getProgramContext().getProgram(); // can only access methods defined on base ProgramContext type
* myProgramContext.foo(); // same context as previous line, but typed correctly
* ...
* </pre>
*
*/
public class ProgramContext implements AutoCloseable {
protected Program program;
protected DataTypeManager programDTM;
protected DataTypeManager archiveDTM;
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;
/**
*
* @param program
* @param archiveGDT
* @throws IOException
*/
protected ProgramContext(Program program, ResourceFile archiveGDT) throws IOException {
this.program = program;
this.programDTM = program.getDataTypeManager();
this.archiveDTM = archiveGDT != null
? FileDataTypeManager.openFileArchive(archiveGDT, false)
: null;
}
@Override
public void close() {
if (archiveDTM != null) {
archiveDTM.close();
archiveDTM = null;
}
}
public CategoryPath getDefaultVariableLengthStructCategoryPath() {
return CategoryPath.ROOT;
}
public Program getProgram() {
return program;
}
protected BinaryReader createProgramReader() {
MemoryByteProvider bp =
new MemoryByteProvider(program.getMemory(), program.getImageBase().getAddressSpace());
return new BinaryReader(bp, !program.getMemory().isBigEndian());
}
public DataConverter getDataConverter() {
return DataConverter.getInstance(program.getMemory().isBigEndian());
}
public ProgramContext addProgramSearchCategoryPath(CategoryPath... paths) {
programSearchCPs.addAll(Arrays.asList(paths));
return this;
}
public ProgramContext addArchiveSearchCategoryPath(CategoryPath... paths) {
archiveSearchCPs.addAll(Arrays.asList(paths));
return this;
}
/**
* Registers a class that has {@link StructureMapping structure mapping} information.
*
* @param <T>
* @param clazz
* @throws IOException if the class's Ghidra structure data type could not be found
*/
public <T> void registerStructure(Class<T> clazz) throws IOException {
Structure structDT = null;
String structName = StructureMappingInfo.getStructureDataTypeNameForClass(clazz);
if (structName != null && !structName.isBlank()) {
structDT = getType(structName, Structure.class);
}
if (!StructureReader.class.isAssignableFrom(clazz) && structDT == null) {
if (structName == null || structName.isBlank()) {
structName = "<missing>";
}
throw new IOException(
"Missing struct definition %s - %s".formatted(clazz.getSimpleName(),
structName));
}
StructureMappingInfo<T> structMappingInfo = StructureMappingInfo.fromClass(clazz, structDT);
mappingInfo.put(clazz, structMappingInfo);
}
public void registerStructures(List<Class<?>> classes) throws IOException {
for (Class<?> clazz : classes) {
registerStructure(clazz);
}
}
@SuppressWarnings("unchecked")
public <T> StructureMappingInfo<T> getStructureMappingInfo(Class<T> clazz) {
StructureMappingInfo<?> smi = mappingInfo.get(clazz);
return (StructureMappingInfo<T>) smi;
}
@SuppressWarnings("unchecked")
public <T> StructureMappingInfo<T> getStructureMappingInfo(T structureInstance) {
return structureInstance != null
? (StructureMappingInfo<T>) mappingInfo.get(structureInstance.getClass())
: null;
}
/**
* Returns a Ghidra structure data type representing the specified class.
*
* @param clazz a structure mapped class
* @return {@link Structure} data type, or null if the class was a struct with variable length
* fields
*/
public Structure getStructureDataType(Class<?> clazz) {
StructureMappingInfo<?> mi = mappingInfo.get(clazz);
return mi != null ? mi.getStructureDataType() : null;
}
/**
* Returns the name of the Ghidra structure that has been registered for the specified
* structure mapped class.
*
* @param clazz
* @return
*/
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.
*
* @param <T>
* @param name
* @param clazz
* @return
*/
public <T extends DataType> T getType(String name, Class<T> clazz) {
DataType dataType = findType(name, programSearchCPs, programDTM);
if (dataType == null && archiveDTM != null) {
dataType = findType(name, archiveSearchCPs, archiveDTM);
if (dataType != null) {
dataType = programDTM.resolve(dataType, DWARFDataTypeConflictHandler.INSTANCE);
}
}
if (dataType == null) {
dataType =
BuiltInDataTypeManager.getDataTypeManager().getDataType(CategoryPath.ROOT, name);
}
return clazz.isInstance(dataType) ? clazz.cast(dataType) : null;
}
public <T extends DataType> T getTypeOrDefault(String name, Class<T> clazz, T defaultValue) {
T result = getType(name, clazz);
return result != null ? result : defaultValue;
}
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 {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
return smi != null ? smi.recoverStructureContext(structureInstance) : null;
}
/**
* Attempts to convert an instance of an object (that represents a chunk of memory in
* the program) into its Address.
*
* @param <T> type of the object
* @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 {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
StructureContext<T> structureContext = smi != null
? smi.recoverStructureContext(structureInstance)
: null;
return structureContext != null
? structureContext.getStructureAddress()
: 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);
}
}
public <T> T readStructure(Class<T> structureClass, BinaryReader structReader)
throws IOException {
StructureContext<T> structureContext = getStructureContext(structureClass, structReader);
T result = structureContext.readNewInstance();
return result;
}
public <T> T readStructure(Class<T> structureClass, long position) throws IOException {
return readStructure(structureClass, getReader(position));
}
public <T> T readStructure(Class<T> structureClass, Address address) throws IOException {
return readStructure(structureClass, getReader(address.getOffset()));
}
public BinaryReader getReader(long position) {
BinaryReader reader = createProgramReader();
reader.setPointerIndex(position);
return reader;
}
public Address getDataAddress(long offset) {
return program.getImageBase().getNewAddress(offset);
}
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 "ProgramContext { program: %s}".formatted(program.getName());
}
}

View file

@ -0,0 +1,352 @@
/* ###
* 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.util.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.lang.reflect.Array;
import ghidra.program.model.data.*;
public class ReflectionHelper {
private static final Set<Class<?>> NUM_CLASSES =
Set.of(Long.class, Long.TYPE, Integer.class, Integer.TYPE, Short.class, Short.TYPE,
Byte.class, Byte.TYPE, Character.class, Character.TYPE);
private static final Map<Class<?>, Integer> SIZEOF_NUM_CLASSES =
Map.ofEntries(
Map.entry(Long.class, Long.BYTES),
Map.entry(Long.TYPE, Long.BYTES),
Map.entry(Integer.class, Integer.BYTES),
Map.entry(Integer.TYPE, Integer.BYTES),
Map.entry(Short.class, Short.BYTES),
Map.entry(Short.TYPE, Short.BYTES),
Map.entry(Byte.class, Byte.BYTES),
Map.entry(Byte.TYPE, Byte.BYTES),
Map.entry(Character.class, Character.BYTES),
Map.entry(Character.TYPE, Character.BYTES));
private static final Map<Class<?>, String> DEFAULT_DATATYPE_NAME =
Map.ofEntries(
Map.entry(Long.class, "long"),
Map.entry(Long.TYPE, "long"),
Map.entry(Integer.class, "int"),
Map.entry(Integer.TYPE, "int"),
Map.entry(Short.class, "word"),
Map.entry(Short.TYPE, "word"),
Map.entry(Byte.class, "byte"),
Map.entry(Byte.TYPE, "byte"),
Map.entry(Character.class, "wchar"),
Map.entry(Character.TYPE, "wchar"));
public static boolean isPrimitiveType(Class<?> clazz) {
return NUM_CLASSES.contains(clazz);
}
/**
* Write a value to a field in a java class.
*
* @param field reflection {@link Field}
* @param obj java instance that contains the field
* @param value value to write
* @throws IOException
*/
public static void assignField(Field field, Object obj, Object value) throws IOException {
Class<?> fieldType = field.getType();
try {
if (fieldType.isPrimitive() && NUM_CLASSES.contains(value.getClass())) {
field.set(obj, value);
}
else {
if (!fieldType.isInstance(value)) {
throw new IOException("Bad conversion from %s to %s.%s:%s".formatted(
value.getClass().getSimpleName(),
obj.getClass().getSimpleName(),
field.getName(),
fieldType.getSimpleName()));
}
field.set(obj, value);
}
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new IOException(e);
}
}
/**
* Return Ghidra data type representing an array of primitive values.
*
* @param array_value java array object
* @param fieldType
* @param length
* @param signedness
* @param programContext
* @return
*/
public static DataType getArrayOutputDataType(Object array_value, Class<?> fieldType, int length,
Signedness signedness, ProgramContext programContext) {
int arrayLen = array_value != null ? Array.getLength(array_value) : 0;
Class<?> elementType = fieldType.getComponentType();
DataType elementDT =
getPrimitiveOutputDataType(elementType, length, signedness, programContext);
return new ArrayDataType(elementDT, arrayLen, -1, programContext.getDTM());
}
public static DataType getPrimitiveOutputDataType(Class<?> fieldType, int length,
Signedness signedness, ProgramContext programContext) {
boolean isChar = (fieldType == Character.class || fieldType == Character.TYPE);
DataTypeManager dtm = programContext.getDTM();
if (length == -1) {
length = getPrimitiveSizeof(fieldType);
}
if (signedness == Signedness.Unspecified) {
signedness = isChar ? Signedness.Unsigned : Signedness.Signed;
}
String defaultDtName = DEFAULT_DATATYPE_NAME.get(fieldType);
if (isChar && length == 1) {
defaultDtName = "char";
}
DataType dt = programContext.getType(defaultDtName, DataType.class);
if (dt == null && isChar) {
dt = switch (length) {
case 1 -> CharDataType.dataType;
case 2 -> WideChar16DataType.dataType;
default -> null;
};
}
if (dt == null || !matches(dt, length, signedness)) {
dt = signedness == Signedness.Signed
? AbstractIntegerDataType.getSignedDataType(length, dtm)
: AbstractIntegerDataType.getSignedDataType(length, dtm);
}
return dt;
}
private static boolean matches(DataType dt, int length, Signedness signedness) {
return (length == -1 || length == dt.getLength()) &&
(signedness == Signedness.Unspecified ||
(dt instanceof AbstractIntegerDataType intDT) &&
(signedness == Signedness.Signed) == intDT.isSigned());
}
public static int getPrimitiveSizeof(Class<?> fieldType) {
return SIZEOF_NUM_CLASSES.getOrDefault(fieldType, 1);
}
public static boolean hasStructureMapping(Class<?> clazz) {
return clazz.getAnnotation(StructureMapping.class) != null;
}
public static Signedness getDataTypeSignedness(DataType dt) {
if (dt instanceof TypeDef typedefDT) {
dt = typedefDT.getBaseDataType();
}
if (dt instanceof AbstractIntegerDataType intDT) {
return intDT.isSigned() ? Signedness.Signed : Signedness.Unsigned;
}
return Signedness.Signed; // default
}
public static Method getCommentMethod(Class<?> clazz, String commentGetterName,
String defaultGetterName) {
if (commentGetterName.isBlank()) {
commentGetterName = defaultGetterName;
}
Method commentGetter = ReflectionHelper.requireGetter(clazz, commentGetterName);
if (commentGetter == null) {
throw new IllegalArgumentException(
"Missing getter %s for %s".formatted(commentGetterName, clazz));
}
return commentGetter;
}
public static Method requireGetter(Class<?> clazz, String getterName) {
Method method = findGetter(clazz, getterName);
if (method == null) {
throw new IllegalArgumentException(
"Missing getter %s for %s".formatted(getterName, clazz));
}
return method;
}
public static Method findGetter(Class<?> structClass, String getterName) {
Method getter = getMethod(structClass, getterName);
if (getter == null) {
String getGetterName = "get%s%s".formatted(getterName.substring(0, 1).toUpperCase(),
getterName.substring(1));
getter = getMethod(structClass, getGetterName);
}
return getter;
}
public static <T> Constructor<T> getCtor(Class<T> clazz, Class<?>... paramTypes) {
try {
return clazz.getDeclaredConstructor(paramTypes);
}
catch (NoSuchMethodException | SecurityException e) {
// fail
}
return null;
}
static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
try {
// try both public and private methods in the class
return clazz.getDeclaredMethod(methodName, paramTypes);
}
catch (NoSuchMethodException | SecurityException e) {
// fail, next try inherited public methods
}
try {
return clazz.getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException | SecurityException e) {
// fail
}
return null;
}
public static void invokeMethods(List<Method> methods, Object obj, Object... params)
throws IOException {
for (Method method : methods) {
try {
method.invoke(obj, params);
}
catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException(e);
}
}
}
public static <T, CTX> T createInstance(Class<T> targetClass, CTX optionalContext)
throws IllegalArgumentException {
try {
if (optionalContext != null) {
Constructor<T> ctor = getCtor(targetClass, optionalContext.getClass());
if (ctor != null) {
ctor.setAccessible(true);
return ctor.newInstance(optionalContext);
}
}
Constructor<T> ctor = getCtor(targetClass);
if (ctor != null) {
ctor.setAccessible(true);
return ctor.newInstance();
}
throw new IllegalArgumentException("Missing ctor for " + targetClass.getSimpleName());
}
catch (SecurityException | InstantiationException | IllegalAccessException
| InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}
public static <T> T callCtor(Constructor<T> ctor, Object... params)
throws IllegalArgumentException {
try {
return ctor.newInstance(params);
}
catch (SecurityException | InstantiationException | IllegalAccessException
| InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}
public static <T> Object callGetter(Method getterMethod, T obj)
throws IOException {
return callGetter(getterMethod, obj, Object.class);
}
public static <T, R> R callGetter(Method getterMethod, T obj, Class<R> expectedType)
throws IOException {
try {
Object getterValue = getterMethod.invoke(obj);
if (getterValue == null || expectedType.isInstance(getterValue)) {
return expectedType.cast(getterValue);
}
return null;
}
catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException(e);
}
}
public static void getMarkedMethods(Class<?> targetClass,
Class<? extends Annotation> annotationClass, List<Method> methods,
boolean includeParentClasses, Class<?>... paramClasses) {
if (includeParentClasses && targetClass.getSuperclass() != null) {
getMarkedMethods(targetClass.getSuperclass(), annotationClass, methods,
includeParentClasses, paramClasses);
}
methodloop: for (Method method : targetClass.getDeclaredMethods()) {
if (method.getParameterCount() == paramClasses.length &&
method.getAnnotation(annotationClass) != null) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != paramClasses.length) {
continue;
}
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> methodParamClass = parameterTypes[i];
if (!methodParamClass.isAssignableFrom(paramClasses[i])) {
continue methodloop;
}
}
method.setAccessible(true);
methods.add(method);
}
}
}
public static <T extends Annotation> List<T> getAnnotations(Class<?> targetClass,
Class<T> annotationClass, List<T> result) {
if (result == null) {
result = new ArrayList<>();
}
T annotation = targetClass.getAnnotation(annotationClass);
if (annotation != null) {
result.add(annotation);
}
if (targetClass.getSuperclass() != null) {
getAnnotations(targetClass.getSuperclass(), annotationClass, result);
}
return result;
}
public static <R> R getFieldValue(Object obj, Field field, Class<R> expectedType)
throws IOException {
try {
Object val = field.get(obj);
if (val != null && !expectedType.isInstance(val)) {
throw new IOException(
"Unexpected field value type: " + val.getClass() + " in " + field);
}
return expectedType.cast(val);
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new IOException(e);
}
}
}

View file

@ -0,0 +1,20 @@
/* ###
* 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;
public enum Signedness {
Unspecified, Signed, Unsigned
}

View file

@ -0,0 +1,286 @@
/* ###
* 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 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
* Ghidra program.
* <p>
* All {@link StructureMapping} tagged classes must have a {@link ContextField} tagged
* StructureContext field for that class to be able to access meta-data about its self, and
* for other classes to reference it when performing markup:
* <pre>
* &#64;StructureMapping(structureName = "mydatatype")
* class MyDataType {
* &#64;ContextField
* private StructureContext&lt;MyDataType&gt; context;
*
* &#64;FieldMapping
* private long someField;
* ...
* </pre>
*
* @param <T> a java class that has been tagged with a {@link StructureMapping} annotation.
*/
public class StructureContext<T> {
protected final ProgramContext programContext;
protected final StructureMappingInfo<T> mappingInfo;
protected final BinaryReader reader;
protected final long structureStart;
protected T structureInstance;
protected Structure structureDataType;
public StructureContext(ProgramContext programContext, StructureMappingInfo<T> mappingInfo,
BinaryReader reader) {
this.programContext = programContext;
this.mappingInfo = mappingInfo;
this.reader = reader;
this.structureStart = reader.getPointerIndex();
this.structureDataType = mappingInfo.getStructureDataType();
}
public T readNewInstance() throws IOException {
structureInstance = mappingInfo.getInstanceCreator().get(this);
mappingInfo.assignContextFieldValues(this);
mappingInfo.readStructure(this);
ReflectionHelper.invokeMethods(mappingInfo.getAfterMethods(), structureInstance);
// TODO: capture actual structure length by checking BinaryReader position?
return structureInstance;
}
/**
* Returns the {@link StructureMappingInfo} for this structure's class.
*
* @return
*/
public StructureMappingInfo<T> getMappingInfo() {
return mappingInfo;
}
/**
* Returns a reference to the root {@link ProgramContext}, as a plain ProgramContext type. If
* a more specific ProgramContext type is needed, either type-cast this value, or use
* a {@link ContextField} tag on a field in your class that specifies the correct
* ProgramContext type.
*
* @return
*/
public ProgramContext getProgramContext() {
return programContext;
}
public Program getProgram() {
return programContext.program;
}
/**
* Returns the address in the program of this structure instance.
*
* @return {@link Address}
*/
public Address getStructureAddress() {
return programContext.getDataAddress(structureStart);
}
/**
* Returns the address of an offset from the start of this structure instance.
*
* @param fieldOffset
* @return
*/
public Address getFieldAddress(long fieldOffset) {
return getStructureAddress().add(fieldOffset);
}
/**
* Returns the stream location of an offset from the start of this structure instance.
*
* @param fieldOffset
* @return
*/
public long getFieldLocation(long fieldOffset) {
return structureStart + fieldOffset;
}
/**
* Returns the stream location of this structure instance.
*
* @return
*/
public long getStructureStart() {
return structureStart;
}
/**
* Returns the stream location of the end of this structure instance.
*
* @return
*/
public long getStructureEnd() {
return structureStart + getStructureLength();
}
/**
* Returns the length of this structure instance.
*
* @return
*/
public int getStructureLength() {
return structureDataType != null
? structureDataType.getLength()
: 0;
}
public T getStructureInstance() {
return structureInstance;
}
public BinaryReader getReader() {
return reader;
}
/**
* Returns an independent {@link BinaryReader} that is positioned at the start of the
* specified field.
*
* @param fieldOffset
* @return
*/
public BinaryReader getFieldReader(long fieldOffset) {
return reader.clone(structureStart + fieldOffset);
}
public FieldContext<T> createFieldContext(FieldMappingInfo<T> fmi, boolean includeReader) {
DataTypeComponent dtc = fmi.getDtc(structureDataType);
BinaryReader fieldReader = includeReader ? getFieldReader(dtc.getOffset()) : null;
FieldContext<T> readContext = new FieldContext<>(this, fmi, dtc, fieldReader);
return readContext;
}
/**
* Places a comment at the start of this structure, appending to any previous values
* already there.
*
* @param commentType
* @param prefix
* @param comment
* @param sep
* @throws IOException
*/
public void appendComment(int commentType, String prefix, String comment, String sep)
throws IOException {
DWARFUtil.appendComment(programContext.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 && !programContext.markedupStructs.add(addr)) {
return;
}
if (!nested) {
try {
Structure structDT = getStructureDataType();
programContext.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) {
programContext.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()) {
programContext.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
structureDataType = mappingInfo.createStructureDataType(this);
}
return structureDataType;
}
@Override
public String toString() {
return "StructureContext<%s> { offset: %s}".formatted(
mappingInfo.getTargetClass().getSimpleName(),
Long.toUnsignedString(structureStart, 16));
}
}

Some files were not shown because too many files have changed in this diff Show more