mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-09 20:18:01 +08:00
GP-2114 golang import / analyzer
This commit is contained in:
@@ -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>
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -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);
|
||||
|
||||
+8
-9
@@ -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();
|
||||
}
|
||||
|
||||
+369
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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";
|
||||
|
||||
+6
-2
@@ -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(
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+38
-24
@@ -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;
|
||||
}
|
||||
|
||||
+35
@@ -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;
|
||||
|
||||
+219
-9
@@ -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><external_name tool="<b>name</b>" name="<b>value</b>"/></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><external_name tool="<b>name</b>" name="<b>value</b>"/></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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -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;
|
||||
|
||||
+3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+63
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -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 */);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+199
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
+173
-147
File diff suppressed because it is too large
Load Diff
+37
-18
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+361
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+251
-1094
File diff suppressed because it is too large
Load Diff
+8
-14
@@ -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;
|
||||
}
|
||||
|
||||
+68
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-63
@@ -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());
|
||||
|
||||
+20
-6
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+552
File diff suppressed because it is too large
Load Diff
+93
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+40
-7
@@ -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");
|
||||
}
|
||||
+321
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+207
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+271
@@ -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;
|
||||
}
|
||||
}
|
||||
+107
@@ -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;
|
||||
}
|
||||
}
|
||||
+221
@@ -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>
|
||||
* <golang>
|
||||
* <register_info versions="V1_17,V1_18">
|
||||
* <int_registers list="RAX,RBX,RCX,RDI,RSI,R8,R9,R10,R11"/>
|
||||
* <float_registers list="XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,XMM8,XMM9,XMM10,XMM11,XMM12,XMM13,XMM14"/>
|
||||
* <stack initialoffset="8" maxalign="8"/>
|
||||
* <current_goroutine register="R14"/>
|
||||
* <zero_register register="XMM15"/>
|
||||
* </register_info>
|
||||
* <register_info versions="V1_2">
|
||||
* ...
|
||||
* </register_info>
|
||||
* </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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+352
@@ -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
Reference in New Issue
Block a user