GP-2114 golang import / analyzer

This commit is contained in:
dev747368
2023-05-01 02:02:53 -04:00
committed by emteere
parent f862cdd93d
commit 87c16f9cd0
125 changed files with 12264 additions and 1492 deletions
@@ -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|
@@ -19,4 +19,5 @@ InstructionSkipper
DataTypeReferenceFinder
ChecksumAlgorithm
OverviewColorService
DWARFFunctionFixup
ElfInfoProducer
@@ -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
@@ -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>
@@ -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();
}
@@ -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);
}
}
@@ -48,6 +48,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) {
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program);
@@ -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)) {
if (dsp == null) {
Msg.info(this, "Unable to find DWARF information, skipping DWARF analysis");
return false;
}
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();
}
@@ -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;
}
}
@@ -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";
@@ -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(
@@ -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"),
@@ -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.
@@ -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
@@ -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;
}
@@ -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...
@@ -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;
@@ -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;
}
}
@@ -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;
@@ -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;
}
@@ -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);
}
}
@@ -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()));
}
}
}
}
@@ -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());
}
}
}
}
@@ -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();
}
}
}
}
@@ -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 */);
}
}
}
@@ -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");
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
@@ -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
@@ -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() &&
(typenameExplicitSize = getExplicitSizeFromTypeName(name)) != -1 &&
typenameExplicitSize / 8 == dwarfSize;
boolean usedFixedSizeType = predeterminedHasExplictSize ||
(importOptions.isSpecialCaseSizedBaseTypes() &&
(typenameExplicitSize = getExplicitSizeFromTypeName(name)) != -1 &&
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() +
@@ -747,7 +765,7 @@ public class DWARFDataTypeManager {
return funcDef;
}
/**
* Regex to match common fixed-size type names like "int64", "int64_t", etc, by triggering
* off some known size designators in the string.
@@ -777,4 +795,5 @@ public class DWARFDataTypeManager {
return -1;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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());
@@ -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);
}
/**
@@ -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;
}
}
@@ -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 */);
@@ -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");
}
@@ -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));
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}

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