GP-2109 Golang analyzer improvements, gostrings.

Add GolangStringAnalyzer.

Improved recovery of function signatures (from method and interfacemetadata, also via stack trace info).

Golang package paths are used as Ghidra namespace.

Propagate RTTI from some built-in runtime alloc methods.
This commit is contained in:
dev747368 2023-11-30 12:16:44 -05:00
parent 9b0e95bccf
commit 272c6a4d1f
88 changed files with 8231 additions and 1741 deletions

View file

@ -90,6 +90,7 @@ data/typeinfo/golang/golang_1.17_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.18_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.19_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/golang_1.20_anybit_any.gdt||GHIDRA||||END|
data/typeinfo/golang/runtimesnapshot.go||GHIDRA||||END|
data/typeinfo/mac_10.9/mac_osx.gdt||GHIDRA||||END|
data/typeinfo/win32/msvcrt/clsids.txt||GHIDRA||reviewed||END|
data/typeinfo/win32/msvcrt/guids.txt||GHIDRA||reviewed||END|

View file

@ -6,55 +6,104 @@ runtime.exitThread
runtime.fatal
runtime.fatalthrow
runtime.fatalpanic
runtime.gopanic
runtime.panicdivide
runtime.throw
runtime.gopanic
runtime.goPanicExtendIndex
runtime.goPanicExtendIndexU
runtime.goPanicExtendSlice3Acap
runtime.goPanicExtendSlice3AcapU
runtime.goPanicExtendSlice3Alen
runtime.goPanicExtendSlice3AlenU
runtime.goPanicExtendSlice3B
runtime.goPanicExtendSlice3BU
runtime.goPanicExtendSlice3C
runtime.goPanicExtendSlice3CU
runtime.goPanicExtendSliceAcap
runtime.goPanicExtendSliceAcapU
runtime.goPanicExtendSliceAlen
runtime.goPanicExtendSliceAlenU
runtime.goPanicExtendSliceB
runtime.goPanicExtendSliceBU
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.goPanicSlice3Alen
runtime.goPanicSlice3AlenU
runtime.goPanicSlice3B
runtime.goPanicSlice3BU
runtime.goPanicSlice3C
runtime.goPanicSlice3CU
runtime.goPanicSliceAcap
runtime.goPanicSliceAcapU
runtime.goPanicSliceAlen
runtime.goPanicSliceAlenU
runtime.goPanicSliceB
runtime.goPanicSliceBU
runtime.goPanicSliceConvert
runtime.panic
runtime.panicCheck1
runtime.panicCheck2
runtime.panicdivide
runtime.panicdottypeE
runtime.panicdottypeI
runtime.panicExtendIndex
runtime.panicExtendIndexU
runtime.panicExtendSlice3Acap
runtime.panicExtendSlice3AcapU
runtime.panicExtendSlice3Alen
runtime.panicExtendSlice3AlenU
runtime.panicExtendSlice3B
runtime.panicExtendSlice3BU
runtime.panicExtendSlice3C
runtime.panicExtendSlice3CU
runtime.panicExtendSliceAcap
runtime.panicExtendSliceAcapU
runtime.panicExtendSliceAlen
runtime.panicExtendSliceAlenU
runtime.panicExtendSliceB
runtime.panicExtendSliceBU
runtime.panicfloat
runtime.panicIndex
runtime.panicIndex
runtime.panicIndexU
runtime.panicSliceAlen
runtime.panicSliceAlenU
runtime.panicSliceAcap
runtime.panicSliceAcapU
runtime.panicSliceB
runtime.panicSliceBU
runtime.panicSlice3Alen
runtime.panicSlice3AlenU
runtime.panicmakeslicecap
runtime.panicmakeslicelen
runtime.panicmem
runtime.panicmemAddr
runtime.panicnildottype
runtime.panicoverflow
runtime.panicshift
runtime.panicSlice3Acap
runtime.panicSlice3Acap
runtime.panicSlice3AcapU
runtime.panicSlice3Alen
runtime.panicSlice3Alen
runtime.panicSlice3AlenU
runtime.panicSlice3B
runtime.panicSlice3BU
runtime.panicSlice3C
runtime.panicSlice3CU
runtime.panicSliceAcap
runtime.panicSliceAcapU
runtime.panicSliceAcapU
runtime.panicSliceAlen
runtime.panicSliceAlenU
runtime.panicSliceAlenU
runtime.panicSliceB
runtime.panicSliceBU
runtime.panicSliceConvert
runtime.panicUnaligned
runtime.panicunsafeslicelen
runtime.panicunsafeslicelen1
runtime.panicunsafeslicenilptr
runtime.panicunsafeslicenilptr1
runtime.panicunsafestringlen
runtime.panicunsafestringnilptr
runtime.panicwrap
runtime.panicdottypeE
runtime.panicdottypeI
runtime.panicnildottype
runtime.panicoverflow
runtime.panicfloat
runtime.panicmem
runtime.panicmemAddr
runtime.panicshift
runtime.goexit0
runtime.goexit0.abi0

File diff suppressed because it is too large Load diff

View file

@ -1,81 +0,0 @@
/* ###
* 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.util.HashMap;
import ghidra.program.model.listing.Program;
/**
* AnalysisStateInfo holds onto AnalysisState information associated with analysis of a
* particular program. An AnalysisState can be associated with an individual analyzer
* or a group of analyzers which maintain information common to those analyzers for a
* program's analysis. The point of an analysis state is to maintain information between
* individual invocations of a particular analyzer or between differing analyzers that
* have information in common. For example, an instruction type of analyzer is invoked
* repeatedly during analysis as various blocks of instructions are created by disassembly.
* If you need to maintain a set of addresses that have been processed by the analyzer so
* that they aren't unnecessarily reprocessed, you could maintain the address set in an
* analysis state. This allows the analysis information to be maintained from one invocation
* to the next or from one analyzer to another.
*/
public class AnalysisStateInfo {
private static HashMap<Program, HashMap<Class<? extends AnalysisState>, AnalysisState>> programStates =
new HashMap<>();
private AnalysisStateInfo() {
// no construct
}
/**
* Return previously stored <code>AnalysisState</code> of the specified analysisStateClass type
* for the specified program.
* @param program
* @param analysisStateClass type of <code>AnalysisState</code>
* @return analysis state or null if not previously stored via {@link #putAnalysisState(Program, AnalysisState)}
*/
@SuppressWarnings("unchecked") // putAnalysisState ensures that stored instance corresponds to key class
public static <T extends AnalysisState> T getAnalysisState(Program program,
Class<T> analysisStateClass) {
HashMap<Class<? extends AnalysisState>, AnalysisState> stateMap =
programStates.get(program);
if (stateMap != null) {
// Found the map for this program.
return (T) stateMap.get(analysisStateClass);
}
return null;
}
/**
* Store/replace a specific AnalysisState implementation for a specific program.
* Note that only a single instance of a given AnaysisState class implementation
* will be stored for a given program.
* @param program
* @param state analysis state instance
*/
public static void putAnalysisState(final Program program, AnalysisState state) {
HashMap<Class<? extends AnalysisState>, AnalysisState> stateMap =
programStates.get(program);
if (stateMap == null) {
stateMap = new HashMap<>();
programStates.put(program, stateMap);
program.addCloseListener(doa -> programStates.remove(program));
}
stateMap.put(state.getClass(), state);
}
}

View file

@ -23,6 +23,7 @@ import ghidra.app.util.bin.format.dwarf4.DWARFPreconditionException;
import ghidra.app.util.bin.format.dwarf4.next.*;
import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProvider;
import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.DWARFSectionProviderFactory;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.AddressSetView;
@ -34,83 +35,10 @@ import ghidra.util.task.TaskMonitor;
public class DWARFAnalyzer extends AbstractAnalyzer {
private static final String DWARF_LOADED_OPTION_NAME = "DWARF Loaded";
private static final String OPTION_IMPORT_DATATYPES = "Import Data Types";
private static final String OPTION_IMPORT_DATATYPES_DESC =
"Import data types defined in the DWARF debug info.";
private static final String OPTION_PRELOAD_ALL_DIES = "Preload All DIEs";
private static final String OPTION_PRELOAD_ALL_DIES_DESC =
"Preload all DIE records. Requires more memory, but necessary for some non-standard " +
"layouts.";
private static final String OPTION_IMPORT_FUNCS = "Import Functions";
private static final String OPTION_IMPORT_FUNCS_DESC =
"Import function information defined in the DWARF debug info\n" +
"(implies 'Import Data Types' is selected).";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug Item Limit";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_DESC =
"If the number of DWARF debug items are greater than this setting, DWARF analysis will " +
"be skipped.";
private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source Info";
private static final String OPTION_OUTPUT_SOURCE_INFO_DESC =
"Include source code location info (filename:linenumber) in comments attached to the " +
"Ghidra datatype or function or variable created.";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC =
"Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " +
"or variable created.";
private static final String OPTION_NAME_LENGTH_CUTOFF = "Maximum Name Length";
private static final String OPTION_NAME_LENGTH_CUTOFF_DESC =
"Truncate symbol and type names longer than this limit. Range 20..2000";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Add Lexical Block Comments";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC =
"Add comments to the start of lexical blocks";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS =
"Add Inlined Functions Comments";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC =
"Add comments to the start of inlined functions";
private static final String OPTION_OUTPUT_FUNC_SIGS = "Create Function Signatures";
private static final String OPTION_OUTPUT_FUNC_SIGS_DESC =
"Create function signature data types for each function encountered in the DWARF debug " +
"data.";
private static final String OPTION_TRY_PACK_STRUCTS = "Try To Pack Structs";
private static final String OPTION_TRY_PACK_STRUCTS_DESC =
"Try to pack structure/union data types.";
private static final String DWARF_ANALYZER_NAME = "DWARF";
private static final String DWARF_ANALYZER_DESCRIPTION =
"Automatically extracts DWARF info from an ELF file.";
"Automatically extracts DWARF info from ELF/MachO/PE files.";
//==================================================================================================
// Old Option Names - Should stick around for multiple major versions after 10.2
//==================================================================================================
private static final String OPTION_IMPORT_DATATYPES_OLD = "Import data types";
private static final String OPTION_PRELOAD_ALL_DIES_OLD = "Preload all DIEs";
private static final String OPTION_IMPORT_FUNCS_OLD = "Import functions";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_OLD = "Debug item count limit";
private static final String OPTION_OUTPUT_SOURCE_INFO_OLD = "Output Source info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_OLD = "Output DWARF DIE info";
private static final String OPTION_NAME_LENGTH_CUTOFF_OLD = "Name length cutoff";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD = "Lexical block comments";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD =
"Inlined functions comments";
private static final String OPTION_OUTPUT_FUNC_SIGS_OLD = "Output function signatures";
private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater();
//==================================================================================================
// End Old Option Names
//==================================================================================================
/**
* Returns true if DWARF has already been imported into the specified program.
@ -132,26 +60,6 @@ public class DWARFAnalyzer extends AbstractAnalyzer {
setDefaultEnablement(true);
setPriority(AnalysisPriority.FORMAT_ANALYSIS.after());
setSupportsOneTimeAnalysis();
optionsUpdater.registerReplacement(OPTION_IMPORT_DATATYPES,
OPTION_IMPORT_DATATYPES_OLD);
optionsUpdater.registerReplacement(OPTION_PRELOAD_ALL_DIES,
OPTION_PRELOAD_ALL_DIES_OLD);
optionsUpdater.registerReplacement(OPTION_IMPORT_FUNCS,
OPTION_IMPORT_FUNCS_OLD);
optionsUpdater.registerReplacement(OPTION_IMPORT_LIMIT_DIE_COUNT,
OPTION_IMPORT_LIMIT_DIE_COUNT_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_SOURCE_INFO,
OPTION_OUTPUT_SOURCE_INFO_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_DWARF_DIE_INFO,
OPTION_OUTPUT_DWARF_DIE_INFO_OLD);
optionsUpdater.registerReplacement(OPTION_NAME_LENGTH_CUTOFF,
OPTION_NAME_LENGTH_CUTOFF_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS,
OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_INLINE_FUNC_COMMENTS,
OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_FUNC_SIGS, OPTION_OUTPUT_FUNC_SIGS_OLD);
}
@Override
@ -186,6 +94,11 @@ public class DWARFAnalyzer extends AbstractAnalyzer {
return false;
}
if (GoRttiMapper.isGolangProgram(program)) {
Msg.info(this, "DWARF: Enabling DIE preload for golang binary");
importOptions.setPreloadAllDIEs(true);
}
try {
try (DWARFProgram prog = new DWARFProgram(program, importOptions, monitor, dsp)) {
if (prog.getRegisterMappings() == null && importOptions.isImportFuncs()) {
@ -235,72 +148,17 @@ public class DWARFAnalyzer extends AbstractAnalyzer {
@Override
public void registerOptions(Options options, Program program) {
options.registerOption(OPTION_IMPORT_DATATYPES, importOptions.isImportDataTypes(), null,
OPTION_IMPORT_DATATYPES_DESC);
options.registerOption(OPTION_PRELOAD_ALL_DIES, importOptions.isPreloadAllDIEs(), null,
OPTION_PRELOAD_ALL_DIES_DESC);
options.registerOption(OPTION_IMPORT_FUNCS, importOptions.isImportFuncs(), null,
OPTION_IMPORT_FUNCS_DESC);
options.registerOption(OPTION_OUTPUT_DWARF_DIE_INFO, importOptions.isOutputDIEInfo(), null,
OPTION_OUTPUT_DWARF_DIE_INFO_DESC);
options.registerOption(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS,
importOptions.isOutputLexicalBlockComments(), null,
OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC);
options.registerOption(OPTION_OUTPUT_INLINE_FUNC_COMMENTS,
importOptions.isOutputInlineFuncComments(), null,
OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC);
options.registerOption(OPTION_OUTPUT_SOURCE_INFO,
importOptions.isOutputSourceLocationInfo(), null, OPTION_OUTPUT_SOURCE_INFO_DESC);
options.registerOption(OPTION_IMPORT_LIMIT_DIE_COUNT,
importOptions.getImportLimitDIECount(), null, OPTION_IMPORT_LIMIT_DIE_COUNT_DESC);
options.registerOption(OPTION_NAME_LENGTH_CUTOFF, importOptions.getNameLengthCutoff(), null,
OPTION_NAME_LENGTH_CUTOFF_DESC);
options.registerOption(OPTION_OUTPUT_FUNC_SIGS, importOptions.isCreateFuncSignatures(),
null, OPTION_OUTPUT_FUNC_SIGS_DESC);
options.registerOption(OPTION_TRY_PACK_STRUCTS, importOptions.isTryPackStructs(),
null, OPTION_TRY_PACK_STRUCTS_DESC);
importOptions.registerOptions(options);
}
@Override
public AnalysisOptionsUpdater getOptionsUpdater() {
return optionsUpdater;
return importOptions.getOptionsUpdater();
}
@Override
public void optionsChanged(Options options, Program program) {
importOptions.setOutputDIEInfo(
options.getBoolean(OPTION_OUTPUT_DWARF_DIE_INFO, importOptions.isOutputDIEInfo()));
importOptions.setPreloadAllDIEs(
options.getBoolean(OPTION_PRELOAD_ALL_DIES, importOptions.isPreloadAllDIEs()));
importOptions.setOutputSourceLocationInfo(options.getBoolean(OPTION_OUTPUT_SOURCE_INFO,
importOptions.isOutputSourceLocationInfo()));
importOptions.setOutputLexicalBlockComments(options.getBoolean(
OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, importOptions.isOutputLexicalBlockComments()));
importOptions.setOutputInlineFuncComments(options.getBoolean(
OPTION_OUTPUT_INLINE_FUNC_COMMENTS, importOptions.isOutputInlineFuncComments()));
importOptions.setImportDataTypes(
options.getBoolean(OPTION_IMPORT_DATATYPES, importOptions.isImportDataTypes()));
importOptions.setImportFuncs(
options.getBoolean(OPTION_IMPORT_FUNCS, importOptions.isImportFuncs()));
importOptions.setImportLimitDIECount(
options.getInt(OPTION_IMPORT_LIMIT_DIE_COUNT, importOptions.getImportLimitDIECount()));
importOptions.setNameLengthCutoff(
options.getInt(OPTION_NAME_LENGTH_CUTOFF, importOptions.getNameLengthCutoff()));
importOptions.setCreateFuncSignatures(
options.getBoolean(OPTION_OUTPUT_FUNC_SIGS, importOptions.isCreateFuncSignatures()));
importOptions.setTryPackDataTypes(
options.getBoolean(OPTION_TRY_PACK_STRUCTS, importOptions.isTryPackStructs()));
importOptions.optionsChanged(options);
}
}

View file

@ -0,0 +1,376 @@
/* ###
* 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.IOException;
import java.util.function.Predicate;
import ghidra.app.services.*;
import ghidra.app.util.bin.format.golang.GoParamStorageAllocator;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.MarkupSession;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.*;
import ghidra.program.model.data.Pointer;
import ghidra.program.model.data.Undefined;
import ghidra.program.model.lang.OperandType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.StringUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
/**
* Analyzer that finds Golang strings (and optionally slices) and marks up the found instances.
* <p>
* The char[] data for Golang strings does not contain null terminators, so the normal logic already
* built into Ghidra to find terminated strings doesn't work.
* <p>
* This implementation looks for data that matches what a Golang string
* struct { char* data, long len } would look like, and follows the pointer to the char[] data
* and creates a fixed-length string at that location using the length info from the struct.
* <p>
* The string struct is found in a couple of different ways:
* <ul>
* <li>References from an instruction (see markupStaticStructRefsInFunction)
* <li>Iterating through data segments and making educated guesses (see markupDataSegmentStructs)
* </ul>
* Some char[] data is only referenced from Golang string structs that exist temporarily
* in registers after being set by an instruction that statically references the char[] data,
* and an instruction that statically contains the length. (see tryCreateInlineString)
* <p>
* Because slice structures can look like string structs, possible string struct locations are also
* examined for slice-ness. When marking a struct as a slice instead of as a string, the data
* pointed to by the slice is not marked up because there is no information about the size of the
* elements that the slice points to.
*/
public class GolangStringAnalyzer extends AbstractAnalyzer {
private final static String NAME = "Golang String Analyzer";
private final static String DESCRIPTION =
"Finds and labels Go string structures that have been referenced from a function.";
private GolangStringAnalyzerOptions analyzerOptions = new GolangStringAnalyzerOptions();
private GoRttiMapper goBinary;
private MarkupSession markupSession;
private GoParamStorageAllocator storageAllocator;
public GolangStringAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
setPriority(AnalysisPriority.REFERENCE_ANALYSIS.after());
setDefaultEnablement(true);
}
@Override
public void registerOptions(Options options, Program program_notused) {
analyzerOptions.registerOptions(options);
}
@Override
public void optionsChanged(Options options, Program program_notused) {
analyzerOptions.optionsChanged(options);
}
@Override
public boolean canAnalyze(Program program) {
return GoRttiMapper.isGolangProgram(program);
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
if (goBinary == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper");
return false;
}
markupSession = goBinary.createMarkupSession(monitor);
storageAllocator = goBinary.newStorageAllocator();
try {
goBinary.initTypeInfoIfNeeded(monitor);
markupStaticStructRefsInFunctions(set, monitor);
if (analyzerOptions.markupDataSegmentStructs) {
markupDataSegmentStructs(monitor);
}
}
catch (IOException e) {
Msg.error(this, "Golang analysis failure", e);
}
return true;
}
private static boolean alignStartOfSet(AddressSet set, int align) {
while (!set.isEmpty()) {
Address addr = set.getMinAddress();
int mod = (int) (addr.getOffset() % align);
if (mod == 0) {
return true;
}
AddressRange range = set.getFirstRange();
addr = addr.add(align - mod - 1);
set.deleteFromMin(range.contains(addr) ? addr : range.getMaxAddress());
}
return false;
}
private void markupStaticStructRefsInFunctions(AddressSetView set, TaskMonitor monitor)
throws IOException, CancelledException {
monitor = new UnknownProgressWrappingTaskMonitor(monitor);
monitor.initialize(1, "Searching for Golang structure references in functions");
FunctionManager funcManager = goBinary.getProgram().getFunctionManager();
for (Function function : funcManager.getFunctions(set, true)) {
monitor.checkCancelled();
markupStaticStructRefsInFunction(function, monitor);
}
}
private void markupStaticStructRefsInFunction(Function function, TaskMonitor monitor)
throws IOException, CancelledException {
ReferenceManager refManager = goBinary.getProgram().getReferenceManager();
Listing listing = goBinary.getProgram().getListing();
AddressSet stringDataRange = new AddressSet(goBinary.getStringDataRange());
AddressSetView validStructRange = goBinary.getStringStructRange();
AddressSetView funcBody = function.getBody();
for (Reference ref : refManager.getReferenceIterator(function.getEntryPoint())) {
monitor.increment();
if (!funcBody.contains(ref.getFromAddress())) {
// limit the reference iterator to the function body
break;
}
Address addr = ref.getToAddress();
if (ref.getReferenceType() != RefType.DATA) {
continue;
}
if (validStructRange.contains(addr) && canCreateStructAt(addr)) {
if (tryCreateStruct(stringDataRange, addr) != null) {
continue;
}
if (!stringDataRange.contains(addr)) {
continue;
}
Instruction instr = listing.getInstructionContaining(ref.getFromAddress());
GoString inlineStr = tryCreateInlineString(funcBody, stringDataRange, addr, instr,
this::isValidStringData);
if (inlineStr != null) {
// just markup the char data, there is no struct body
inlineStr.additionalMarkup(markupSession);
}
}
}
}
private GoString tryCreateInlineString(AddressSetView funcBody, AddressSet stringDataRange,
Address stringDataAddr, Instruction instr1, Predicate<String> stringContentValidator) {
// Precondition: instr1 has a ref to a location that could be the chars of a
// string (stringDataAddr).
// The logic matches on 2 consecutive instructions that load 2 consecutive go param
// storage allocator registers (ex AX, then BX), because we know that a go string struct
// can fit into 2 registers when passed to a function
// This needs to be improved to handle initializing a stack parameter.
Instruction instr2 = instr1.getNext();
if (instr2 == null || !funcBody.contains(instr2.getAddress())) {
return null;
}
Register strAddrReg = getRegisterFromInstr(instr1);
Register strLenReg = storageAllocator.getNextIntParamRegister(strAddrReg);
if (strLenReg != null && strLenReg.contains(getRegisterFromInstr(instr2))) {
// the first register was from the set of param passing registers, and we now know
// what the second register must be to match the pattern
long strLen = getScalarFromInstr(instr2);
try {
GoString str = GoString.createInlineString(goBinary, stringDataAddr, strLen);
if (str.isValidInlineString(stringDataRange, stringContentValidator)) {
return str;
}
}
catch (IOException e) {
// fail
}
}
return null;
}
private Register getRegisterFromInstr(Instruction instr) {
if (instr.getNumOperands() != 2 || instr.getOperandType(0) != OperandType.REGISTER) {
return null;
}
return (Register) instr.getOpObjects(0)[0];
}
private long getScalarFromInstr(Instruction instr) {
if (instr.getNumOperands() != 2) {
return -1;
}
Object operand1Obj0 = instr.getOpObjects(1)[0];
return instr.getOperandType(1) == OperandType.SCALAR && operand1Obj0 instanceof Scalar s
? s.getUnsignedValue()
: -1;
}
private void markupDataSegmentStructs(TaskMonitor monitor)
throws IOException, CancelledException {
AddressSet structDataRange = new AddressSet(goBinary.getStringStructRange());
structDataRange.delete(markupSession.getMarkedupAddresses());
AddressSet stringDataRange = new AddressSet(goBinary.getStringDataRange());
long initAddrCount = structDataRange.getNumAddresses();
monitor.initialize(initAddrCount,
"Searching for Golang strings & structures in data segments");
int stringCount = 0;
int sliceCount = 0;
int align = goBinary.getPtrSize();
while (alignStartOfSet(structDataRange, align)) {
monitor.setProgress(initAddrCount - structDataRange.getNumAddresses());
monitor.checkCancelled();
Address addr = structDataRange.getMinAddress();
if (!canCreateStructAt(addr)) {
Data data = goBinary.getProgram().getListing().getDataContaining(addr);
if (data != null) {
structDataRange.deleteFromMin(data.getMaxAddress());
}
continue;
}
structDataRange.deleteFromMin(addr);
Object newObj = tryCreateStruct(stringDataRange, addr);
if (newObj != null) {
structDataRange.deleteFromMin(goBinary.getMaxAddressOfStructure(newObj));
stringCount += newObj instanceof GoString ? 1 : 0;
sliceCount += newObj instanceof GoSlice ? 1 : 0;
monitor.setMessage("Searching for Golang strings & slices in data segments: %d+%d"
.formatted(stringCount, sliceCount));
}
}
}
private Object tryCreateStruct(AddressSet stringDataRange, Address addr)
throws IOException, CancelledException {
// test to see if its a slice first because strings can kinda look like slices (a pointer
// then a length field).
Object newObj = tryReadSliceStruct(addr);
if (newObj == null) {
newObj = tryReadStringStruct(stringDataRange, addr);
}
if (newObj != null) {
boolean doMarkup = analyzerOptions.markupSliceStructs || !(newObj instanceof GoSlice);
if (doMarkup) {
markupSession.markup(newObj, false);
if (newObj instanceof GoString goStr) {
stringDataRange.delete(goStr.getStringDataRange());
}
}
}
return newObj;
}
private GoString tryReadStringStruct(AddressSetView stringDataRange, Address addr) {
try {
GoString goString = goBinary.readStructure(GoString.class, addr);
if (goString.isValid(stringDataRange, this::isValidStringData)) {
return goString;
}
}
catch (IOException e) {
// ignore, skip
}
return null;
}
private boolean isValidStringData(String s) {
// naive test that ensures that the candidate string doesn't have 'garbage' characters
// TODO: this should be wired into the string model logic
return (s != null) && !s.codePoints().anyMatch(this::isBadCodePoint);
}
private boolean isBadCodePoint(int codePoint) {
return codePoint == StringUtilities.UNICODE_REPLACEMENT ||
(0 <= codePoint && codePoint < 32) && !(codePoint == '\n' || codePoint == '\t');
}
private GoSlice tryReadSliceStruct(Address addr) {
try {
GoSlice slice = goBinary.readStructure(GoSlice.class, addr);
if (slice.getLen() != 0 && slice.isFull() && slice.isValid()) {
return slice;
}
}
catch (IOException e) {
// ignore, skip
}
return null;
}
private boolean canCreateStructAt(Address addr) {
Data data = goBinary.getProgram().getListing().getDataContaining(addr);
return data == null || !data.isDefined() || Undefined.isUndefined(data.getDataType()) ||
(data.getDataType() instanceof Pointer ptr && ptr.getDataType() == null);
}
//-------------------------------------------------------------------------------------------
private static class GolangStringAnalyzerOptions {
static final String MARKUP_SLICES_OPTIONNAME = "Markup slices";
static final String MARKUP_SLICES_DESC = "Markup things that look like slices.";
static final String MARKUP_STRUCTS_IN_DATA_OPTIONNAME = "Search data segments";
static final String MARKUP_STRUCTS_IN_DATA_DESC =
"Search for strings and slices in data segments.";
boolean markupSliceStructs = true;
boolean markupDataSegmentStructs = true;
void registerOptions(Options options) {
options.registerOption(GolangStringAnalyzerOptions.MARKUP_SLICES_OPTIONNAME,
markupSliceStructs, null, GolangStringAnalyzerOptions.MARKUP_SLICES_DESC);
options.registerOption(GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_OPTIONNAME,
markupDataSegmentStructs, null,
GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_DESC);
}
void optionsChanged(Options options) {
markupDataSegmentStructs =
options.getBoolean(GolangStringAnalyzerOptions.MARKUP_STRUCTS_IN_DATA_OPTIONNAME,
markupDataSegmentStructs);
markupSliceStructs = options.getBoolean(
GolangStringAnalyzerOptions.MARKUP_SLICES_OPTIONNAME, markupSliceStructs);
}
}
}

View file

@ -15,30 +15,43 @@
*/
package ghidra.app.plugin.core.analysis;
import static ghidra.app.util.bin.format.golang.GoConstants.*;
import static java.util.stream.Collectors.*;
import static java.util.stream.StreamSupport.*;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import generic.jar.ResourceFile;
import ghidra.app.cmd.comments.SetCommentCmd;
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
import ghidra.app.services.*;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
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.*;
import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo;
import ghidra.app.util.bin.format.golang.rtti.types.GoType;
import ghidra.app.util.bin.format.golang.structmapping.MarkupSession;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.program.util.SymbolicPropogator.Value;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.*;
@ -48,7 +61,8 @@ import ghidra.xml.XmlParseException;
import utilities.util.FileUtilities;
/**
* Analyzes Golang binaries for RTTI and function symbol information.
* Analyzes Golang binaries for RTTI and function symbol information by following references from
* the root GoModuleData instance.
*/
public class GolangSymbolAnalyzer extends AbstractAnalyzer {
@ -57,58 +71,86 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
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 static final String ANALYZED_FLAG_OPTION_NAME = "Golang Analyzed";
private GolangAnalyzerOptions analyzerOptions = new GolangAnalyzerOptions();
private GoRttiMapper goBinary;
private MarkupSession markupSession;
private AutoAnalysisManager aam;
private long lastTxId = -1;
public GolangSymbolAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
setPriority(AnalysisPriority.FORMAT_ANALYSIS.after().after());
setDefaultEnablement(true);
}
@Override
public boolean canAnalyze(Program program) {
return GoRttiMapper.isGolangProgram(program);
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
long txId = program.getCurrentTransactionInfo().getID();
if (txId == lastTxId) {
// Only run once per analysis session - as denoted by being in the same transaction
return true;
}
lastTxId = txId;
if (isAlreadyImported(program)) {
Msg.info(this, "Golang analysis already performed, skipping.");
return false;
}
monitor.setMessage("Golang symbol analyzer");
try (GoRttiMapper goBinary = GoRttiMapper.getMapperFor(program, log)) {
if (goBinary == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper");
return false;
}
goBinary.init(monitor);
goBinary.discoverGoTypes(monitor);
aam = AutoAnalysisManager.getAnalysisManager(program);
UnknownProgressWrappingTaskMonitor upwtm =
new UnknownProgressWrappingTaskMonitor(monitor, 100);
upwtm.initialize(0);
upwtm.setMessage("Marking up Golang RTTI structures");
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
if (goBinary == null) {
Msg.error(this, "Golang analyzer error: unable to get GoRttiMapper");
return false;
}
MarkupSession markupSession = goBinary.createMarkupSession(upwtm);
try {
goBinary.initTypeInfoIfNeeded(monitor);
goBinary.initMethodInfoIfNeeded();
markupSession = goBinary.createMarkupSession(monitor);
GoModuledata firstModule = goBinary.getFirstModule();
if (firstModule != null) {
markupSession.labelStructure(firstModule, "firstmoduledata");
markupSession.labelStructure(firstModule, "firstmoduledata", null);
markupSession.markup(firstModule, false);
}
markupWellknownSymbols(goBinary, markupSession);
setupProgramContext(goBinary, markupSession);
markupWellknownSymbols();
setupProgramContext();
goBinary.recoverDataTypes(monitor);
markupGoFunctions(goBinary, markupSession);
fixupNoReturnFuncs(program);
markupMiscInfoStructs(program);
markupGoFunctions(monitor);
if (analyzerOptions.fixupDuffFunctions) {
fixDuffFunctions();
}
if (analyzerOptions.propagateRtti) {
aam.schedule(new PropagateRttiBackgroundCommand(goBinary),
AnalysisPriority.REFERENCE_ANALYSIS.after().priority());
}
if (analyzerOptions.createBootstrapDatatypeArchive) {
createBootstrapGDT(goBinary, program, monitor);
createBootstrapGDT(monitor);
}
program.getOptions(Program.PROGRAM_INFO).setBoolean(ANALYZED_FLAG_OPTION_NAME, true);
return true;
}
catch (IOException e) {
Msg.error(this, "Golang analysis failure", e);
}
return true;
return false;
}
@Override
@ -116,6 +158,12 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
options.registerOption(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME,
analyzerOptions.createBootstrapDatatypeArchive, null,
GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_DESC);
options.registerOption(GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_OPTIONNAME,
analyzerOptions.outputSourceInfo, null, GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_DESC);
options.registerOption(GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_OPTIONNAME,
analyzerOptions.fixupDuffFunctions, null, GolangAnalyzerOptions.FIXUP_DUFF_FUNCS_DESC);
options.registerOption(GolangAnalyzerOptions.PROP_RTTI_OPTIONNAME,
analyzerOptions.propagateRtti, null, GolangAnalyzerOptions.PROP_RTTI_DESC);
}
@Override
@ -123,143 +171,219 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
analyzerOptions.createBootstrapDatatypeArchive =
options.getBoolean(GolangAnalyzerOptions.CREATE_BOOTSTRAP_GDT_OPTIONNAME,
analyzerOptions.createBootstrapDatatypeArchive);
analyzerOptions.outputSourceInfo = options.getBoolean(
GolangAnalyzerOptions.OUTPUT_SOURCE_INFO_OPTIONNAME, analyzerOptions.outputSourceInfo);
}
private void markupWellknownSymbols(GoRttiMapper goBinary, MarkupSession session)
throws IOException {
private void markupWellknownSymbols() throws IOException {
Program program = goBinary.getProgram();
Symbol g0 = SymbolUtilities.getUniqueSymbol(program, "runtime.g0");
Structure gStruct = goBinary.getGhidraDataType("runtime.g", Structure.class);
if (g0 != null && gStruct != null) {
session.markupAddressIfUndefined(g0.getAddress(), gStruct);
markupSession.markupAddressIfUndefined(g0.getAddress(), gStruct);
}
Symbol m0 = SymbolUtilities.getUniqueSymbol(program, "runtime.m0");
Structure mStruct = goBinary.getGhidraDataType("runtime.m", Structure.class);
if (m0 != null && mStruct != null) {
session.markupAddressIfUndefined(m0.getAddress(), mStruct);
markupSession.markupAddressIfUndefined(m0.getAddress(), mStruct);
}
}
private void markupGoFunctions(GoRttiMapper goBinary, MarkupSession markupSession)
throws IOException {
for (GoFuncData funcdata : goBinary.getAllFunctions()) {
String funcname = SymbolUtilities.replaceInvalidChars(funcdata.getName(), true);
markupSession.createFunctionIfMissing(funcname, funcdata.getFuncAddress());
}
try {
fixDuffFunctions(goBinary, markupSession);
}
catch (InvalidInputException | DuplicateNameException e) {
Msg.error(this, "Error configuring duff functions", e);
private void markupGoFunctions(TaskMonitor monitor) throws IOException, CancelledException {
Set<String> noreturnFuncNames = readNoReturnFuncNames();
int noreturnFuncCount = 0;
int functionSignatureFromBootstrap = 0;
int functionSignatureFromMethod = 0;
int partialFunctionSignatureFromMethod = 0;
List<GoFuncData> funcs = goBinary.getAllFunctions();
monitor.initialize(funcs.size(), "Fixing golang function signatures");
for (GoFuncData funcdata : funcs) {
monitor.increment();
Address funcAddr = funcdata.getFuncAddress();
GoSymbolName funcSymbolNameInfo = funcdata.getSymbolName();
String funcname =
SymbolUtilities.replaceInvalidChars(funcSymbolNameInfo.getSymbolName(), true);
Namespace funcns = funcSymbolNameInfo.getSymbolNamespace(goBinary.getProgram());
if ("go:buildid".equals(funcSymbolNameInfo.getSymbolName())) {
// this funcdata entry is a bogus element that points to the go buildid string. skip
continue;
}
Function func = markupSession.createFunctionIfMissing(funcname, funcns, funcAddr);
if (func == null) {
continue;
}
boolean existingFuncSignature =
func.getParameterCount() != 0 || !Undefined.isUndefined(func.getReturnType());
markupSession.appendComment(func, "Golang function info: ",
AddressAnnotatedStringHandler.createAddressAnnotationString(
funcdata.getStructureContext().getStructureAddress(),
"Flags: %s, ID: %s".formatted(funcdata.getFlags(), funcdata.getFuncIDEnum())));
if (!funcSymbolNameInfo.getSymbolName().equals(funcname)) {
markupSession.appendComment(func, "Golang original name: ",
funcSymbolNameInfo.getSymbolName());
}
GoSourceFileInfo sfi = null;
if (analyzerOptions.outputSourceInfo && (sfi = funcdata.getSourceFileInfo()) != null) {
markupSession.appendComment(func, "Golang source: ", sfi.getDescription());
}
if (funcdata.getFlags().isEmpty() /* dont try to get arg info for ASM funcs*/) {
markupSession.appendComment(func, null,
"Golang recovered signature: " + funcdata.recoverFunctionSignature());
}
// Try to get a function definition signature from:
// 1) Methods (with full info) attached to a type that point to this func
// 2) Signature found in the bootstrap gdt file (matched by name)
// 3) Artificial partial func signatures constructed from the go type method that points here
GoMethodInfo boundMethod = funcdata.findMethodInfo();
FunctionDefinition funcdef = boundMethod != null ? boundMethod.getSignature() : null;
FunctionDefinition partialFuncdef =
boundMethod != null && funcdef == null ? boundMethod.getPartialSignature() : null;
if (funcdef == null) {
funcdef = goBinary.getBootstrapFunctionDefintion(funcname);
if (funcdef != null) {
functionSignatureFromBootstrap++;
}
}
else {
functionSignatureFromMethod++;
}
if (funcdef == null && partialFuncdef != null && !existingFuncSignature) {
// use partial funcdef that only has a receiver 'this' parameter
funcdef = partialFuncdef;
partialFunctionSignatureFromMethod++;
}
if (funcdef != null) {
ApplyFunctionSignatureCmd cmd =
new ApplyFunctionSignatureCmd(funcAddr, funcdef, SourceType.ANALYSIS);
cmd.applyTo(goBinary.getProgram());
try {
GoFunctionFixup.fixupFunction(func, goBinary.getGolangVersion());
}
catch (DuplicateNameException | InvalidInputException e) {
Msg.error(this, "Failed to fix function custom storage", e);
}
}
if (noreturnFuncNames.contains(funcname)) {
if (!func.hasNoReturn()) {
func.setNoReturn(true);
noreturnFuncCount++;
}
}
if (boundMethod != null) {
String addrAnnotation = AddressAnnotatedStringHandler.createAddressAnnotationString(
boundMethod.getType().getStructureContext().getStructureAddress(),
boundMethod.getType().getName());
String methodComment = "Golang method in type %s%s: ".formatted(addrAnnotation,
partialFuncdef != null ? " [partial]" : "");
markupSession.appendComment(func, "",
methodComment + (partialFuncdef != null ? partialFuncdef : funcdef));
}
}
Msg.info(this, "Marked %d golang funcs as NoReturn".formatted(noreturnFuncCount));
Msg.info(this, "Fixed %d golang function signatures from runtime snapshot signatures"
.formatted(functionSignatureFromBootstrap));
Msg.info(this, "Fixed %d golang function signatures from method info"
.formatted(functionSignatureFromMethod));
Msg.info(this, "Fixed %d golang function signatures from partial method info"
.formatted(partialFunctionSignatureFromMethod));
}
/**
* Fixes the function signature of the runtime.duffzero and runtime.duffcopy functions.
* <p>
* The alternate duff-ified entry points haven't been discovered yet, so the information
* set to the main function entry point will be propagated at a later time to the alternate
* entry points by the GolangDuffFixupAnalyzer.
*
* @param goBinary the golang binary
* @param session {@link MarkupSession}
* @throws InvalidInputException if error assigning the function signature
* @throws DuplicateNameException if error assigning the function signature
* set to the main function entry point will be propagated at a later time by the
* FixupDuffAlternateEntryPointsBackgroundCommand.
*/
private void fixDuffFunctions(GoRttiMapper goBinary, MarkupSession session)
throws InvalidInputException, DuplicateNameException {
private void fixDuffFunctions() {
Program program = goBinary.getProgram();
GoRegisterInfo regInfo = goBinary.getRegInfo();
DataType voidPtr = program.getDataTypeManager().getPointer(VoidDataType.dataType);
DataType uintDT = goBinary.getTypeOrDefault("uint", DataType.class,
AbstractUnsignedIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null));
AbstractIntegerDataType.getUnsignedDataType(goBinary.getPtrSize(), null));
GoFuncData duffzeroFuncdata = goBinary.getFunctionByName("runtime.duffzero");
Function duffzeroFunc = duffzeroFuncdata != null
? program.getFunctionManager().getFunctionAt(duffzeroFuncdata.getFuncAddress())
: null;
PrototypeModel duffzeroCC = goBinary.getDuffzeroCallingConvention();
if (duffzeroFunc != null && duffzeroCC != null) {
// NOTE: some duffzero funcs need a zero value supplied to them via a register set
// by the caller. (depending on the arch) The duffzero calling convention defined
// by the callspec should take care of this by defining that register as the second
// storage location. Otherwise, the callspec will only have a single storage
// location defined.
boolean needZeroValueParam = regInfo.getZeroRegister() == null;
List<Variable> params = new ArrayList<>();
params.add(new ParameterImpl("dest", voidPtr, program));
if (needZeroValueParam) {
params.add(new ParameterImpl("zeroValue", uintDT, program));
if (duffzeroFunc != null &&
goBinary.hasCallingConvention(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME)) {
try {
// NOTE: some duffzero funcs need a zero value supplied to them via a register set
// by the caller. (depending on the arch) The duffzero calling convention defined
// by the callspec should take care of this by defining that register as the second
// storage location. Otherwise, the callspec will only have a single storage
// location defined.
boolean needZeroValueParam = regInfo.getZeroRegister() == null;
List<Variable> params = new ArrayList<>();
params.add(new ParameterImpl("dest", voidPtr, program));
if (needZeroValueParam) {
params.add(new ParameterImpl("zeroValue", uintDT, program));
}
duffzeroFunc.updateFunction(GOLANG_DUFFZERO_CALLINGCONVENTION_NAME,
new ReturnParameterImpl(VoidDataType.dataType, program), params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
markupSession.appendComment(duffzeroFunc, null,
"Golang special function: duffzero");
aam.schedule(new FixupDuffAlternateEntryPointsBackgroundCommand(duffzeroFuncdata,
duffzeroFunc), AnalysisPriority.FUNCTION_ANALYSIS.after().priority());
}
catch (InvalidInputException | DuplicateNameException e) {
Msg.error(this, "Failed to update main duffzero function", e);
}
duffzeroFunc.updateFunction(duffzeroCC.getName(),
new ReturnParameterImpl(VoidDataType.dataType, program), params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true,
SourceType.ANALYSIS);
GoFuncData duffcopyFuncdata = goBinary.getFunctionByName("runtime.duffcopy");
Function duffcopyFunc = duffcopyFuncdata != null
? program.getFunctionManager().getFunctionAt(duffcopyFuncdata.getFuncAddress())
: null;
if (duffcopyFuncdata != null &&
goBinary.hasCallingConvention(GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME)) {
try {
List<Variable> params = List.of(new ParameterImpl("dest", voidPtr, program),
new ParameterImpl("src", voidPtr, program));
duffcopyFunc.updateFunction(GOLANG_DUFFCOPY_CALLINGCONVENTION_NAME,
new ReturnParameterImpl(VoidDataType.dataType, program), params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
DWARFUtil.appendComment(program, duffzeroFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT,
"Golang special function: ", "duffzero", "\n");
}
markupSession.appendComment(duffcopyFunc, null,
"Golang special function: duffcopy");
GoFuncData duffcopyFuncdata = goBinary.getFunctionByName("runtime.duffcopy");
Function duffcopyFunc = duffcopyFuncdata != null
? program.getFunctionManager().getFunctionAt(duffcopyFuncdata.getFuncAddress())
: null;
PrototypeModel duffcopyCC = goBinary.getDuffcopyCallingConvention();
if (duffcopyFuncdata != null && duffcopyCC != null) {
List<Variable> params = List.of(
new ParameterImpl("dest", voidPtr, program),
new ParameterImpl("src", voidPtr, program));
duffcopyFunc.updateFunction(duffcopyCC.getName(),
new ReturnParameterImpl(VoidDataType.dataType, program), params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
DWARFUtil.appendComment(program, duffcopyFunc.getEntryPoint(), CodeUnit.PLATE_COMMENT,
"Golang special function: ", "duffcopy", "\n");
}
}
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;
aam.schedule(
new FixupDuffAlternateEntryPointsBackgroundCommand(duffcopyFuncdata,
duffcopyFunc),
AnalysisPriority.FUNCTION_ANALYSIS.after().priority());
}
catch (InvalidInputException | DuplicateNameException e) {
Msg.error(this, "Failed to update main duffcopy function", e);
}
}
wrappedPeBuildId.item().markupProgram(program, wrappedPeBuildId.address());
}
}
private void fixupNoReturnFuncs(Program program) {
private Set<String> readNoReturnFuncNames() {
Set<String> noreturnFuncnames = new HashSet<>();
Program program = goBinary.getProgram();
try {
for (ResourceFile file : NonReturningFunctionNames.findDataFiles(program)) {
FileUtilities.getLines(file)
@ -272,31 +396,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
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));
return noreturnFuncnames;
}
private Address createFakeContextMemory(Program program, long len) {
@ -310,8 +410,7 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
return newMB.getStart();
}
private void setupProgramContext(GoRttiMapper goBinary, MarkupSession session)
throws IOException {
private void setupProgramContext() throws IOException {
Program program = goBinary.getProgram();
GoRegisterInfo goRegInfo = goBinary.getRegInfo();
@ -349,19 +448,18 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
? NumericUtilities.getUnsignedAlignedValue(mStruct.getLength(), alignment)
: 0;
Address contextMemoryAddr = sizeNeeded > 0
? createFakeContextMemory(program, sizeNeeded)
: null;
Address contextMemoryAddr =
sizeNeeded > 0 ? createFakeContextMemory(program, sizeNeeded) : null;
if (zerobase == null) {
session.labelAddress(contextMemoryAddr.add(zerobaseSymbol),
ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME);
markupSession.labelAddress(contextMemoryAddr.add(zerobaseSymbol),
GoRttiMapper.ARTIFICIAL_RUNTIME_ZEROBASE_SYMBOLNAME);
}
if (gStruct != null) {
Address gAddr = contextMemoryAddr.add(gStructOffset);
session.markupAddressIfUndefined(gAddr, gStruct);
session.labelAddress(gAddr, "CURRENT_G");
markupSession.markupAddressIfUndefined(gAddr, gStruct);
markupSession.labelAddress(gAddr, "CURRENT_G");
Register currentGoroutineReg = goRegInfo.getCurrentGoroutineRegister();
if (currentGoroutineReg != null && txtMemblock != null) {
@ -380,66 +478,290 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
}
if (mStruct != null) {
Address mAddr = contextMemoryAddr.add(mStructOffset);
session.markupAddressIfUndefined(mAddr, mStruct);
markupSession.markupAddressIfUndefined(mAddr, mStruct);
}
}
private void createBootstrapGDT(GoRttiMapper goBinary, Program program,
TaskMonitor monitor) throws IOException {
private void createBootstrapGDT(TaskMonitor monitor) throws IOException, CancelledException {
Program program = goBinary.getProgram();
GoVer goVer = goBinary.getGolangVersion();
String osName = GoRttiMapper.getGolangOSString(program);
String gdtFilename =
GoRttiMapper.getGDTFilename(goVer, goBinary.getPtrSize(), osName);
gdtFilename =
gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis()));
String gdtFilename = GoRttiMapper.getGDTFilename(goVer, goBinary.getPtrSize(), osName);
gdtFilename = gdtFilename.replace(".gdt", "_%d.gdt".formatted(System.currentTimeMillis()));
File gdt = new File(System.getProperty("user.home"), gdtFilename);
goBinary.exportTypesToGDT(gdt, monitor);
goBinary.exportTypesToGDT(gdt, analyzerOptions.createRuntimeSnapshotDatatypeArchive,
monitor);
Msg.info(this, "Golang bootstrap GDT created: " + gdt);
}
@Override
public void analysisEnded(Program program) {
}
@Override
public boolean canAnalyze(Program program) {
return GoConstants.GOLANG_CSPEC_NAME.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 {@link Program}
* @return {@link Address} of the runtime.zerobase, or artificial substitute
* A background command that runs later, it copies the function signature information from the
* main entry point of the duff function to any unnamed functions that are within the footprint
* of the main function.
*/
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);
private static class FixupDuffAlternateEntryPointsBackgroundCommand extends BackgroundCommand {
private Function duffFunc;
private GoFuncData funcData;
public FixupDuffAlternateEntryPointsBackgroundCommand(GoFuncData funcData,
Function duffFunc) {
this.funcData = funcData;
this.duffFunc = duffFunc;
}
return zerobaseAddr;
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
String ccName = duffFunc.getCallingConventionName();
Namespace funcNS = duffFunc.getParentNamespace();
AddressSet funcBody = new AddressSet(funcData.getBody());
Program program = duffFunc.getProgram();
String duffComment = program.getListing()
.getCodeUnitAt(duffFunc.getEntryPoint())
.getComment(CodeUnit.PLATE_COMMENT);
monitor.setMessage("Fixing alternate duffzero/duffcopy entry points");
for (FunctionIterator funcIt =
program.getFunctionManager().getFunctions(funcBody, true); funcIt.hasNext();) {
Function func = funcIt.next();
if (!FunctionUtility.isDefaultFunctionName(func)) {
continue;
}
try {
func.setName(duffFunc.getName() + "_" + func.getEntryPoint(),
SourceType.ANALYSIS);
func.setParentNamespace(funcNS);
func.updateFunction(ccName, duffFunc.getReturn(),
Arrays.asList(duffFunc.getParameters()),
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
if (duffComment != null && !duffComment.isBlank()) {
new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment)
.applyTo(program);
}
}
catch (DuplicateNameException | InvalidInputException
| CircularDependencyException e) {
Msg.error(GolangSymbolAnalyzer.class, "Error updating duff functions", e);
}
}
return true;
}
}
//--------------------------------------------------------------------------------------------
/**
* A background command that runs after reference analysis, it applies functions signature
* overrides to callsites that have a RTTI type parameter that return a specialized
* type instead of a void*.
*/
private static class PropagateRttiBackgroundCommand extends BackgroundCommand {
record RttiFuncInfo(GoSymbolName funcName, int rttiParamIndex,
java.util.function.Function<GoType, DataType> returnTypeMapper) {
public RttiFuncInfo(String funcName, int rttiParamIndex,
java.util.function.Function<GoType, DataType> returnTypeMapper) {
this(GoSymbolName.parse(funcName), rttiParamIndex, returnTypeMapper);
}
}
record CallSiteInfo(Reference ref, Function callingFunc, Function calledFunc,
Register register,
java.util.function.Function<GoType, DataType> returnTypeMapper) {}
private GoRttiMapper goBinary;
private MarkupSession markupSession;
int totalCallsiteCount;
int fixedCallsiteCount;
int unfixedCallsiteCount;
int callingFunctionCount;
public PropagateRttiBackgroundCommand(GoRttiMapper goBinary) {
this.goBinary = goBinary;
}
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
if (goBinary.newStorageAllocator().isAbi0Mode()) {
// If abi0 mode, don't even bother because currently only handles rtti passed via
// register.
return true;
}
try {
this.markupSession = goBinary.createMarkupSession(monitor);
Set<Entry<Function, List<CallSiteInfo>>> callsiteInfo =
getInformationAboutCallsites(monitor);
monitor.initialize(totalCallsiteCount, "Propagating RTTI from callsites");
for (Entry<Function, List<CallSiteInfo>> callsite : callsiteInfo) {
monitor.checkCancelled();
fixupRttiCallsitesInFunc(callsite.getKey(), callsite.getValue(), monitor);
}
Msg.info(this, "Golang RTTI callsite fixup info (total/updated/skipped): %d/%d/%d"
.formatted(totalCallsiteCount, fixedCallsiteCount, unfixedCallsiteCount));
return true;
}
catch (CancelledException e) {
return false;
}
}
private void fixupRttiCallsitesInFunc(Function callingFunc, List<CallSiteInfo> callsites,
TaskMonitor monitor) throws CancelledException {
Program program = goBinary.getProgram();
monitor.setMessage("Propagating RTTI from callsites in %s@%s"
.formatted(callingFunc.getName(), callingFunc.getEntryPoint()));
ContextEvaluator eval = new ConstantPropagationContextEvaluator(monitor, true);
SymbolicPropogator symEval = new SymbolicPropogator(program);
symEval.flowConstants(callingFunc.getEntryPoint(), callingFunc.getBody(), eval, true,
monitor);
monitor.setMessage("Propagating RTTI from callsites in %s@%s"
.formatted(callingFunc.getName(), callingFunc.getEntryPoint()));
for (CallSiteInfo callsite : callsites) {
monitor.increment();
Value val =
symEval.getRegisterValue(callsite.ref.getFromAddress(), callsite.register);
if (val == null || val.isRegisterRelativeValue()) {
//Msg.warn(this, "Failed to get RTTI param reg value: " + callsite);
unfixedCallsiteCount++;
continue;
}
long goTypeOffset = val.getValue();
try {
GoType goType = goBinary.getCachedGoType(goTypeOffset);
if (goType == null) {
// if it was previously not discovered (usually closure anon types), also mark it up
goType = goBinary.getGoType(goTypeOffset);
markupSession.markup(goType, false);
}
DataType newReturnType =
goType != null ? callsite.returnTypeMapper.apply(goType) : null;
if (newReturnType != null) {
// Create a funcdef for this call site, where the return value is a
// specific glang type instead of the void* it was before.
FunctionDefinitionDataType signature =
new FunctionDefinitionDataType(callsite.calledFunc, true);
signature.setReturnType(newReturnType);
try {
HighFunctionDBUtil.writeOverride(callsite.callingFunc,
callsite.ref.getFromAddress(), signature);
}
catch (InvalidInputException e) {
Msg.error(this, "Failed to override call", e);
}
fixedCallsiteCount++;
}
}
catch (IOException e) {
Msg.error(this, "Failed to override call", e);
}
}
}
Set<Entry<Function, List<CallSiteInfo>>> getInformationAboutCallsites(TaskMonitor monitor) {
TaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor);
upwtm.initialize(1, "Finding callsites with RTTI");
BiConsumer<RttiFuncInfo, Consumer<CallSiteInfo>> getReferencesToRttiFuncWithMonitor =
(rfi, c) -> getReferencesToRttiFunc(rfi, c, upwtm);
//@formatter:off
Map<Function, List<CallSiteInfo>> result = List.of(
new RttiFuncInfo("runtime.newobject", 0, this::getReturnTypeForNewObjectFunc),
new RttiFuncInfo("runtime.makeslice", 0, this::getReturnTypeForSliceFunc),
new RttiFuncInfo("runtime.growslice", 4, this::getReturnTypeForSliceFunc), // won't work unless func signature for growslice is applied, which is a future "todo"
new RttiFuncInfo("runtime.makeslicecopy", 0, this::getReturnTypeForSliceFunc))
.stream()
.mapMulti(getReferencesToRttiFuncWithMonitor)
.collect(groupingBy(csi -> csi.callingFunc));
//@formatter:on
callingFunctionCount = result.size();
return result.entrySet();
}
private void getReferencesToRttiFunc(RttiFuncInfo rfi, Consumer<CallSiteInfo> consumer,
TaskMonitor monitor) {
Program program = goBinary.getProgram();
FunctionManager funcMgr = program.getFunctionManager();
ReferenceManager refMgr = program.getReferenceManager();
Function func = rfi.funcName.getFunction(program);
if (func != null) {
// TODO: improve this to handle stack values. Currently only supports values in
// registers.
List<Register> callRegs = getRegistersForParameter(func, rfi.rttiParamIndex);
if (callRegs == null || callRegs.size() != 1) {
return;
}
Register paramReg = callRegs.get(0);
stream(refMgr.getReferencesTo(func.getEntryPoint()).spliterator(), false) //
.filter(ref -> !monitor.isCancelled() && ref != null &&
ref.getReferenceType().isCall())
.map(ref -> new CallSiteInfo(ref,
funcMgr.getFunctionContaining(ref.getFromAddress()), func, paramReg,
rfi.returnTypeMapper))
.forEach(consumer.andThen(_unused -> {
monitor.incrementProgress();
totalCallsiteCount++;
}));
}
}
private DataType getReturnTypeForNewObjectFunc(GoType goType) {
try {
DataTypeManager dtm = goBinary.getDTM();
DataType dt = goBinary.getRecoveredType(goType);
return dtm.getPointer(dt);
}
catch (IOException e) {
return null;
}
}
private DataType getReturnTypeForSliceFunc(GoType goType) {
try {
GoType sliceGoType = goBinary.findGoType("[]" + goType.getNameWithPackageString());
DataType dt = sliceGoType != null ? goBinary.getRecoveredType(sliceGoType) : null;
return dt;
}
catch (IOException e) {
return null;
}
}
private List<Register> getRegistersForParameter(Function func, int paramIndex) {
GoParamStorageAllocator storageAllocator = goBinary.newStorageAllocator();
Parameter[] params = func.getParameters();
if (params.length == 0 && paramIndex == 0) {
// TODO: this is a hack to handle lack of func param info for built-in runtime alloc methods
// This will not be needed once param info for the alloc methods is applied before
// we get to this step.
// This only works with the rtti funcs that pass the gotype ref in first param
return storageAllocator.getRegistersFor(goBinary.getUintptrDT());
}
for (int i = 0; i < params.length; i++) {
DataType paramDT = params[i].getDataType();
List<Register> regs = storageAllocator.getRegistersFor(paramDT);
if (i == paramIndex) {
return regs;
}
}
return List.of();
}
}
//--------------------------------------------------------------------------------------------
private static class GolangAnalyzerOptions {
static final String CREATE_BOOTSTRAP_GDT_OPTIONNAME = "Create Bootstrap GDT";
static final String CREATE_BOOTSTRAP_GDT_DESC = """
@ -450,5 +772,37 @@ public class GolangSymbolAnalyzer extends AbstractAnalyzer {
be called golang_MajorVer.MinorVer_XXbit_osname.NNNNNNNNN.gdt, where NNNNNN \
is a timestamp.""";
boolean createBootstrapDatatypeArchive;
boolean createRuntimeSnapshotDatatypeArchive;
static final String OUTPUT_SOURCE_INFO_OPTIONNAME = "Output Source Info";
static final String OUTPUT_SOURCE_INFO_DESC = """
Add "source_file_name:line_number" information to functions.""";
boolean outputSourceInfo = true;
static final String FIXUP_DUFF_FUNCS_OPTIONNAME = "Fixup Duff Functions";
static final String FIXUP_DUFF_FUNCS_DESC = """
Copies information from the runtime.duffzero and runtime.duffcopy functions to \
the alternate duff entry points that are discovered during later analysis.""";
boolean fixupDuffFunctions = true;
static final String PROP_RTTI_OPTIONNAME = "Propagate RTTI";
static final String PROP_RTTI_DESC = """
Override the function signature of calls to some built-in Golang allocator \
functions (runtime.newobject(), runtime.makeslice(), etc) that have a constant \
reference to a Golang type record to have a return type of that specific Golang \
type.""";
boolean propagateRtti = true;
}
/**
* Returns true if DWARF has already been imported into the specified program.
*
* @param program {@link Program} to check
* @return true if DWARF has already been imported, false if not yet
*/
public static boolean isAlreadyImported(Program program) {
Options options = program.getOptions(Program.PROGRAM_INFO);
return options.getBoolean(ANALYZED_FLAG_OPTION_NAME, false);
}
}

View file

@ -0,0 +1,193 @@
/* ###
* 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.Closeable;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Supplier;
import ghidra.formats.gfilesystem.FSUtilities;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.program.model.listing.Program;
/**
* Mechanism to associate values with a currently open program. Values will be released when
* the program is closed, or when the current analysis session is finished.
* <p>
* Values that are linked to things in a Program that are subject to be reverted during a
* transaction roll-back should probably not be stored in a PROGRAM scoped property. (example:
* DataTypes, CodeUnits, etc) ANALYSIS_SESSION scoped properties are protected from rollback
* by the active transaction that is held during the session.
*
*/
public class TransientProgramProperties {
private static final Map<Program, PerProgramProperties> properties = new HashMap<>();
public enum SCOPE {
PROGRAM, // value will be released when the program is closed
ANALYSIS_SESSION // value will be released when the current analysis session is finished
}
/**
* A checked {@link Supplier}
*
* @param <T> type of result
* @param <E> type of exception thrown
*/
public interface PropertyValueSupplier<T, E extends Throwable> {
T get() throws E;
}
/**
* Returns true if the specified property is present.
*
* @param program {@link Program}
* @param key property key
* @return boolean true if property is present.
*/
public static synchronized boolean hasProperty(Program program, Object key) {
PerProgramProperties perProgramProps = properties.get(program);
return perProgramProps != null ? perProgramProps.props.containsKey(key) : false;
}
/**
* Returns a property value that has been associated with the specified program.
* <p>
* If the property wasn't present, the {@link PropertyValueSupplier} will be used to
* create the value and associate it with the program.
*
* @param <T> type of the property value. If the property value is {@link Closeable}, it
* will be {@link Closeable#close() closed} when released.
* @param <E> type of the exception the supplier throws
* @param program {@link Program}
* @param key property key
* @param scope {@link SCOPE} lifetime of property. If an analysis session is NOT active,
* requesting {@link SCOPE#ANALYSIS_SESSION} will throw an IllegalArgumentException. If the
* requested scope does not match the scope of the already existing value, an
* IllegalArgumentException will be thrown.
* @param clazz type of the property value
* @param supplier {@link PropertyValueSupplier} callback that will create the property
* value if it is not present
* @return property value
* @throws IllegalArgumentException if scope == ANALYSIS_SESSION and there is no active analysis
* session, OR, if the requested scope does not match the scope used to an earlier call for
* the same property
* @throws E same exception type that the supplier throws
*/
public static synchronized <T, E extends Throwable> T getProperty(Program program, Object key,
SCOPE scope, Class<? extends T> clazz, PropertyValueSupplier<T, E> supplier) throws E {
if (scope == SCOPE.ANALYSIS_SESSION &&
(!AutoAnalysisManager.hasAutoAnalysisManager(program) ||
!AutoAnalysisManager.getAnalysisManager(program).isAnalyzing())) {
throw new IllegalArgumentException("No active analysis session");
}
PerProgramProperties perProgramProps =
properties.computeIfAbsent(program, PerProgramProperties::new);
Property property = perProgramProps.props.get(key);
if (property == null) {
T supplierVal = supplier.get();
if (supplierVal == null) {
return null;
}
property = new Property(key, supplierVal, scope);
perProgramProps.addProperty(key, supplierVal, scope);
}
if (property.scope != scope) {
throw new IllegalArgumentException("Mismatched Program property scope");
}
return clazz.isInstance(property.value) ? clazz.cast(property.value) : null;
}
/**
* Release all properties for the specified program.
*
* @param program {@link Program}
*/
public static synchronized void removeProgramProperties(Program program) {
PerProgramProperties removedProps = properties.remove(program);
if (removedProps != null) {
removedProps.close();
}
}
//---------------------------------------------------------------------------------------------
private record Property(Object key, Object value, SCOPE scope) implements Closeable {
@Override
public void close() {
if (value instanceof Closeable c) {
FSUtilities.uncheckedClose(c, null);
}
}
}
private static class PerProgramProperties
implements DomainObjectClosedListener, AutoAnalysisManagerListener, Closeable {
final Program program;
final Map<Object, Property> props;
boolean aamListenerAdded;
PerProgramProperties(Program program) {
this.program = program;
this.props = new HashMap<>();
program.addCloseListener(this);
}
@Override
public void domainObjectClosed(DomainObject dobj) {
removeProgramProperties(program);
}
@Override
public void analysisEnded(AutoAnalysisManager manager, boolean isCancelled) {
for (Iterator<Entry<Object, Property>> it = props.entrySet().iterator(); it
.hasNext();) {
Entry<Object, Property> entry = it.next();
Property prop = entry.getValue();
if (prop.scope == SCOPE.ANALYSIS_SESSION) {
it.remove();
prop.close();
}
}
}
@Override
public void close() {
props.forEach((key, prop) -> prop.close());
props.clear();
program.removeCloseListener(this);
if (aamListenerAdded) {
AutoAnalysisManager.getAnalysisManager(program).removeListener(this);
}
}
private void addProperty(Object key, Object value, SCOPE scope) {
if (scope == SCOPE.ANALYSIS_SESSION) {
if (!aamListenerAdded) {
AutoAnalysisManager.getAnalysisManager(program).addListener(this);
aamListenerAdded = true;
}
}
props.put(key, new Property(key, value, scope));
}
}
}

View file

@ -60,9 +60,9 @@ 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, main.main, start, entry";
"main.main, main, WinMain, libc_start_main, WinMainStartup, start, entry";
public static enum StartLocationType {
public enum StartLocationType {
LOWEST_ADDRESS("Lowest Address"),
LOWEST_CODE_BLOCK("Lowest Code Block Address"),
SYMBOL_NAME("Preferred Symbol Name"),
@ -70,7 +70,7 @@ public class ProgramStartingLocationOptions implements OptionsChangeListener {
private String label;
private StartLocationType(String label) {
StartLocationType(String label) {
this.label = label;
}

View file

@ -644,8 +644,10 @@ public class BinaryReader {
* Reads an integer array of <code>nElements</code>
* starting at the current index and then increments the current
* index by <code>SIZEOF_INT * nElements</code>.
*
* @param nElements number of elements to read
* @return the integer array starting at the current index
* @exception IOException if an I/O error occurs
* @throws IOException if an I/O error occurs
*/
public int[] readNextIntArray(int nElements) throws IOException {
int[] i = readIntArray(currentIndex, nElements);
@ -657,8 +659,10 @@ public class BinaryReader {
* Reads a long array of <code>nElements</code>
* starting at the current index and then increments the current
* index by <code>SIZEOF_LONG * nElements</code>.
*
* @param nElements number of elements to read
* @return the long array starting at the current index
* @exception IOException if an I/O error occurs
* @throws IOException if an I/O error occurs
*/
public long[] readNextLongArray(int nElements) throws IOException {
long[] l = readLongArray(currentIndex, nElements);

View file

@ -28,6 +28,7 @@ import ghidra.app.util.bin.format.dwarf4.encoding.*;
import ghidra.app.util.bin.format.dwarf4.expression.*;
import ghidra.app.util.bin.format.dwarf4.next.DWARFProgram;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
/**
* DIEAggregate groups related {@link DebugInfoEntry} records together in a single interface
@ -724,7 +725,8 @@ public class DIEAggregate {
}
// Check to see if this is a base address entry
if (beginning == -1) {
if (beginning == -1 ||
(pointerSize == 4 && beginning == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG)) {
baseAddressOffset = ending;
continue;
}
@ -913,7 +915,8 @@ public class DIEAggregate {
}
// Check to see if this is a base address entry
if (beginning == -1) {
if (beginning == -1 ||
(pointerSize == 4 && beginning == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG)) {
baseAddress = ending;
continue;
}

View file

@ -15,11 +15,11 @@
*/
package ghidra.app.util.bin.format.dwarf4.funcfixup;
import java.io.Closeable;
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;
@ -32,6 +32,14 @@ import ghidra.util.classfinder.ExtensionPoint;
* <p>
* Fixups are found using {@link ClassSearcher}, and their class names must end
* in "DWARFFunctionFixup" (see ExtensionPoint.manifest).
* <p>
* Instance lifetime:
* <p>
* New instances are not shared between programs or analysis sessions, but will be re-used to
* handle the various functions found in a single binary.
* <p>
* If the implementation also implements {@link Closeable}, it will be called when the fixup
* is no longer needed.
*/
public interface DWARFFunctionFixup extends ExtensionPoint {
public static final int PRIORITY_NORMAL_EARLY = 4000;
@ -46,9 +54,8 @@ public interface DWARFFunctionFixup extends ExtensionPoint {
* 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;
void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException;
/**
* Return a list of all current {@link DWARFFunctionFixup fixups} found in the classpath

View file

@ -17,7 +17,6 @@ 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;
@ -29,13 +28,13 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class OutputParamCheckDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
public void fixupDWARFFunction(DWARFFunction dfunc) {
// 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()));
Msg.warn(this, "Unsupported output parameter for %s@%s"
.formatted(dfunc.name.getName(), dfunc.address));
}
}
}

View file

@ -16,7 +16,6 @@
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;
/**
@ -26,13 +25,13 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class ParamNameDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
public void fixupDWARFFunction(DWARFFunction dfunc) {
// 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));
nameDeduper.addUsedNames(dfunc.getNonParamSymbolNames());
for (DWARFVariable param : dfunc.params) {
String newName = nameDeduper.getUniqueName(param.name.getName());

View file

@ -17,7 +17,6 @@ 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;
/**
@ -31,7 +30,7 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class ParamSpillDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
public void fixupDWARFFunction(DWARFFunction dfunc) {
for (DWARFVariable param : dfunc.params) {
if (!param.isStackStorage()) {
continue;

View file

@ -19,7 +19,6 @@ 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;
/**
@ -29,7 +28,7 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class RustDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException {
public void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException {
DIEAggregate diea = dfunc.diea;
int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage();
if (cuLang == DWARFSourceLanguage.DW_LANG_Rust && dfunc.params.isEmpty()) {

View file

@ -18,7 +18,6 @@ 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;
@ -29,15 +28,13 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class SanityCheckDWARFFunctionFixup implements DWARFFunctionFixup, DWARFAttributeValue {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) throws DWARFException {
public void fixupDWARFFunction(DWARFFunction dfunc) 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()));
Msg.error(this, "Inconsistent function signature information, leaving undefined: %s@%s"
.formatted(dfunc.name.getName(), dfunc.address));
throw new DWARFException("Failed sanity check");
}
}

View file

@ -18,7 +18,6 @@ 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;
/**
@ -32,7 +31,7 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class StorageVerificationDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
public void fixupDWARFFunction(DWARFFunction dfunc) {
boolean storageIsGood = true;
for (DWARFVariable param : dfunc.params) {
if (param.isMissingStorage() && !param.isZeroByte()) {

View file

@ -17,7 +17,7 @@ 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.lang.CompilerSpec;
import ghidra.program.model.listing.Function;
import ghidra.util.Msg;
import ghidra.util.classfinder.ExtensionPointProperties;
@ -29,8 +29,8 @@ import ghidra.util.classfinder.ExtensionPointProperties;
public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFixup {
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
if (dfunc.params.isEmpty() || dfunc.callingConvention != null) {
public void fixupDWARFFunction(DWARFFunction dfunc) {
if (dfunc.params.isEmpty() || dfunc.callingConventionName != null) {
// if someone else set calling convention, don't override it
return;
}
@ -39,14 +39,13 @@ public class ThisCallingConventionDWARFFunctionFixup implements DWARFFunctionFix
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()));
Msg.warn(this, "Renaming %s to %s in function %s@%s".formatted(
firstParam.name.getName(), Function.THIS_PARAM_NAME, dfunc.name.getName(),
dfunc.address));
}
firstParam.name =
firstParam.name.replaceName(Function.THIS_PARAM_NAME, Function.THIS_PARAM_NAME);
dfunc.callingConvention = GenericCallingConvention.thiscall;
dfunc.callingConventionName = CompilerSpec.CALLING_CONVENTION_thiscall;
}
}

View file

@ -26,12 +26,18 @@ import ghidra.program.model.listing.*;
public class DWARFDataInstanceHelper {
private Program program;
private Listing listing;
private boolean allowTruncating = true;
public DWARFDataInstanceHelper(Program program) {
this.program = program;
this.listing = program.getListing();
}
public DWARFDataInstanceHelper setAllowTruncating(boolean b) {
this.allowTruncating = b;
return this;
}
private boolean isArrayDataTypeCompatibleWithExistingData(Array arrayDT, Data existingData) {
DataType existingDataDT = existingData.getBaseDataType();
@ -60,10 +66,13 @@ public class DWARFDataInstanceHelper {
return false;
}
if (arrayDT.getLength() <= existingData.getLength()) {
// if proposed array is smaller than in-memory array: ok
if (arrayDT.getLength() == existingData.getLength()) {
return true;
}
if (arrayDT.getLength() < existingData.getLength()) {
// if proposed array is smaller than in-memory array
return allowTruncating;
}
// if proposed array is longer than in-memory array, check if there is only
// undefined data following the in-memory array
@ -105,7 +114,8 @@ public class DWARFDataInstanceHelper {
return false;
}
}
return true;
boolean isTruncating = structDT.getLength() < existingData.getLength();
return !isTruncating || allowTruncating;
}
private boolean isPointerDataTypeCompatibleWithExistingData(Pointer pdt, Data existingData) {
@ -119,22 +129,27 @@ public class DWARFDataInstanceHelper {
private boolean isSimpleDataTypeCompatibleWithExistingData(DataType simpleDT,
Data existingData) {
// dataType will only be a base data type, not a typedef
// simpleDT will be int, char, float, or string data types
boolean isSameLen = isSameLen(simpleDT, existingData);
DataType existingDT = existingData.getBaseDataType();
if (simpleDT instanceof CharDataType && existingDT instanceof StringDataType) {
// char overwriting a string
return isSameLen || allowTruncating;
}
if (Undefined.isUndefined(existingDT) && isSameLen) {
// some type overwriting an undefined
return true;
}
if (!simpleDT.getClass().isInstance(existingDT)) {
return false;
}
int dataTypeLen = simpleDT.getLength();
if (dataTypeLen > 0 && dataTypeLen != existingData.getLength()) {
return false;
}
return true;
return simpleDT.getClass().isInstance(existingDT) && isSameLen;
}
private boolean isSameLen(DataType dt, Data existingData) {
return existingData.getLength() == dt.getLength() ||
(dt instanceof Dynamic dyDT && dyDT.canSpecifyLength());
}
private boolean isEnumDataTypeCompatibleWithExistingData(Enum enumDT, Data existingData) {
@ -192,8 +207,14 @@ public class DWARFDataInstanceHelper {
return true;
}
Data data = listing.getDataAt(address);
return data == null || isDataTypeCompatibleWithExistingData(dataType, data);
Data data = listing.getDataContaining(address);
if (data == null) {
return false; // will only get null if something is really screwed up
}
if (!data.getMinAddress().equals(address)) {
return false; // was pointing to something in the middle of an existing data instance
}
return isDataTypeCompatibleWithExistingData(dataType, data);
}
}

View file

@ -28,6 +28,7 @@ import ghidra.app.util.DataTypeNamingUtil;
import ghidra.app.util.bin.format.dwarf4.*;
import ghidra.app.util.bin.format.dwarf4.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFEndianity;
import ghidra.app.util.bin.format.dwarf4.encoding.DWARFSourceLanguage;
import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.golang.rtti.types.GoKind;
import ghidra.program.database.DatabaseObject;
@ -197,10 +198,7 @@ public class DWARFDataTypeImporter {
break;
case DW_TAG_subroutine_type:
result = makeDataTypeForFunctionDefinition(diea, true);
break;
case DW_TAG_subprogram:
result = makeDataTypeForFunctionDefinition(diea, false);
result = makeDataTypeForFunctionDefinition(diea);
break;
default:
Msg.warn(this, "Unsupported datatype in die: " + diea.toString());
@ -265,19 +263,36 @@ public class DWARFDataTypeImporter {
/*
* Returns a Ghidra {@link FunctionDefinition} datatype built using the info from the DWARF die.
* <p>
* Function types may be assigned "mangled" names with parameter type info if the name
* is already used or if this is an unnamed function defintion.
* <p>
* Can accept DW_TAG_subprogram, DW_TAG_subroutine_type DIEAs.
* <p>
* The logic of this impl is the same as {@link DWARFDataTypeManager#createFunctionDefinitionDataType(DIEAggregate)}
* but the impls can't be shared without excessive over-engineering.
* Function types may be assigned "mangled" names with parameter type info if this is an
* unnamed function definition.
*/
private DWARFDataType makeDataTypeForFunctionDefinition(DIEAggregate diea,
boolean mangleAnonFuncNames) throws IOException, DWARFExpressionException {
private DWARFDataType makeDataTypeForFunctionDefinition(DIEAggregate diea)
throws IOException, DWARFExpressionException {
DWARFNameInfo dni = prog.getName(diea);
// push an empty funcdef data type into the lookup cache to prevent recursive loops
FunctionDefinitionDataType funcDef =
new FunctionDefinitionDataType(dni.getParentCP(), dni.getName(), dataTypeManager);
DataType dtToAdd = funcDef;
int cuLang = diea.getCompilationUnit().getCompileUnit().getLanguage();
if (cuLang == DWARFSourceLanguage.DW_LANG_Go) {
if (diea.hasAttribute(DW_AT_byte_size)) {
// if the funcdef has a bytesize attribute, we should convert this data type to a ptr
long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1);
if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) {
ptrSize = -1;// use default pointer size
}
dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize);
}
// golang funcdefs are ptr->ptr->funcs
dtToAdd = dwarfDTM.getPtrTo(dtToAdd, -1);
}
DWARFDataType result = new DWARFDataType(dtToAdd, dni, diea.getOffset());
recordTempDataType(result); // prevents recursive type-lookups for this funcdef
DWARFDataType returnType = getDataType(diea.getTypeRef(), voidDDT);
boolean foundThisParam = false;
@ -288,15 +303,6 @@ public class DWARFDataTypeImporter {
DWARFDataType paramDT = getDataType(paramDIEA.getTypeRef(), null);
DataType dt = fixupDataTypeInconsistencies(paramDT);
if (dt == null && DWARFUtil.isPointerDataType(paramDIEA.getTypeRef())) {
// Hack to handle Golang self-referencing func defs.
Msg.error(this,
"Error resolving parameter data type, probable recursive definition, replacing with void*: " +
dni.getName());
Msg.debug(this, "Problem funcDef: " + diea.toString());
Msg.debug(this, "Problem param: " + paramDIEA);
dt = dwarfDTM.getPtrTo(dwarfDTM.getVoidType());
}
if (dt == null || dt.getLength() <= 0) {
Msg.error(this, "Bad function parameter type for " + dni.asCategoryPath());
return null;
@ -308,8 +314,6 @@ public class DWARFDataTypeImporter {
foundThisParam |= DWARFUtil.isThisParam(paramDIEA);
}
FunctionDefinitionDataType funcDef =
new FunctionDefinitionDataType(dni.getParentCP(), dni.getName(), dataTypeManager);
funcDef.setReturnType(returnType.dataType);
funcDef.setNoReturn(diea.getBool(DW_AT_noreturn, false));
funcDef.setArguments(params.toArray(new ParameterDefinition[params.size()]));
@ -327,9 +331,8 @@ public class DWARFDataTypeImporter {
}
}
if (dni.isAnon() && mangleAnonFuncNames) {
String mangledName =
DataTypeNamingUtil.setMangledAnonymousFunctionName(funcDef);
if (dni.isAnon()) {
String mangledName = DataTypeNamingUtil.setMangledAnonymousFunctionName(funcDef);
dni = dni.replaceName(mangledName, dni.getOriginalName());
}
@ -339,17 +342,7 @@ public class DWARFDataTypeImporter {
updateMapping(origPD.getDataType(), newPD.getDataType());
}
DataType dtToAdd = funcDef;
if (diea.hasAttribute(DW_AT_byte_size)) {
// if the funcdef has a bytesize attribute, we should convert this data type to a ptr
long ptrSize = diea.getUnsignedLong(DW_AT_byte_size, -1);
if (ptrSize == dataTypeManager.getDataOrganization().getPointerSize()) {
ptrSize = -1;// use default pointer size
}
dtToAdd = dwarfDTM.getPtrTo(dtToAdd, (int) ptrSize);
}
return new DWARFDataType(dtToAdd, dni, diea.getOffset());
return result;
}
/**
@ -1301,11 +1294,16 @@ public class DWARFDataTypeImporter {
boolean typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict(
typedefDNI.asDataTypePath().getPath(), refdDT.dataType.getPathName());
if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT &&
ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) {
// hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare
typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict(
typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName());
if (!typedefWithSameName && refdDT.dataType instanceof Pointer ptrDT) {
if (ptrDT.getDataType() instanceof Pointer ptrptrDT) {
// handle double ptr-to-ptr-to-funcdef (golang typedefs to funcdefs)
ptrDT = ptrptrDT;
}
if (ptrDT.getDataType() instanceof FunctionDefinition pointedToFuncDefDT) {
// hack to handle funcDefs that produce a ptr_to_funcdef instead of a funcdef type, which messes with name compare
typedefWithSameName = DataTypeUtilities.equalsIgnoreConflict(
typedefDNI.asDataTypePath().getPath(), pointedToFuncDefDT.getPathName());
}
}
if (typedefWithSameName) {

View file

@ -588,10 +588,6 @@ public class DWARFDataTypeManager {
int dtCountAfter = dataTypeManager.getDataTypeCount(true);
importSummary.dataTypesAdded = (dtCountAfter - dtCountBefore);
if (prog.getImportOptions().isCreateFuncSignatures()) {
importFuncSignatures(monitor);
}
}
private DIEAggregate getFuncDIEA(DIEAggregate diea) {
@ -632,49 +628,6 @@ public class DWARFDataTypeManager {
return null;
}
private void importFuncSignatures(TaskMonitor monitor) throws CancelledException {
int dtCountBefore = dataTypeManager.getDataTypeCount(true);
for (DIEAggregate diea : DIEAMonitoredIterator.iterable(prog,
"DWARF Import Function Signatures", monitor)) {
monitor.checkCancelled();
try {
diea = getFuncDIEA(diea);
if (diea != null) {
DWARFNameInfo dni = prog.getName(diea);
DataType funcDefDT = createFunctionDefinitionDataType(diea, dni);
if (funcDefDT != null) {
// submit the temp 'impl' funcdef datatype to the DTM and get back a permanent
// db instance.
funcDefDT = dataTypeManager.addDataType(funcDefDT,
DataTypeConflictHandler.DEFAULT_HANDLER);
// Look for the source info in the funcdef die and fall back to its
// parent's source info (handles auto-generated ctors and such)
addDataType(diea.getOffset(), funcDefDT,
DWARFSourceInfo.getSourceInfoWithFallbackToParent(diea));
Swing.runNow(Dummy.runnable());
}
}
}
catch (OutOfMemoryError oom) {
throw oom;
}
catch (Throwable th) {
// Aggressively catch pretty much everything to allow the import to
// try to continue with the next compunit.
Msg.error(this,
"Error when processing DWARF information for DIE " + diea.getHexOffset(), th);
Msg.info(this, "DIE info:\n" + diea.toString());
}
}
int dtCountAfter = dataTypeManager.getDataTypeCount(true);
importSummary.funcSignaturesAdded = (dtCountAfter - dtCountBefore);
}
private boolean isDataType(DIEAggregate diea) {
switch (diea.getTag()) {
case DWARFTag.DW_TAG_base_type:
@ -704,16 +657,8 @@ public class DWARFDataTypeManager {
* Creates a new {@link FunctionDefinitionDataType} from the specified {@link DIEAggregate}
* using already known datatypes.
* <p>
* The logic of this impl is the same as {@link DWARFDataTypeImporter#makeDataTypeForFunctionDefinition(DIEAggregate, boolean)}
* but the impls can't be shared without excessive over-engineering.
* <p>
* This impl uses DataType's that have already been resolved and committed to the DTM, and
* a cache mapping entry of the DWARF die -&gt; DataType has been registered via {@link #addDataType(long, DataType, DWARFSourceInfo)}.
* <p>
* This approach is necessary because of speed issues that arise if the referred datatypes
* are created from scratch from the DWARF information and then have to go through a
* resolve() before being used in the FunctionDefinition.
*
*
* @param diea DWARF {@link DIEAggregate} that points to a subprogram or subroutine_type.
* @param dni DWARF name info for the new function def

View file

@ -23,24 +23,26 @@ import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
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.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.Msg;
import ghidra.util.exception.*;
/**
* Represents a function that was read from DWARF information.
*/
public class DWARFFunction {
public enum CommitMode {
SKIP, FORMAL, STORAGE,
}
public enum CommitMode { SKIP, FORMAL, STORAGE, }
public DIEAggregate diea;
public DWARFNameInfo name;
@ -48,9 +50,9 @@ public class DWARFFunction {
public Address address;
public Address highAddress;
public long frameBase; // TODO: change this to preserve the func's frameBase expr instead of value
public Function function; // ghidra function
public GenericCallingConvention callingConvention;
public PrototypeModel prototypeModel;
public String callingConventionName;
public DWARFVariable retval;
public List<DWARFVariable> params = new ArrayList<>();
@ -138,11 +140,7 @@ public class DWARFFunction {
}
public String getCallingConventionName() {
return prototypeModel != null
? prototypeModel.getName()
: callingConvention != null
? callingConvention.getDeclarationName()
: null;
return callingConventionName;
}
/**
@ -187,10 +185,10 @@ public class DWARFFunction {
return false;
}
public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar, Function gfunc)
public boolean hasConflictWithExistingLocalVariableStorage(DWARFVariable dvar)
throws InvalidInputException {
VariableStorage newVarStorage = dvar.getVariableStorage();
for (Variable existingVar : gfunc.getAllVariables()) {
for (Variable existingVar : function.getAllVariables()) {
if (existingVar.getFirstUseOffset() == dvar.lexicalOffset &&
existingVar.getVariableStorage().intersects(newVarStorage)) {
if ((existingVar instanceof LocalVariable) &&
@ -217,20 +215,20 @@ public class DWARFFunction {
.collect(Collectors.toList());
}
public List<String> getExistingLocalVariableNames(Function gfunc) {
return Arrays.stream(gfunc.getLocalVariables())
public List<String> getExistingLocalVariableNames() {
return Arrays.stream(function.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);
public List<String> getNonParamSymbolNames() {
SymbolIterator symbols = function.getProgram().getSymbolTable().getSymbols(function);
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.
@ -238,63 +236,45 @@ public class DWARFFunction {
* @param includeStorageDetail boolean flag, if true storage information will be included, if
* false, VariableStorage.UNASSIGNED_STORAGE will be used
* @return list of Parameters
* @throws InvalidInputException
* @throws InvalidInputException if bad information in param storage
*/
public List<Parameter> getParameters(boolean includeStorageDetail)
throws InvalidInputException {
List<Parameter> result = new ArrayList<>();
for (DWARFVariable dvar : params) {
result.add(dvar.asParameter(includeStorageDetail, getProgram().getGhidraProgram()));
result.add(dvar.asParameter(includeStorageDetail));
}
return result;
}
/**
* Returns a {@link FunctionDefinition} that reflects this function's information.
*
* @return {@link FunctionDefinition} that reflects this function's information
* Returns the parameters of this function as {@link ParameterDefinition}s.
*
* @return array of {@link ParameterDefinition}s
*/
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 ParameterDefinition[] getParameterDefinitions() {
return params.stream()
.map(dvar -> new ParameterDefinitionImpl(dvar.name.getName(), dvar.type, null))
.toArray(ParameterDefinition[]::new);
}
public void commitLocalVariable(DWARFVariable dvar, Function gfunc) {
public void commitLocalVariable(DWARFVariable dvar) {
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),
appendComment(function.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),
if (hasConflictWithExistingLocalVariableStorage(dvar)) {
appendComment(function.getEntryPoint().add(dvar.lexicalOffset),
CodeUnit.EOL_COMMENT, "Local omitted variable %s[%s] scope starts here"
.formatted(dvar.getDeclInfoString(), varStorage),
"; ");
return;
}
@ -302,7 +282,7 @@ public class DWARFFunction {
NameDeduper nameDeduper = new NameDeduper();
nameDeduper.addReservedNames(getAllLocalVariableNames());
nameDeduper.addUsedNames(getAllParamNames());
nameDeduper.addUsedNames(getExistingLocalVariableNames(gfunc));
nameDeduper.addUsedNames(getExistingLocalVariableNames());
Variable var = dvar.asLocalVariable();
String origName = var.getName();
@ -317,11 +297,11 @@ public class DWARFFunction {
var.setComment("Original name: " + origName);
}
VariableUtilities.checkVariableConflict(gfunc, var, varStorage, true);
gfunc.addLocalVariable(var, SourceType.IMPORTED);
VariableUtilities.checkVariableConflict(function, var, varStorage, true);
function.addLocalVariable(var, SourceType.IMPORTED);
}
catch (InvalidInputException | DuplicateNameException e) {
appendComment(gfunc.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT,
appendComment(function.getEntryPoint().add(dvar.lexicalOffset), CodeUnit.EOL_COMMENT,
"Local omitted variable %s[%s] scope starts here".formatted(
dvar.getDeclInfoString(),
varStorage != null ? varStorage.toString() : "UNKNOWN"),
@ -330,11 +310,19 @@ public class DWARFFunction {
}
}
@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) {
@ -362,4 +350,152 @@ public class DWARFFunction {
return null;
}
public boolean syncWithExistingGhidraFunction(boolean createIfMissing) {
try {
Program currentProgram = getProgram().getGhidraProgram();
function = currentProgram.getListing().getFunctionAt(address);
if (function != null) {
if (function.hasNoReturn() && !noReturn) {
// preserve the noReturn flag if set by earlier analyzer
noReturn = true;
}
}
if (!createIfMissing && function == null) {
return false;
}
// create a new symbol if one does not exist (symbol table will figure this out)
SymbolTable symbolTable = currentProgram.getSymbolTable();
symbolTable.createLabel(address, name.getName(), namespace, SourceType.IMPORTED);
// force new label to become primary (if already a function it will become function name)
SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(address, name.getName(), namespace);
cmd.applyTo(currentProgram);
if (isExternal) {
currentProgram.getSymbolTable().addExternalEntryPoint(address);
}
else {
currentProgram.getSymbolTable().removeExternalEntryPoint(address);
}
function = currentProgram.getListing().getFunctionAt(address);
if (function == null) {
// TODO: If not contained within program memory should they be considered external?
if (!currentProgram.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(address)) {
Msg.warn(this,
"DWARF: unable to create function not contained within loaded memory: %s@%s"
.formatted(name, address));
return false;
}
// create 1-byte function if one does not exist - primary label will become function names
function = currentProgram.getFunctionManager()
.createFunction(null, address, new AddressSet(address),
SourceType.IMPORTED);
}
return true;
}
catch (OverlappingFunctionException e) {
throw new AssertException(e);
}
catch (InvalidInputException e) {
Msg.error(this, "Failed to create function " + namespace + "/" + name.getName() + ": " +
e.getMessage());
}
return false;
}
public void runFixups() {
// Run all the DWARFFunctionFixup instances
for (DWARFFunctionFixup fixup : getProgram().getFunctionFixups()) {
try {
fixup.fixupDWARFFunction(this);
}
catch (DWARFException e) {
signatureCommitMode = CommitMode.SKIP;
}
if (signatureCommitMode == CommitMode.SKIP) {
break;
}
}
}
public void updateFunctionSignature() {
try {
boolean includeStorageDetail = signatureCommitMode == CommitMode.STORAGE;
FunctionUpdateType functionUpdateType = includeStorageDetail
? FunctionUpdateType.CUSTOM_STORAGE
: FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS;
Parameter returnVar = retval.asReturnParameter(includeStorageDetail);
List<Parameter> parameters = getParameters(includeStorageDetail);
if (includeStorageDetail && !retval.isZeroByte() && retval.isMissingStorage()) {
// TODO: this logic is faulty and borks the auto _return_storage_ptr_ when present
// Update return value in a separate step as its storage isn't typically specified
// in dwarf info.
// This will allow automagical storage assignment for return value by ghidra.
function.updateFunction(callingConventionName, returnVar, List.of(),
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED);
returnVar = null; // don't update it in the second call to updateFunction()
}
function.updateFunction(callingConventionName, returnVar, parameters,
functionUpdateType, true, SourceType.IMPORTED);
function.setVarArgs(varArg);
function.setNoReturn(noReturn);
}
catch (InvalidInputException | DuplicateNameException e) {
Msg.error(this, "Error updating function %s@%s with params: %s".formatted(
function.getName(), function.getEntryPoint().toString(), e.getMessage()));
Msg.error(this, "DIE info: " + diea.toString());
}
}
/**
* Returns a {@link FunctionDefinition} that reflects this function's information.
*
* @param includeCC boolean flag, if true the returned funcdef will include calling convention
* @return {@link FunctionDefinition} that reflects this function's information
*/
public FunctionDefinition asFunctionDefinition(boolean includeCC) {
ProgramBasedDataTypeManager dtm = getProgram().getGhidraProgram().getDataTypeManager();
FunctionDefinitionDataType funcDef =
new FunctionDefinitionDataType(name.getParentCP(), name.getName(), dtm);
funcDef.setReturnType(retval.type);
funcDef.setNoReturn(noReturn);
funcDef.setArguments(getParameterDefinitions());
if (varArg) {
funcDef.setVarArgs(true);
}
if (getProgram().getImportOptions().isOutputSourceLocationInfo() && sourceInfo != null) {
funcDef.setComment(sourceInfo.getDescriptionStr());
}
if (includeCC && callingConventionName != null) {
try {
funcDef.setCallingConvention(callingConventionName);
}
catch (InvalidInputException e) {
Msg.warn(this, "Unable to set calling convention name to %s for function def: %s"
.formatted(callingConventionName, funcDef));
}
}
return funcDef;
}
@Override
public String toString() {
return String.format(
"DWARFFunction [name=%s, address=%s, sourceInfo=%s, retval=%s, params=%s, function=%s, diea=%s, signatureCommitMode=%s]",
name, address, sourceInfo, retval, params, function, diea, signatureCommitMode);
}
}

View file

@ -25,7 +25,6 @@ import java.util.stream.Collectors;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.util.bin.format.dwarf4.*;
import ghidra.app.util.bin.format.dwarf4.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.dwarf4.funcfixup.DWARFFunctionFixup;
import ghidra.app.util.bin.format.dwarf4.next.DWARFFunction.CommitMode;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
@ -33,7 +32,6 @@ import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
@ -62,7 +60,6 @@ public class DWARFFunctionImporter {
private Set<Long> processedOffsets = new HashSet<>();
private Set<Address> functionsProcessed = new HashSet<>();
private Set<Address> variablesProcesesed = new HashSet<>();
private List<DWARFFunctionFixup> functionFixups = DWARFFunctionFixup.findFixups();
private TaskMonitor monitor;
@ -142,6 +139,8 @@ public class DWARFFunctionImporter {
}
}
logImportErrorSummary();
}
private void logImportErrorSummary() {
@ -181,7 +180,7 @@ public class DWARFFunctionImporter {
return;
}
FunctionDefinition origFuncDef = dfunc.asFuncDef(); // before any fixups
FunctionDefinition origFuncDef = dfunc.asFunctionDefinition(true); // before any fixups
if (functionsProcessed.contains(dfunc.address)) {
markAllChildrenAsProcessed(dfunc.diea.getHeadFragment());
@ -199,38 +198,23 @@ public class DWARFFunctionImporter {
// location, we will get multiple side-effect output from processFuncChildren
processFuncChildren(diea, dfunc, 0);
Function gfunc = createFunction(dfunc, diea); // create empty func with no info
if (gfunc == null) {
if (!dfunc.syncWithExistingGhidraFunction(true)) {
// if false, the stub ghidra function could not be found or created
return;
}
if (gfunc.hasNoReturn() && !dfunc.noReturn) {
// preserve the noReturn flag if set by earlier analyzer
dfunc.noReturn = true;
}
dfunc.runFixups();
// Run all the DWARFFunctionFixup instances
for (DWARFFunctionFixup fixup : functionFixups) {
try {
fixup.fixupDWARFFunction(dfunc, gfunc);
}
catch (DWARFException e) {
dfunc.signatureCommitMode = CommitMode.SKIP;
break;
}
}
decorateFunctionWithDWARFInfo(dfunc, gfunc, origFuncDef);
decorateFunctionWithDWARFInfo(dfunc, origFuncDef);
if (dfunc.signatureCommitMode != CommitMode.SKIP) {
updateFunctionSignature(gfunc, dfunc);
dfunc.updateFunctionSignature();
}
else {
Msg.error(this,
String.format(
"Failed to get DWARF function signature information, leaving undefined: %s@%s",
gfunc.getName(), gfunc.getEntryPoint()));
Msg.debug(this, "DIE info: " + diea.toString());
Msg.warn(this,
"Failed to get DWARF function signature information, leaving undefined: %s@%s"
.formatted(dfunc.function.getName(), dfunc.function.getEntryPoint()));
//Msg.debug(this, "DIE info: " + diea.toString());
}
for (DWARFVariable localVar : dfunc.localVars) {
@ -238,9 +222,23 @@ public class DWARFFunctionImporter {
outputGlobal(localVar); // static variable scoped to the function
}
else {
dfunc.commitLocalVariable(localVar, gfunc);
dfunc.commitLocalVariable(localVar);
}
}
if (importOptions.isCreateFuncSignatures()) {
DataType funcDefDT = dfunc.asFunctionDefinition(false);
funcDefDT = prog.getGhidraProgram()
.getDataTypeManager()
.addDataType(funcDefDT, DWARFDataTypeConflictHandler.INSTANCE);
// Look for the source info in the funcdef die and fall back to its
// parent's source info (handles auto-generated ctors and such)
dwarfDTM.addDataType(diea.getOffset(), funcDefDT,
DWARFSourceInfo.getSourceInfoWithFallbackToParent(diea));
}
}
private void decorateFunctionWithAlternateInfo(DWARFFunction dfunc, Function gfunc,
@ -257,12 +255,12 @@ public class DWARFFunctionImporter {
}
}
private void decorateFunctionWithDWARFInfo(DWARFFunction dfunc, Function gfunc,
private void decorateFunctionWithDWARFInfo(DWARFFunction dfunc,
FunctionDefinition origFuncDef) {
if (dfunc.sourceInfo != null) {
// Move the function into the program tree of the file
moveIntoFragment(gfunc.getName(), dfunc.address,
moveIntoFragment(dfunc.function.getName(), dfunc.address,
dfunc.highAddress != null ? dfunc.highAddress : dfunc.address.add(1),
dfunc.sourceInfo.getFilename());
@ -281,7 +279,7 @@ public class DWARFFunctionImporter {
dfunc.name.getOriginalName());
}
FunctionDefinition newFuncDef = dfunc.asFuncDef();
FunctionDefinition newFuncDef = dfunc.asFunctionDefinition(true);
String origFuncDefStr = origFuncDef.getPrototypeString(true);
if (!newFuncDef.getPrototypeString(true).equals(origFuncDefStr)) {
// if the prototype of the function was modified during the fixup phase, append
@ -292,39 +290,6 @@ public class DWARFFunctionImporter {
}
private void updateFunctionSignature(Function gfunc, DWARFFunction dfunc) {
try {
boolean includeStorageDetail = dfunc.signatureCommitMode == CommitMode.STORAGE;
FunctionUpdateType functionUpdateType = includeStorageDetail
? FunctionUpdateType.CUSTOM_STORAGE
: FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS;
Parameter returnVar = dfunc.retval.asReturnParameter(includeStorageDetail);
List<Parameter> parameters = dfunc.getParameters(includeStorageDetail);
if (includeStorageDetail && !dfunc.retval.isZeroByte() &&
dfunc.retval.isMissingStorage()) {
// Update return value in a separate step as its storage isn't typically specified
// in dwarf info.
// This will allow automagical storage assignment for return value by ghidra.
gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, List.of(),
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.IMPORTED);
returnVar = null; // don't update it in the second call to updateFunction()
}
gfunc.updateFunction(dfunc.getCallingConventionName(), returnVar, parameters,
functionUpdateType, true, SourceType.IMPORTED);
gfunc.setVarArgs(dfunc.varArg);
gfunc.setNoReturn(dfunc.noReturn);
}
catch (InvalidInputException | DuplicateNameException e) {
Msg.error(this,
String.format("Error updating function %s@%s with params: %s",
gfunc.getName(), gfunc.getEntryPoint().toString(), e.getMessage()));
Msg.error(this, "DIE info: " + dfunc.diea.toString());
}
}
private void processFuncChildren(DIEAggregate diea, DWARFFunction dfunc,
long offsetFromFuncStart)
throws InvalidInputException, IOException, DWARFExpressionException {
@ -339,7 +304,11 @@ public class DWARFFunctionImporter {
DWARFVariable localVar =
DWARFVariable.readLocalVariable(childDIEA, dfunc, offsetFromFuncStart);
if (localVar != null) {
dfunc.localVars.add(localVar);
if (prog.getImportOptions().isImportLocalVariables() ||
localVar.isRamStorage()) {
// only retain the local var if option is turned on, or global/static variable
dfunc.localVars.add(localVar);
}
}
}
break;

View file

@ -15,13 +15,94 @@
*/
package ghidra.app.util.bin.format.dwarf4.next;
import ghidra.app.plugin.core.analysis.AnalysisOptionsUpdater;
import ghidra.app.plugin.core.analysis.DWARFAnalyzer;
import ghidra.app.services.Analyzer;
import ghidra.framework.options.Options;
/**
* Import options exposed by the {@link DWARFAnalyzer}
*/
public class DWARFImportOptions {
private static final String OPTION_IMPORT_DATATYPES = "Import Data Types";
private static final String OPTION_IMPORT_DATATYPES_DESC =
"Import data types defined in the DWARF debug info.";
private static final String OPTION_PRELOAD_ALL_DIES = "Preload All DIEs";
private static final String OPTION_PRELOAD_ALL_DIES_DESC =
"Preload all DIE records. Requires more memory, but necessary for some non-standard " +
"layouts.";
private static final String OPTION_IMPORT_FUNCS = "Import Functions";
private static final String OPTION_IMPORT_FUNCS_DESC =
"Import function information defined in the DWARF debug info\n" +
"(implies 'Import Data Types' is selected).";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug Item Limit";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_DESC =
"If the number of DWARF debug items are greater than this setting, DWARF analysis will " +
"be skipped.";
private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source Info";
private static final String OPTION_OUTPUT_SOURCE_INFO_DESC =
"Include source code location info (filename:linenumber) in comments attached to the " +
"Ghidra datatype or function or variable created.";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC =
"Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " +
"or variable created.";
private static final String OPTION_NAME_LENGTH_CUTOFF = "Maximum Name Length";
private static final String OPTION_NAME_LENGTH_CUTOFF_DESC =
"Truncate symbol and type names longer than this limit. Range 20..2000";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Add Lexical Block Comments";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC =
"Add comments to the start of lexical blocks";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS =
"Add Inlined Functions Comments";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC =
"Add comments to the start of inlined functions";
private static final String OPTION_OUTPUT_FUNC_SIGS = "Create Function Signatures";
private static final String OPTION_OUTPUT_FUNC_SIGS_DESC =
"Create function signature data types for each function encountered in the DWARF debug " +
"data.";
private static final String OPTION_TRY_PACK_STRUCTS = "Try To Pack Structs";
private static final String OPTION_TRY_PACK_STRUCTS_DESC =
"Try to pack structure/union data types.";
private static final String OPTION_IMPORT_LOCAL_VARS = "Import Local Variable Info";
private static final String OPTION_IMPORT_LOCAL_VARS_DESC =
"Import local variable information from DWARF and attempt to create Ghidra local variables.";
//==================================================================================================
// Old Option Names - Should stick around for multiple major versions after 10.2
//==================================================================================================
private static final String OPTION_IMPORT_DATATYPES_OLD = "Import data types";
private static final String OPTION_PRELOAD_ALL_DIES_OLD = "Preload all DIEs";
private static final String OPTION_IMPORT_FUNCS_OLD = "Import functions";
private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_OLD = "Debug item count limit";
private static final String OPTION_OUTPUT_SOURCE_INFO_OLD = "Output Source info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_OLD = "Output DWARF DIE info";
private static final String OPTION_NAME_LENGTH_CUTOFF_OLD = "Name length cutoff";
private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD = "Lexical block comments";
private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD =
"Inlined functions comments";
private static final String OPTION_OUTPUT_FUNC_SIGS_OLD = "Output function signatures";
//==================================================================================================
// End Old Option Names
//==================================================================================================
private static final int DEFAULT_IMPORT_LIMIT_DIE_COUNT = 2_000_000;
private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater();
private boolean outputDWARFLocationInfo = false;
private boolean outputDWARFDIEInfo = false;
private boolean elideTypedefsWithSameName = true;
@ -37,9 +118,37 @@ public class DWARFImportOptions {
private boolean organizeTypesBySourceFile = true;
private boolean tryPackStructs = true;
private boolean specialCaseSizedBaseTypes = true;
private boolean importLocalVariables = true;
/**
* Create new instance
*/
public DWARFImportOptions() {
// nada
optionsUpdater.registerReplacement(OPTION_IMPORT_DATATYPES, OPTION_IMPORT_DATATYPES_OLD);
optionsUpdater.registerReplacement(OPTION_PRELOAD_ALL_DIES, OPTION_PRELOAD_ALL_DIES_OLD);
optionsUpdater.registerReplacement(OPTION_IMPORT_FUNCS, OPTION_IMPORT_FUNCS_OLD);
optionsUpdater.registerReplacement(OPTION_IMPORT_LIMIT_DIE_COUNT,
OPTION_IMPORT_LIMIT_DIE_COUNT_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_SOURCE_INFO,
OPTION_OUTPUT_SOURCE_INFO_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_DWARF_DIE_INFO,
OPTION_OUTPUT_DWARF_DIE_INFO_OLD);
optionsUpdater.registerReplacement(OPTION_NAME_LENGTH_CUTOFF,
OPTION_NAME_LENGTH_CUTOFF_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS,
OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_INLINE_FUNC_COMMENTS,
OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD);
optionsUpdater.registerReplacement(OPTION_OUTPUT_FUNC_SIGS, OPTION_OUTPUT_FUNC_SIGS_OLD);
}
/**
* See {@link Analyzer#getOptionsUpdater()}
*
* @return {@link AnalysisOptionsUpdater}
*/
public AnalysisOptionsUpdater getOptionsUpdater() {
return optionsUpdater;
}
/**
@ -236,7 +345,7 @@ public class DWARFImportOptions {
/**
* Option to control a feature that copies anonymous types into a structure's "namespace"
* CategoryPath and giving that anonymous type a new name based on the structure's field's
* CategoryPath and giving that anonymousfunction.getEntryPoint() type a new name based on the structure's field's
* name.
*
* @param b boolean flag to set.
@ -322,4 +431,82 @@ public class DWARFImportOptions {
public void setSpecialCaseSizedBaseTypes(boolean b) {
this.specialCaseSizedBaseTypes = b;
}
public boolean isImportLocalVariables() {
return importLocalVariables;
}
public void setImportLocalVariables(boolean importLocalVariables) {
this.importLocalVariables = importLocalVariables;
}
/**
* See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)}
*
* @param options {@link Options}
*/
public void registerOptions(Options options) {
options.registerOption(OPTION_IMPORT_DATATYPES, isImportDataTypes(), null,
OPTION_IMPORT_DATATYPES_DESC);
options.registerOption(OPTION_PRELOAD_ALL_DIES, isPreloadAllDIEs(), null,
OPTION_PRELOAD_ALL_DIES_DESC);
options.registerOption(OPTION_IMPORT_FUNCS, isImportFuncs(), null,
OPTION_IMPORT_FUNCS_DESC);
options.registerOption(OPTION_OUTPUT_DWARF_DIE_INFO, isOutputDIEInfo(), null,
OPTION_OUTPUT_DWARF_DIE_INFO_DESC);
options.registerOption(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, isOutputLexicalBlockComments(),
null, OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC);
options.registerOption(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, isOutputInlineFuncComments(),
null, OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC);
options.registerOption(OPTION_OUTPUT_SOURCE_INFO, isOutputSourceLocationInfo(), null,
OPTION_OUTPUT_SOURCE_INFO_DESC);
options.registerOption(OPTION_IMPORT_LIMIT_DIE_COUNT, getImportLimitDIECount(), null,
OPTION_IMPORT_LIMIT_DIE_COUNT_DESC);
options.registerOption(OPTION_NAME_LENGTH_CUTOFF, getNameLengthCutoff(), null,
OPTION_NAME_LENGTH_CUTOFF_DESC);
options.registerOption(OPTION_OUTPUT_FUNC_SIGS, isCreateFuncSignatures(), null,
OPTION_OUTPUT_FUNC_SIGS_DESC);
options.registerOption(OPTION_TRY_PACK_STRUCTS, isTryPackStructs(), null,
OPTION_TRY_PACK_STRUCTS_DESC);
options.registerOption(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables(), null,
OPTION_IMPORT_LOCAL_VARS_DESC);
}
/**
* See {@link Analyzer#optionsChanged(Options, ghidra.program.model.listing.Program)}
*
* @param options {@link Options}
*/
public void optionsChanged(Options options) {
setOutputDIEInfo(options.getBoolean(OPTION_OUTPUT_DWARF_DIE_INFO, isOutputDIEInfo()));
setPreloadAllDIEs(options.getBoolean(OPTION_PRELOAD_ALL_DIES, isPreloadAllDIEs()));
setOutputSourceLocationInfo(
options.getBoolean(OPTION_OUTPUT_SOURCE_INFO, isOutputSourceLocationInfo()));
setOutputLexicalBlockComments(options.getBoolean(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS,
isOutputLexicalBlockComments()));
setOutputInlineFuncComments(
options.getBoolean(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, isOutputInlineFuncComments()));
setImportDataTypes(options.getBoolean(OPTION_IMPORT_DATATYPES, isImportDataTypes()));
setImportFuncs(options.getBoolean(OPTION_IMPORT_FUNCS, isImportFuncs()));
setImportLimitDIECount(
options.getInt(OPTION_IMPORT_LIMIT_DIE_COUNT, getImportLimitDIECount()));
setNameLengthCutoff(options.getInt(OPTION_NAME_LENGTH_CUTOFF, getNameLengthCutoff()));
setCreateFuncSignatures(
options.getBoolean(OPTION_OUTPUT_FUNC_SIGS, isCreateFuncSignatures()));
setTryPackDataTypes(options.getBoolean(OPTION_TRY_PACK_STRUCTS, isTryPackStructs()));
setImportLocalVariables(
options.getBoolean(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables()));
}
}

View file

@ -15,11 +15,10 @@
*/
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.*;

View file

@ -15,10 +15,9 @@
*/
package ghidra.app.util.bin.format.dwarf4.next;
import java.util.*;
import java.io.Closeable;
import java.io.IOException;
import java.util.*;
import org.apache.commons.collections4.ListValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
@ -29,8 +28,11 @@ import ghidra.app.util.bin.format.dwarf4.attribs.DWARFAttributeFactory;
import ghidra.app.util.bin.format.dwarf4.encoding.*;
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.funcfixup.DWARFFunctionFixup;
import ghidra.app.util.bin.format.dwarf4.next.sectionprovider.*;
import ghidra.app.util.bin.format.golang.rtti.GoSymbolName;
import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.FSUtilities;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.CategoryPath;
@ -64,11 +66,12 @@ public class DWARFProgram implements Closeable {
* all the work that querying all registered DWARFSectionProviders would take.
* <p>
* If the program is an Elf binary, it must have (at least) ".debug_info" and ".debug_abbr",
* program sections, or their compressed "z" versions.
* program sections, or their compressed "z" versions, or ExternalDebugInfo that would point
* to an external DWARF file.
* <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. (lie. outside of Ghidra). See the DSymSectionProvider
* for more info.
* If the program is a MachO binary (Mac), it must have a ".dSYM" directory co-located
* next to the original binary file on the native filesystem (outside of Ghidra). See the
* DSymSectionProvider for more info.
* <p>
* @param program {@link Program} to test
* @return boolean true if program probably has DWARF info, false if not
@ -182,7 +185,7 @@ public class DWARFProgram implements Closeable {
private final boolean stackGrowsNegative;
private final Map<Object, Object> opaqueProps = new HashMap<>();
private List<DWARFFunctionFixup> functionFixups;
/**
* Main constructor for DWARFProgram.
@ -272,6 +275,16 @@ public class DWARFProgram implements Closeable {
debugStrings.clear();
dniCache.clear();
clearDIEIndexes();
if (functionFixups != null) {
for (DWARFFunctionFixup funcFixup : functionFixups) {
if (funcFixup instanceof Closeable c) {
FSUtilities.uncheckedClose(c, null);
}
}
functionFixups.clear();
functionFixups = null;
}
}
public DWARFImportOptions getImportOptions() {
@ -464,7 +477,7 @@ public class DWARFProgram implements Closeable {
String origName = isAnon ? null : name;
String workingName = ensureSafeNameLength(name);
workingName = fixupSpecialMeaningCharacters(workingName);
workingName = GoSymbolName.fixGolangSpecialSymbolnameChars(workingName);
DWARFNameInfo result =
parentDNI.createChild(origName, workingName, DWARFUtil.getSymbolTypeFromDIE(diea));
@ -570,16 +583,6 @@ 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) {
@ -1064,12 +1067,10 @@ public class DWARFProgram implements Closeable {
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);
public List<DWARFFunctionFixup> getFunctionFixups() {
if (functionFixups == null) {
functionFixups = DWARFFunctionFixup.findFixups();
}
return functionFixups;
}
}

View file

@ -38,8 +38,9 @@ import ghidra.util.exception.InvalidInputException;
*/
public class DWARFVariable {
/**
* Creates an unnamed, storage-less {@link DWARFVariable} from a DataType.
*
* Creates an unnamed, storage-less {@link DWARFVariable} from a DataType.
*
* @param dfunc containing function
* @param dt {@link DataType} of the variable
* @return new {@link DWARFVariable}, never null
*/
@ -52,9 +53,8 @@ public class DWARFVariable {
*
* @param diea {@link DIEAggregate} DW_TAG_formal_parameter
* @param dfunc {@link DWARFFunction} that this parameter is attached to
* @param paramOrdinal
* @param paramOrdinal ordinal in containing list
* @return new parameter, never null, possibly without storage info
* @throws IOException if error
*/
public static DWARFVariable readParameter(DIEAggregate diea, DWARFFunction dfunc,
int paramOrdinal) {
@ -72,6 +72,7 @@ public class DWARFVariable {
*
* @param diea {@link DIEAggregate} DW_TAG_variable
* @param dfunc {@link DWARFFunction} that this local var belongs to
* @param offsetFromFuncStart offset from start of containing function
* @return new DWARFVariable that represents a local var, or <strong>null</strong> if
* error reading storage info
*/
@ -199,13 +200,6 @@ public class DWARFVariable {
return stackStorage.getOffset();
}
public String getToolTip() {
return """
<html>Built In Data Types<br>
&nbsp;&nbsp;%s
""".formatted("DEFAULT_DATA_ORG_DESCRIPTION");
}
/**
* @return true if this variable's storage is in ram
*/
@ -482,8 +476,7 @@ public class DWARFVariable {
return result;
}
public Parameter asParameter(boolean includeStorageDetail, Program program)
throws InvalidInputException {
public Parameter asParameter(boolean includeStorageDetail) throws InvalidInputException {
VariableStorage paramStorage = !isMissingStorage() && includeStorageDetail
? getVariableStorage()
: VariableStorage.UNASSIGNED_STORAGE;
@ -493,7 +486,7 @@ public class DWARFVariable {
ParameterImpl result =
new ParameterImpl(newName, Parameter.UNASSIGNED_ORDINAL, type, paramStorage, true,
program, SourceType.IMPORTED);
program.getGhidraProgram(), SourceType.IMPORTED);
result.setComment(getParamComment());
return result;
@ -513,24 +506,14 @@ public class DWARFVariable {
public Parameter asReturnParameter(boolean includeStorageDetail)
throws InvalidInputException {
VariableStorage storage = isVoidType()
VariableStorage varStorage = isVoidType()
? VariableStorage.VOID_STORAGE
: !isMissingStorage() && includeStorageDetail
? getVariableStorage()
: VariableStorage.UNASSIGNED_STORAGE;
return new ReturnParameterImpl(type, storage, true, program.getGhidraProgram());
return new ReturnParameterImpl(type, varStorage, true, program.getGhidraProgram());
}
public void appendComment(String prefix, String comment, String sep) {
if (comment == null || comment.isEmpty()) {
comment = "";
}
else {
comment += sep;
}
this.comment += prefix + comment;
}
public String getDeclInfoString() {
return "%s:%s".formatted(name.getName(), type.getDisplayName());
}

View file

@ -15,16 +15,14 @@
*/
package ghidra.app.util.bin.format.golang;
import static ghidra.app.util.bin.StructConverter.ASCII;
import static ghidra.app.util.bin.StructConverter.BYTE;
import static ghidra.app.util.bin.StructConverter.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
@ -33,8 +31,7 @@ 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;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.*;
/**
* A program section that contains Go build information strings, namely go module package names,
@ -109,7 +106,14 @@ public class GoBuildInfo implements ElfInfoItem {
throw new IOException("Mixed endian-ness");
}
return readStringInfo(reader, inlineStr, program, pointerSize);
DataTypeManager dtm = program.getDataTypeManager();
StructureDataType struct =
new StructureDataType(GoConstants.GOLANG_CATEGORYPATH, "GoBuildInfo", 0, dtm);
struct.add(new ArrayDataType(ASCII, 14, -1, dtm), "magic", "\\xff Go buildinf:");
struct.add(BYTE, "ptrSize", null);
struct.add(BYTE, "flags", null);
return readStringInfo(reader, inlineStr, program, pointerSize, struct);
}
/**
@ -139,10 +143,11 @@ public class GoBuildInfo implements ElfInfoItem {
private final GoModuleInfo moduleInfo; // info about the module that contains the main package. version typically will be "(devel)"
private final List<GoModuleInfo> dependencies;
private final List<GoBuildSettings> buildSettings; // compile/linker flags used during build process
private final StructureDataType struct;
private GoBuildInfo(int pointerSize, Endian endian, String version, String path,
GoModuleInfo moduleInfo, List<GoModuleInfo> dependencies,
List<GoBuildSettings> buildSettings) {
List<GoBuildSettings> buildSettings, StructureDataType struct) {
this.pointerSize = pointerSize;
this.endian = endian;
this.version = version;
@ -150,6 +155,7 @@ public class GoBuildInfo implements ElfInfoItem {
this.moduleInfo = moduleInfo;
this.dependencies = dependencies;
this.buildSettings = buildSettings;
this.struct = struct;
}
public int getPointerSize() {
@ -189,7 +195,6 @@ public class GoBuildInfo implements ElfInfoItem {
decorateProgramInfo(program.getOptions(Program.PROGRAM_INFO));
try {
StructureDataType struct = toStructure(program.getDataTypeManager());
if (struct != null) {
DataUtilities.createData(program, address, struct, -1, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
@ -204,8 +209,7 @@ public class GoBuildInfo implements ElfInfoItem {
GoVer.setProgramPropertiesWithOriginalVersionString(props, getVersion());
props.setString("Golang app path", getPath());
if (getModuleInfo() != null) {
getModuleInfo()
.asKeyValuePairs("Golang main package ")
getModuleInfo().asKeyValuePairs("Golang main package ")
.entrySet()
.stream()
.forEach(entry -> props.setString(entry.getKey(), entry.getValue()));
@ -222,13 +226,7 @@ public class GoBuildInfo implements ElfInfoItem {
}
StructureDataType toStructure(DataTypeManager dtm) {
StructureDataType result =
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);
return result;
return struct.clone(dtm);
}
@Override
@ -240,18 +238,37 @@ public class GoBuildInfo implements ElfInfoItem {
//---------------------------------------------------------------------------------------------
private static GoBuildInfo readStringInfo(BinaryReader reader, boolean inlineStr,
Program program, int ptrSize) throws IOException {
Program program, int ptrSize, StructureDataType struct) throws IOException {
DataTypeManager dtm = program.getDataTypeManager();
String moduleString;
String versionString;
if (inlineStr) {
reader.setPointerIndex(32 /* static start of inline strings */);
versionString = reader.readNext(GoBuildInfo::varlenString);
byte[] moduleStringBytes = reader.readNext(GoBuildInfo::varlenBytes);
LEB128Info verStrLen = reader.readNext(LEB128Info::unsigned);
byte[] versionStringBytes = reader.readNextByteArray(verStrLen.asInt32());
versionString = new String(versionStringBytes, StandardCharsets.UTF_8);
moduleString = extractModuleString(moduleStringBytes);
LEB128Info modStrLen = reader.readNext(LEB128Info::unsigned);
byte[] moduleStringBytes = reader.readNextByteArray(modStrLen.asInt32());
struct.add(new ArrayDataType(BYTE, 16, -1, dtm), -1, "padding", null);
struct.add(new UnsignedLeb128DataType(dtm), verStrLen.getLength(), "versionlen", null);
struct.add(new ArrayDataType(ASCII, verStrLen.asInt32(), -1, dtm), -1, "version", null);
struct.add(new UnsignedLeb128DataType(dtm), modStrLen.getLength(), "modulelen", null);
moduleString = extractModuleString(moduleStringBytes, struct);
try {
String structNameSuffix = "_inline_%d_%d_%d_%d".formatted(verStrLen.getLength(),
verStrLen.asInt32(), modStrLen.getLength(), modStrLen.asInt32());
struct.setName(struct.getName() + structNameSuffix);
}
catch (InvalidNameException e) {
// ignore
}
}
else {
reader.setPointerIndex(16 /* static start of 2 string pointers */);
@ -267,15 +284,19 @@ public class GoBuildInfo implements ElfInfoItem {
fullReader.setPointerIndex(moduleStrOffset);
byte[] moduleStrBytes = readRawGoString(fullReader, ptrSize);
moduleString = extractModuleString(moduleStrBytes);
moduleString = extractModuleString(moduleStrBytes, null);
DataType ofsDT = AbstractIntegerDataType.getUnsignedDataType(ptrSize, dtm);
struct.add(ofsDT, -1, "versionofs", null);
struct.add(ofsDT, -1, "moduleofs", null);
}
return parseBuildInfo(ptrSize, reader.isBigEndian() ? Endian.BIG : Endian.LITTLE,
versionString, moduleString);
versionString, moduleString, struct);
}
private static GoBuildInfo parseBuildInfo(int pointerSize, Endian endian, String versionString,
String moduleString) throws IOException {
String moduleString, StructureDataType struct) throws IOException {
String path = null;
GoModuleInfo module = null;
List<GoModuleInfo> deps = new ArrayList<>();
@ -303,9 +324,8 @@ public class GoBuildInfo implements ElfInfoItem {
path = lineParts[1];
break;
case "mod": {
GoModuleInfo replace = replaceInfo != null
? GoModuleInfo.fromString(replaceInfo, null)
: null;
GoModuleInfo replace =
replaceInfo != null ? GoModuleInfo.fromString(replaceInfo, null) : null;
module = GoModuleInfo.fromString(lineParts[1], replace);
break;
}
@ -326,10 +346,11 @@ public class GoBuildInfo implements ElfInfoItem {
}
return new GoBuildInfo(pointerSize, endian, versionString, path, module, deps,
buildSettings);
buildSettings, struct);
}
private static String extractModuleString(byte[] bytes) throws IOException {
private static String extractModuleString(byte[] bytes, StructureDataType struct)
throws IOException {
int sentLen = INFOSTART_SENTINEL.length; // both are same len
if (bytes.length < sentLen * 2) {
return "";
@ -340,36 +361,19 @@ public class GoBuildInfo implements ElfInfoItem {
!Arrays.equals(INFOEND_SENTINEL, 0, sentLen, bytes, sentEndStart, bytes.length)) {
throw new IOException("bad sentinel");
}
return new String(bytes, sentLen, bytes.length - (sentLen * 2), StandardCharsets.UTF_8);
}
int moduleStrLen = bytes.length - (sentLen * 2);
if (struct != null) {
struct.add(new ArrayDataType(BYTE, sentLen, -1, struct.getDataTypeManager()), -1,
"sentinelstart", null);
struct.add(new ArrayDataType(ASCII, moduleStrLen, -1, struct.getDataTypeManager()), -1,
"moduleinfo", null);
struct.add(new ArrayDataType(BYTE, sentLen, -1, struct.getDataTypeManager()), -1,
"sentinelend", null);
}
/**
* Reads a pascal-ish string used by go in lower level file format.
* <p>
* Not to be confused with a real golang string produced when strings are created in
* a golang program.
*
* @param reader BinaryReader
* @return string at the current index of the BinaryReader
* @throws IOException if error
*/
private static String varlenString(BinaryReader reader) throws IOException {
byte[] bytes = varlenBytes(reader);
return new String(bytes, StandardCharsets.UTF_8);
}
return new String(bytes, sentLen, moduleStrLen, StandardCharsets.UTF_8);
/**
* Reads a variable length byte array.
*
* @param reader BinaryReader
* @return variable length byte array at the current index of the BinaryReader
* @throws IOException if error
*/
private static byte[] varlenBytes(BinaryReader reader) throws IOException {
int strLen = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
byte[] bytes = reader.readNextByteArray(strLen);
return bytes;
}
private static String readGoString(BinaryReader reader, int ptrSize) throws IOException {

View file

@ -27,6 +27,11 @@ public class GoConstants {
* Category path to place golang types in
*/
public static final CategoryPath GOLANG_CATEGORYPATH = new CategoryPath("/golang");
public static final CategoryPath GOLANG_BOOTSTRAP_FUNCS_CATEGORYPATH =
GOLANG_CATEGORYPATH.extend("functions");
public static final CategoryPath GOLANG_RECOVERED_TYPES_CATEGORYPATH =
new CategoryPath("/golang-recovered");
public static final String GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME = "abi-internal";
public static final String GOLANG_ABI0_CALLINGCONVENTION_NAME = "abi0";

View file

@ -18,8 +18,8 @@ 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.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
@ -191,14 +191,12 @@ public class GoFunctionFixup {
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);
if (DWARFUtil.isEmptyArray(dt)) {
dt = makeEmptyArrayDataType(dt);
}
Address zerobaseAddress = GoRttiMapper.getZerobaseAddress(program);
return new ParameterImpl(oldParam.getName(), dt, zerobaseAddress, program,
SourceType.USER_DEFINED);
}
@ -214,10 +212,6 @@ public class GoFunctionFixup {
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 =
@ -246,7 +240,7 @@ public class GoFunctionFixup {
if (DWARFUtil.isEmptyArray(returnDT)) {
returnDT = makeEmptyArrayDataType(returnDT);
}
varnodes.add(new Varnode(GolangSymbolAnalyzer.getZerobaseAddress(program), 1));
varnodes.add(new Varnode(GoRttiMapper.getZerobaseAddress(program), 1));
}
else {
allocateReturnStorage(program, "return-value-alias-variable", returnDT,
@ -273,9 +267,20 @@ public class GoFunctionFixup {
else {
if (!DWARFUtil.isZeroByteDataType(dt)) {
long stackOffset = storageAllocator.getStackAllocation(dt);
varnodes.add(
new Varnode(program.getAddressFactory().getStackSpace().getAddress(stackOffset),
Varnode prev = !varnodes.isEmpty() ? varnodes.get(varnodes.size() - 1) : null;
if (prev != null && prev.getAddress().isStackAddress()) {
// if ( prev.getAddress().getOffset() + prev.getSize() != stackOffset ) {
// throw new InvalidInputException("Non-adjacent stack storage");
// }
Varnode updatedVN =
new Varnode(prev.getAddress(), prev.getSize() + dt.getLength());
varnodes.set(varnodes.size() - 1, updatedVN);
}
else {
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
@ -289,7 +294,8 @@ public class GoFunctionFixup {
public static boolean isGolangAbi0Func(Function func) {
Address funcAddr = func.getEntryPoint();
for (Symbol symbol : func.getProgram().getSymbolTable().getSymbolsAsIterator(funcAddr)) {
if (symbol.getSymbolType() == SymbolType.LABEL) {
if (symbol.getSymbolType() == SymbolType.LABEL ||
symbol.getSymbolType() == SymbolType.FUNCTION) {
String labelName = symbol.getName();
if (labelName.endsWith("abi0")) {
return true;

View file

@ -19,7 +19,8 @@ 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.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.util.NumericUtilities;
@ -37,8 +38,6 @@ public class GoParamStorageAllocator {
private GoRegisterInfo callspecInfo;
private long stackOffset;
private boolean isBigEndian;
private PrototypeModel abiInternalCallingConvention;
private PrototypeModel abi0CallingConvention;
private String archDescription;
/**
@ -57,9 +56,6 @@ public class GoParamStorageAllocator {
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());
@ -67,30 +63,19 @@ public class GoParamStorageAllocator {
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;
archDescription);
}
public String getArchDescription() {
@ -146,6 +131,24 @@ public class GoParamStorageAllocator {
return regs.get(INTREG).isEmpty() && regs.get(FLOATREG).isEmpty();
}
/**
* Returns the integer parameter that follows the supplied register.
*
* @param reg register in the integer reg list
* @return the following register of the queried register, or null if no following register
* found
*/
public Register getNextIntParamRegister(Register reg) {
List<Register> intRegs = regs.get(INTREG);
for (int regNum = 0; regNum < intRegs.size() - 1; regNum++) {
Register tmpReg = intRegs.get(regNum);
if (tmpReg.equals(reg)) {
return intRegs.get(regNum + 1);
}
}
return null;
}
/**
* 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.

View file

@ -15,43 +15,47 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
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.plugin.core.analysis.TransientProgramProperties;
import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE;
import ghidra.app.util.bin.format.dwarf4.*;
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.app.util.bin.format.golang.rtti.GoFuncData;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
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.listing.*;
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;
import ghidra.util.task.TaskMonitor;
/**
* Fixups for golang functions.
* Fixups for golang functions found during DWARF processing.
* <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) -> "/"
* Special characters used by golang in symbol names (middle dot \u00B7, weird slash \u2215) are
* fixed up in DWARFProgram.getDWARFNameInfo() by calling
* GoSymbolName.fixGolangSpecialSymbolnameChars().
* <p>
* Go's 'unique' usage of DW_TAG_subroutine_type to define its ptr-to-ptr-to-func is handled in
* DWARFDataTypeImporter.makeDataTypeForFunctionDefinition().
* <p>
*/
@ExtensionPointProperties(priority = DWARFFunctionFixup.PRIORITY_NORMAL_EARLY)
public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
public static final CategoryPath GOLANG_API_EXPORT =
new CategoryPath(CategoryPath.ROOT, "GolangAPIExport");
private static final String GOLANG_FUNC_INFO_PREFIX = "Golang function info: ";
/**
* Returns true if the specified {@link DWARFFunction} wrapper refers to a function in a golang
@ -73,33 +77,43 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
return true;
}
private GoRttiMapper goBinary;
@Override
public void fixupDWARFFunction(DWARFFunction dfunc, Function gfunc) {
if (!isGolangFunction(dfunc)) {
return;
}
GoVer goVersion = getGolangVersion(dfunc);
if (goVersion == GoVer.UNKNOWN) {
public void fixupDWARFFunction(DWARFFunction dfunc) throws DWARFException {
if (!isGolangFunction(dfunc) || !initGoBinaryContext(dfunc, TaskMonitor.DUMMY)) {
return;
}
DataTypeManager dtm = gfunc.getProgram().getDataTypeManager();
GoParamStorageAllocator storageAllocator =
new GoParamStorageAllocator(gfunc.getProgram(), goVersion);
GoFuncData funcData = goBinary.getFunctionData(dfunc.address);
if (funcData == null) {
appendComment(dfunc.function, GOLANG_FUNC_INFO_PREFIX, "No function data");
dfunc.signatureCommitMode = CommitMode.SKIP;
return;
}
if (!funcData.getFlags().isEmpty()) {
// Don't apply any DWARF info to special functions (ASM) as they are typically
// marked as no-params, but in reality they do have params passed in a non-standard way.
dfunc.signatureCommitMode = CommitMode.SKIP;
return;
}
if (GoFunctionFixup.isGolangAbi0Func(gfunc)) {
DataTypeManager dtm = goBinary.getProgram().getDataTypeManager();
GoParamStorageAllocator storageAllocator = goBinary.newStorageAllocator();
if (goBinary.isGolangAbi0Func(dfunc.function)) {
// 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();
}
dfunc.callingConventionName =
storageAllocator.isAbi0Mode() ? GoConstants.GOLANG_ABI0_CALLINGCONVENTION_NAME
: GoConstants.GOLANG_ABI_INTERNAL_CALLINGCONVENTION_NAME;
GoFunctionMultiReturn multiReturnInfo = fixupFormalFuncDef(dfunc, storageAllocator, dtm);
fixupCustomStorage(dfunc, gfunc, storageAllocator, dtm, multiReturnInfo);
fixupCustomStorage(dfunc, storageAllocator, dtm, multiReturnInfo);
}
private GoFunctionMultiReturn fixupFormalFuncDef(DWARFFunction dfunc,
@ -109,10 +123,19 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
// 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.
// Note: sometimes Go will duplicate the dwarf information about return values, which
// will lead to have multiple "~r0", "~r1" elements. These need to be de-duped.
List<DWARFVariable> realParams = new ArrayList<>();
List<DWARFVariable> returnParams = new ArrayList<>();
Set<String> returnParamNames = new HashSet<>();
for (DWARFVariable dvar : dfunc.params) {
if (dvar.isOutputParameter) {
if (returnParamNames.contains(dvar.name.getName())) {
// skip this, its probably a duplicate. Golang github issue #61357
continue;
}
returnParamNames.add(dvar.name.getName());
returnParams.add(dvar);
}
else {
@ -136,9 +159,8 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
return multiReturn;
}
private void fixupCustomStorage(DWARFFunction dfunc, Function gfunc,
GoParamStorageAllocator storageAllocator, DataTypeManager dtm,
GoFunctionMultiReturn multiReturn) {
private void fixupCustomStorage(DWARFFunction dfunc, 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.
@ -200,9 +222,9 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
storageAllocator, false);
}
Program program = gfunc.getProgram();
Program program = goBinary.getProgram();
if (!program.getMemory().isBigEndian()) {
// revserse the ordering of the storage varnodes when little-endian
// Reverse the ordering of the storage varnodes when little-endian
List<Varnode> varnodes = dfunc.retval.getVarnodes();
GoFunctionFixup.reverseNonStackStorageLocations(varnodes);
dfunc.retval.setVarnodes(varnodes);
@ -267,93 +289,26 @@ public class GolangDWARFFunctionFixup implements DWARFFunctionFixup {
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);
}
Program program = dprog.getGhidraProgram();
Address zerobaseAddr = TransientProgramProperties.getProperty(program, GOLANG_ZEROBASE_ADDR,
SCOPE.ANALYSIS_SESSION, Address.class, () -> GoRttiMapper.getZerobaseAddress(program));
return zerobaseAddr;
}
private boolean initGoBinaryContext(DWARFFunction dfunc, TaskMonitor monitor) {
if (goBinary == null) {
Program program = dfunc.getProgram().getGhidraProgram();
goBinary = GoRttiMapper.getSharedGoBinary(program, monitor);
}
return goBinary != null;
}
private void appendComment(Function func, String prefix, String comment) {
DWARFUtil.appendComment(goBinary.getProgram(), func.getEntryPoint(), CodeUnit.PLATE_COMMENT,
prefix, comment, "\n");
}
}

View file

@ -28,6 +28,7 @@ import ghidra.program.model.listing.Program;
*/
public class NoteGoBuildId extends ElfNote {
public static final String SECTION_NAME = ".note.go.buildid";
public static final String PROGRAM_INFO_KEY = "Golang BuildId";
/**
* Reads a NoteGoBuildId from the specified BinaryReader, matching the signature of
@ -67,7 +68,7 @@ public class NoteGoBuildId extends ElfNote {
@Override
public String getProgramInfoKey() {
return "Golang BuildId";
return PROGRAM_INFO_KEY;
}
@Override

View file

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang;
import java.util.Arrays;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteArrayProvider;
@ -105,7 +104,8 @@ public class PEGoBuildId implements ElfInfoItem {
@Override
public void markupProgram(Program program, Address address) {
program.getOptions(Program.PROGRAM_INFO).setString("Golang BuildId", getBuildId());
program.getOptions(Program.PROGRAM_INFO)
.setString(NoteGoBuildId.PROGRAM_INFO_KEY, getBuildId());
try {
StructureDataType struct = toStructure(program.getDataTypeManager());

View file

@ -16,14 +16,23 @@
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.symbol.SymbolUtilities;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
/**
* A structure that golang generates that contains metadata about a function.
*/
@StructureMapping(structureName = "runtime._func")
public class GoFuncData implements StructureMarkup<GoFuncData> {
@ContextField
private GoRttiMapper programContext;
@ -31,37 +40,239 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
private StructureContext<GoFuncData> context;
@FieldMapping(optional = true, fieldName = { "entryoff", "entryOff" })
@EOLComment("description")
@MarkupReference("funcAddress")
@EOLComment("getDescription")
@MarkupReference("getFuncAddress")
private long entryoff; // valid in >=1.18, relative offset of function
@FieldMapping(optional = true)
@EOLComment("description")
@MarkupReference("funcAddress")
@EOLComment("getDescription")
@MarkupReference("getFuncAddress")
private long entry; // valid in <=1.17, location of function
@FieldMapping(fieldName = { "nameoff", "nameOff" })
@MarkupReference("nameAddress")
@MarkupReference("getNameAddress")
private long nameoff;
//private long args; // size of arguments
@FieldMapping
private long pcfile; // offset in moduledata.pctab where file info starts
@FieldMapping
private long pcln; // offset in moduledata.pctab where line num info starts
@FieldMapping
private int npcdata; // number of elements in varlen pcdata array
@FieldMapping
private long cuOffset;
@FieldMapping
@EOLComment("getFuncIDEnum")
private byte funcID; // see GoFuncID enum
@FieldMapping
@EOLComment("flags")
private byte flag; // runtime.funcFlag, see GoFuncFlag enum
@FieldMapping
private int nfuncdata; // number of elements in varlen funcdata array
//--------------------------------------------------------------------------------------
private Address funcAddress; // set when entryoff or entry are set
/**
* Sets the function's entry point via a relative offset value
* <p>
* Called via deserialization for entryoff fieldmapping annotation
*
* @param entryoff relative offset to function
*/
public void setEntryoff(long entryoff) {
this.entryoff = entryoff;
GoModuledata moduledata = getModuledata();
this.funcAddress = moduledata != null ? moduledata.getText().add(entryoff) : null;
this.entry = funcAddress != null ? funcAddress.getOffset() : -1;
}
/**
* Sets the absolute entry address.
* <p>
* Called via deserialization for entry fieldmapping annotation
*
* @param entry absolute value.
*/
public void setEntry(long entry) {
this.entry = entry;
this.funcAddress = context.getDataTypeMapper().getCodeAddress(entry);
}
/**
* Returns the address of this function.
*
* @return the address of this function
*/
public Address getFuncAddress() {
return funcAddress;
}
/**
* Returns the address range of this function's body, recovered by examining addresses in the
* function's pc-to-filename translation table, or if not present, a single address range
* that contains the function's entry point.
*
* @return {@link AddressRange} representing the function's known footprint
*/
public AddressRange getBody() {
// find the body of a function by looking at its pc-to-filename translation table and
// using the max pc value
try {
long max = new GoPcValueEvaluator(this, pcfile).getMaxPC() - 1;
return max > entry
? new AddressRangeImpl(funcAddress, funcAddress.getNewAddress(max))
: null;
}
catch (IOException e) {
return new AddressRangeImpl(getFuncAddress(), getFuncAddress());
}
}
/**
* Returns the Ghidra function that corresponds to this go function.
*
* @return Ghidra {@link Function}, or null if there is no Ghidra function at the address
*/
public Function getFunction() {
Address addr = getFuncAddress();
return programContext.getProgram().getFunctionManager().getFunctionAt(addr);
}
private long getPcDataStartOffset(int tableIndex) {
return context.getStructureLength() + (4 /*size(int32)*/ * tableIndex);
}
private long getPcDataStart(int tableIndex) throws IOException {
return context.getFieldReader(getPcDataStartOffset(tableIndex)).readNextUnsignedInt();
}
private long getFuncDataPtr(int tableIndex) throws IOException {
// hacky, since both pcdata and funcdata are sequential int32[] arrays, just reuse logic
// for first one to index into second one
return getPcDataStart(npcdata + tableIndex);
}
/**
* Returns a value from the specified pc->value lookup table, for a specific
* address (that should be within the function's footprint).
*
* @param tableIndex {@link GoPcDataTable} enum
* @param targetPC address (inside the function) to determine the value of
* @return int value, will be specific to the {@link GoPcDataTable table} it comes from, or
* -1 if the requested table index is not present for this function
* @throws IOException if error reading lookup data
*/
public int getPcDataValue(GoPcDataTable tableIndex, long targetPC) throws IOException {
if (tableIndex == null || tableIndex.ordinal() >= npcdata) {
return -1;
}
long pcstart = getPcDataStart(tableIndex.ordinal());
return new GoPcValueEvaluator(this, pcstart).eval(targetPC);
}
/**
* Returns all values for the specified pc->value lookup table for the entire range of the
* function's footprint.
*
* @param tableIndex {@link GoPcDataTable} enum
* @return list of int values, will be specific to the {@link GoPcDataTable table} it comes
* from, or an empty list if the requested table index is not present for this function
* @throws IOException if error reading lookup data
*/
public List<Integer> getPcDataValues(GoPcDataTable tableIndex) throws IOException {
if (tableIndex == null || tableIndex.ordinal() >= npcdata) {
return List.of();
}
long pcstart = getPcDataStart(tableIndex.ordinal());
return new GoPcValueEvaluator(this, pcstart).evalAll(Long.MAX_VALUE);
}
/**
* Returns a value associated with this function.
*
* @param tableIndex {@link GoFuncDataTable} enum
* @return requested value, or -1 if the requested table index is not present for this function
* @throws IOException if error reading lookup data
*/
public long getFuncDataValue(GoFuncDataTable tableIndex) throws IOException {
if (tableIndex == null || tableIndex.ordinal() < 0 || tableIndex.ordinal() >= nfuncdata) {
return -1;
}
long gofuncoffset = getModuledata().getGofunc();
if (gofuncoffset == 0) {
return -1;
}
long off = getFuncDataPtr(tableIndex.ordinal());
return off == -1 ? null : gofuncoffset + off;
}
/**
* Attempts to build a 'function signature' string representing the known information about
* this function's arguments, using go's built-in stack trace metadata.
* <p>
* The information that can be recovered about arguments is limited to:
* <ul>
* <li>the size of the argument
* <li>general grouping (eg. grouping of arg values as a structure or array)
* </ul>
* Return value information is unknown and always represented as an "undefined" data type.
*
* @return pseduo-function signature string, such as "undefined foo( 8, 8 )" which would
* indicate the function had 2 8-byte arguments
* @throws IOException if error reading lookup data
*/
public String recoverFunctionSignature() throws IOException {
RecoveredSignature sig = RecoveredSignature.read(this, programContext);
return sig.toString();
}
/**
* Attempts to return a {@link FunctionDefinition} for this function, based on this
* function's inclusion in a golang interface as a method.
*
* @return {@link FunctionDefinition}
* @throws IOException if error
*/
public FunctionDefinition findMethodSignature() throws IOException {
MethodInfo methodInfo = findMethodInfo();
return methodInfo != null ? methodInfo.getSignature() : null;
}
/**
* Attempts to return a {@link GoMethodInfo} for this function, based on this
* function's inclusion in a golang interface as a method.
*
* @return {@link GoMethodInfo}
*/
public GoMethodInfo findMethodInfo() {
for (MethodInfo methodInfo : programContext.getMethodInfoForFunction(funcAddress)) {
if (methodInfo instanceof GoMethodInfo gmi) {
if (gmi.isTfn(funcAddress)) {
return gmi;
}
}
}
return null;
}
/**
* Returns the address of this function's name string.
* <p>
* Referenced from nameoff's markup annotation
*
* @return {@link Address}
*/
public Address getNameAddress() {
GoModuledata moduledata = getModuledata();
return moduledata != null
@ -69,23 +280,109 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
: null;
}
public String getName() throws IOException {
/**
* Returns the name of this function.
*
* @return String name of this function
*/
public String getName() {
GoModuledata moduledata = getModuledata();
return moduledata != null
? programContext.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff)
.readNextUtf8String()
: null;
try {
if (moduledata != null) {
return programContext
.getReader(moduledata.getFuncnametab().getArrayOffset() + nameoff)
.readNextUtf8String();
}
}
catch (IOException e) {
// fall thru
}
return "unknown_func_%x_%s".formatted(context.getStructureStart(),
funcAddress != null ? funcAddress : "missing_addr");
}
public String getDescription() throws IOException {
/**
* Returns the name of this function, as a parsed symbol object.
*
* @return {@link GoSymbolName} containing this function's name
*/
public GoSymbolName getSymbolName() {
return GoSymbolName.parse(getName());
}
/**
* Returns a descriptive string.
* <p>
* Referenced from the entry, entryoff field's markup annotation
*
* @return String description
*/
public String getDescription() {
return getName() + "@" + getFuncAddress();
}
/**
* Returns true if this function is inline
* @return true if this function is inline
*/
public boolean isInline() {
return entryoff == -1 || entryoff == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG;
}
private GoModuledata getModuledata() {
/**
* Returns the func flags for this function.
*
* @return {@link GoFuncFlag}s
*/
public Set<GoFuncFlag> getFlags() {
return GoFuncFlag.parseFlags(flag);
}
/**
* Returns true if this function is an ASM function
*
* @return true if this function is an ASM function
*/
public boolean isAsmFunction() {
return GoFuncFlag.ASM.isSet(flag);
}
/**
* Returns the {@link GoFuncID} enum that categorizes this function
* @return the {@link GoFuncID} enum that categorizes this function
*/
public GoFuncID getFuncIDEnum() {
return GoFuncID.parseIDByte(funcID);
}
/**
* Returns information about the source file that this function was defined in.
*
* @return {@link GoSourceFileInfo}, or null if no source file info present
* @throws IOException if error reading lookup data
*/
public GoSourceFileInfo getSourceFileInfo() throws IOException {
GoModuledata moduledata = getModuledata();
if (moduledata == null) {
return null;
}
int fileno = new GoPcValueEvaluator(this, pcfile).eval(entry);
int lineNum = new GoPcValueEvaluator(this, pcln).eval(entry);
long fileoff = fileno >= 0
? moduledata.getCutab()
.readUIntElement(4 /*sizeof(uint32)*/, (int) cuOffset + fileno)
: -1;
String fileName = fileoff != -1 ? moduledata.getFilename(fileoff) : null;
return fileName != null ? new GoSourceFileInfo(fileName, lineNum) : null;
}
/**
* Returns a reference to the {@link GoModuledata} that contains this function.
*
* @return {@link GoModuledata} that contains this function
*/
public GoModuledata getModuledata() {
return programContext.findContainingModuleByFuncData(context.getStructureStart());
}
@ -100,10 +397,185 @@ public class GoFuncData implements StructureMarkup<GoFuncData> {
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
Address addr = getFuncAddress();
String name = SymbolUtilities.replaceInvalidChars(getName(), true);
session.createFunctionIfMissing(name, addr);
public void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
if (npcdata > 0) {
ArrayDataType pcdataArrayDT = new ArrayDataType(programContext.getUint32DT(), npcdata,
-1, programContext.getDTM());
Address addr = context.getStructureAddress().add(getPcDataStartOffset(0));
session.markupAddress(addr, pcdataArrayDT);
session.labelAddress(addr, getStructureLabel() + "___pcdata");
}
if (nfuncdata > 0) {
ArrayDataType funcdataArrayDT = new ArrayDataType(programContext.getUint32DT(),
nfuncdata, -1, programContext.getDTM());
Address addr = context.getStructureAddress().add(getPcDataStartOffset(npcdata));
session.markupAddress(addr, funcdataArrayDT);
session.labelAddress(addr, getStructureLabel() + "___funcdata");
}
}
//-------------------------------------------------------------------------------------------
/**
* Represents approximate parameter signatures for a function.
* <p>
* Golang's exception/stack-trace metadata is mined to provide these approximate signatures,
* and any limitation in the information recovered is due to what golang stores.
* <p>
* Instead of data types, only the size and limited grouping of structure/array parameters
* is recoverable.
*
* @param returnType return type of the function (currently just undefined)
* @param name name of the function
* @param args list of recovered arguments
* @param partial boolean flag, if true there was an argument that was marked as partial
* @param error boolean flag, if true there was an error reading the argument info
*
*/
record RecoveredSignature(DataType returnType, String name, List<RecoveredArg> args,
boolean partial, boolean error) {
private static final int ARGINFO_ENDSEQ = 0xff;
private static final int ARGINFO_STARTAGG = 0xfe;
private static final int ARGINFO_ENDAGG = 0xfd;
private static final int ARGINFO_DOTDOTDOT = 0xfc;
private static final int ARGINFO_OFFSET_TOOLARGE = 0xfb;
public static RecoveredSignature read(GoFuncData funcData, GoRttiMapper goBinary)
throws IOException {
RecoveredArg args = readArgs(funcData, goBinary);
return new RecoveredSignature(DataType.DEFAULT, funcData.getName(), args.subArgs,
args.hasPartialFlag(), args.partial);
}
public static RecoveredArg readArgs(GoFuncData funcData, GoRttiMapper goBinary)
throws IOException {
long argInfoOffset = funcData.getFuncDataValue(GoFuncDataTable.FUNCDATA_ArgInfo);
if (argInfoOffset == -1) {
return new RecoveredArg(List.of(), 0, false);
}
BinaryReader argInfoReader = goBinary.getReader(argInfoOffset);
Deque<List<RecoveredArg>> resultStack = new ArrayDeque<>();
List<RecoveredArg> parent = null;
List<RecoveredArg> current = new ArrayList<>();
List<RecoveredArg> results = current;
try {
while (true) {
int b = argInfoReader.readNextUnsignedByte();
switch (b) {
case ARGINFO_ENDSEQ:
return new RecoveredArg(results, 0, false);
case ARGINFO_STARTAGG:
parent = current;
current = new ArrayList<>();
resultStack.addLast(current);
break;
case ARGINFO_ENDAGG:
if (parent == null) {
throw new IOException("no parent");
}
parent.add(new RecoveredArg(current, 0, false));
current = parent;
parent = resultStack.pollLast();
break;
case ARGINFO_DOTDOTDOT:
current.add(new RecoveredArg(null, -1, true));
break;
case ARGINFO_OFFSET_TOOLARGE:
current.add(new RecoveredArg(null, -2, true));
break;
default:
// b == 'offset', but value doesn't seem to be consistently useful
int sz = argInfoReader.readNextUnsignedByte();
current.add(new RecoveredArg(null, sz, false));
break;
}
}
}
catch (IOException e) {
return new RecoveredArg(results, 0, true /* error flag */);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (partial) {
sb.append("[partial] ");
}
if (error) {
sb.append("[error] ");
}
sb.append(returnType != null ? returnType.getName() : "???");
sb.append(" ").append(name).append("(");
boolean first = true;
for (RecoveredArg arg : args) {
if (!first) {
sb.append(", ");
}
first = false;
arg.concatString(sb);
}
sb.append(")");
return sb.toString();
}
}
/**
* Represents the information recovered about a single argument.
*
* @param subArgs list of components if this arg is an aggregate, otherwise null
* @param argSize size of this arg if it primitive
* @param partial boolean flag, if true this arg was marked as a "..." or "_" arg
*
*/
record RecoveredArg(List<RecoveredArg> subArgs, int argSize, boolean partial) {
boolean hasPartialFlag() {
if (partial) {
return true;
}
if (subArgs != null) {
for (RecoveredArg subArg : subArgs) {
if (subArg.hasPartialFlag()) {
return true;
}
}
}
return false;
}
void concatString(StringBuilder sb) {
if (subArgs != null) {
boolean first = true;
sb.append("struct? {");
for (RecoveredArg subArg : subArgs) {
if (!first) {
sb.append(", ");
}
first = false;
subArg.concatString(sb);
}
sb.append("}");
}
else {
sb.append(switch (argSize) {
case -1 -> "...";
case -2 -> "???";
default -> Integer.toString(argSize);
});
}
}
}
@Override
public String toString() {
return "GoFuncData [getFuncAddress()=%s, getSymbolName()=%s, getStructureContext()=%s]"
.formatted(getFuncAddress(), getSymbolName(), getStructureContext());
}
}
@ -126,4 +598,6 @@ Length: 40 Alignment: 4
uint8 nfuncdata
} pack()
int32[] pcdata
int32[] funcdata
*/

View file

@ -0,0 +1,31 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
/**
* An index into a GoFuncData's variable-size funcdata array. See GoFuncData's nfuncdata for
* actual array size.
*/
public enum GoFuncDataTable {
FUNCDATA_ArgsPointerMaps, // 0
FUNCDATA_LocalsPointerMaps, // 1
FUNCDATA_StackObjects, // 2;
FUNCDATA_InlTree, // 3
FUNCDATA_OpenCodedDeferInfo, // 4
FUNCDATA_ArgInfo, // 5
FUNCDATA_ArgLiveInfo, // 6
FUNCDATA_WrapInfo // 7
}

View file

@ -0,0 +1,54 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.util.EnumSet;
import java.util.Set;
/**
* Bitmask flags for runtime._func (GoFuncData) flags field.
*/
public enum GoFuncFlag {
TOPFRAME(1 << 0), // 1
SPWRITE(1 << 1), // 2
ASM(1 << 2); // 4
private final int value;
private GoFuncFlag(int i) {
this.value = i;
}
public int getValue() {
return value;
}
public boolean isSet(int i) {
return (i & value) != 0;
}
public static Set<GoFuncFlag> parseFlags(int b) {
EnumSet<GoFuncFlag> result = EnumSet.noneOf(GoFuncFlag.class);
for (GoFuncFlag flag : values()) {
if (flag.isSet(b)) {
result.add(flag);
}
}
return result;
}
}

View file

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
/**
* List of function ids for runtime._func (GoFuncData) funcID field.
*/
public enum GoFuncID {
NORMAL,
ABORT,
ASMCGOCALL,
ASYNCPREEMPT,
CGOCALLBACK,
DEBUGCALLV2,
GCBGMARKWORKER,
GOEXIT,
GOGO,
GOPANIC,
HANDLEASYNCEVENT,
MCALL,
MORESTACK,
MSTART,
PANICWRAP,
RT0_GO,
RUNFINQ,
RUNTIME_MAIN,
SIGPANIC,
SYSTEMSTACK,
SYSTEMSTACK_SWITCH,
WRAPPER;
public static GoFuncID parseIDByte(int b) {
GoFuncID[] values = values();
return 0 <= b && b < values.length ? values[b] : null;
}
}

View file

@ -20,6 +20,10 @@ import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
/**
* A structure that golang generates that maps between a function's entry point and the
* location of the function's GoFuncData structure.
*/
@StructureMapping(structureName = "runtime.functab")
public class GoFunctabEntry {
@ContextField
@ -29,19 +33,26 @@ public class GoFunctabEntry {
private StructureContext<GoFunctabEntry> context;
@FieldMapping(optional = true)
@MarkupReference("funcAddress")
@MarkupReference("getFuncAddress")
private long entryoff; // valid in >=1.18, relative offset of function
@FieldMapping(optional = true)
@MarkupReference("funcAddress")
@MarkupReference("getFuncAddress")
private long entry; // valid in <=1.17, location of function
@FieldMapping
@MarkupReference("funcData")
@MarkupReference("getFuncData")
private long funcoff; // offset into pclntable -> _func
private Address funcAddress;
/**
* Set the function's entry point using a relative offset.
* <p>
* Called via deserialization for entryoff fieldmapping annotation.
*
* @param entryoff relative offset of the function's entry point
*/
public void setEntryoff(long entryoff) {
this.entryoff = entryoff;
@ -49,15 +60,33 @@ public class GoFunctabEntry {
this.funcAddress = moduledata != null ? moduledata.getText().add(entryoff) : null;
}
/**
* Set the function's entry point using the absolute address.
* <p>
* Called via deserialization for entry fieldmapping annotation.
*
* @param entry address of the function's entry point
*/
public void setEntry(long entry) {
this.entry = entry;
this.funcAddress = programContext.getCodeAddress(entry);
}
/**
* Returns the address of the function's entry point
*
* @return address of the function's entry point
*/
public Address getFuncAddress() {
return funcAddress;
}
/**
* Return the GoFuncData structure that contains metadata about the function.
*
* @return {@link GoFuncData} structure that contains metadata about the function.
* @throws IOException if error
*/
@Markup
public GoFuncData getFuncData() throws IOException {
GoModuledata moduledata = getModuledata();
@ -66,6 +95,11 @@ public class GoFunctabEntry {
: null;
}
/**
* Returns the offset of the GoFuncData structure.
*
* @return offset of the GoFuncData structure.
*/
public long getFuncoff() {
return funcoff;
}

View file

@ -19,6 +19,9 @@ import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
/**
* A structure that golang generates that maps between a interface and its data
*/
@StructureMapping(structureName = "runtime.iface")
public class GoIface {
@ContextField
@ -28,7 +31,7 @@ public class GoIface {
private StructureContext<GoIface> context;
@FieldMapping
@MarkupReference("itab")
@MarkupReference("getItab")
long tab; // runtime.itab *
@FieldMapping
@ -39,4 +42,15 @@ public class GoIface {
return programContext.readStructure(GoItab.class, tab);
}
@Override
public String toString() {
try {
return "GoIface { offset: %x, type: %s }"
.formatted(context != null ? context.getStructureStart() : 0, getItab());
}
catch (IOException e) {
return "GoIface { %x, %x }".formatted(tab, data);
}
}
}

View file

@ -16,16 +16,21 @@
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.*;
import java.util.Map.Entry;
import ghidra.app.util.bin.format.golang.rtti.types.GoInterfaceType;
import ghidra.app.util.bin.format.golang.rtti.types.GoType;
import ghidra.app.util.bin.format.golang.rtti.types.*;
import ghidra.app.util.bin.format.golang.rtti.types.GoIMethod.GoIMethodInfo;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.util.Msg;
/**
* Represents a mapping between a golang interface and a type that implements the methods of
* the interface.
*/
@PlateComment
@StructureMapping(structureName = "runtime.itab")
public class GoItab implements StructureMarkup<GoItab> {
@ -36,41 +41,130 @@ public class GoItab implements StructureMarkup<GoItab> {
private StructureContext<GoItab> context;
@FieldMapping
@MarkupReference("interfaceType")
@MarkupReference("getInterfaceType")
long inter; // runtime.interfacetype *
@FieldMapping
@MarkupReference("type")
@MarkupReference("getType")
long _type; // runtime._type *
@FieldMapping
long fun; // inline varlen array, specd as uintptr[1], we are treating as simple long
/**
* Returns the interface implemented by the specified type.
*
* @return interface implemented by the specified type
* @throws IOException if error reading ref'd interface structure
*/
@Markup
public GoInterfaceType getInterfaceType() throws IOException {
return programContext.readStructure(GoInterfaceType.class, inter);
}
/**
* Returns the type that implements the specified interface.
*
* @return type that implements the specified interface
* @throws IOException if error reading the ref'd type structure
*/
@Markup
public GoType getType() throws IOException {
return programContext.getGoType(_type);
}
/**
* Return the number of methods implemented.
*
* @return number of methods implemented
* @throws IOException if error reading interface structure
*/
public long getFuncCount() throws IOException {
GoInterfaceType iface = getInterfaceType();
GoSlice methods = iface.getMethodsSlice();
return Math.max(1, methods.getLen());
}
/**
* Returns an artificial slice that contains the address of the functions that implement
* the interface methods.
*
* @return artificial slice that contains the address of the functions that implement
* the interface methods
* @throws IOException if error reading method info
*/
public GoSlice getFunSlice() throws IOException {
long funcCount = getFuncCount();
long funOffset = context.getStructureEnd() - programContext.getPtrSize();
return new GoSlice(funOffset, funcCount, funcCount, programContext);
}
private Map<Address, GoIMethod> getInterfaceMethods() throws IOException {
long[] functionAddrs = getFunSlice().readUIntList(programContext.getPtrSize());
GoInterfaceType iface = getInterfaceType();
List<GoIMethod> ifaceMethods = iface.getMethods();
if (functionAddrs.length != ifaceMethods.size()) {
Msg.warn(this, "Bad interface spec: %s, iface length doesn't match function impl list"
.formatted(getStructureLabel()));
return Map.of();
}
Map<Address, GoIMethod> results = new HashMap<>();
for (int i = 0; i < functionAddrs.length; i++) {
if (functionAddrs[i] == 0) {
continue;
}
Address addr = programContext.getCodeAddress(functionAddrs[i]);
if (!programContext.getProgram()
.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(addr)) {
continue;
}
GoIMethod imethod = ifaceMethods.get(i);
results.put(addr, imethod);
}
return results;
}
/**
* Returns list of {@link GoIMethodInfo} instances, that represent the methods implemented by
* the specified type / interface.
*
* @return list of {@link GoIMethodInfo} instances
* @throws IOException if error reading interface method list
*/
public List<GoIMethodInfo> getMethodInfoList() throws IOException {
List<GoIMethodInfo> results = new ArrayList<>();
for (Entry<Address, GoIMethod> entry : getInterfaceMethods().entrySet()) {
results.add(new GoIMethodInfo(this, entry.getValue(), entry.getKey()));
}
return results;
}
/**
* Returns a {@link FunctionDefinition} for the specified method of this itab.
*
* @param imethod info about an interface method
* @return {@link FunctionDefinition} for the specified method of this itab
* @throws IOException if error reading required info
*/
public FunctionDefinition getSignatureFor(GoIMethod imethod) throws IOException {
GoType receiverType = getType();
DataType receiverDT = programContext.getRecoveredType(receiverType);
GoType methodType = imethod.getType();
if (methodType == null) {
return null;
}
return programContext.getSpecializedMethodSignature(imethod.getName(), methodType,
receiverDT, false);
}
@Override
public String getStructureName() throws IOException {
return getInterfaceType().getStructureName();
return "%s__implements__%s".formatted(getType().getName(),
getInterfaceType().getName());
}
@Override
@ -83,7 +177,7 @@ public class GoItab implements StructureMarkup<GoItab> {
GoSlice funSlice = getFunSlice();
List<Address> funcAddrs = Arrays.stream(funSlice.readUIntList(programContext.getPtrSize()))
.mapToObj(offset -> programContext.getCodeAddress(offset))
.collect(Collectors.toList());
.toList();
// this adds references from the elements of the artificial slice. However, the reference
// from element[0] of the real "fun" array won't show anything in the UI even though
// there is a outbound reference there.
@ -91,15 +185,17 @@ public class GoItab implements StructureMarkup<GoItab> {
GoSlice extraFunSlice =
funSlice.getSubSlice(1, funSlice.getLen() - 1, programContext.getPtrSize());
extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", (DataType) null,
true, session);
extraFunSlice.markupArray(getStructureName() + "_extra_itab_functions", null,
(DataType) null, true, session);
}
@Override
public String toString() {
try {
String s = "itab for " + getStructureName();
GoInterfaceType ifaceType = getInterfaceType();
String s =
"itab for %s implements %s".formatted(getType().getName(),
ifaceType.getName());
String methodListString = ifaceType.getMethodListString();
if (!methodListString.isEmpty()) {
s += "\n// Methods\n" + methodListString;

View file

@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.rtti.types.GoType;
@ -31,10 +30,11 @@ import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolUtilities;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Represents a golang moduledata structure, which contains a lot of invaluable bootstrapping
* Represents a golang moduledata structure, which contains a lot of valuable bootstrapping
* data for RTTI and function data.
*/
@StructureMapping(structureName = "runtime.moduledata")
@ -60,6 +60,9 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
@FieldMapping(fieldName = "etypes")
private long typesEndOffset;
@FieldMapping(optional = true)
private long gofunc;
@FieldMapping(fieldName = "typelinks")
private GoSlice typeLinks;
@ -108,24 +111,62 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return programContext.readStructure(GoPcHeader.class, pcHeader);
}
/**
* Returns the address of the beginning of the text section.
*
* @return address of the beginning of the text section
*/
public Address getText() {
return programContext.getCodeAddress(text);
}
/**
* Returns the starting offset of type info
*
* @return starting offset of type info
*/
public long getTypesOffset() {
return typesOffset;
}
/**
* Returns the ending offset of type info
*
* @return ending offset of type info
*/
public long getTypesEndOffset() {
return typesEndOffset;
}
/**
* Return the offset of the gofunc location
* @return offset of the gofunc location
*/
public long getGofunc() {
return gofunc;
}
/**
* Reads a {@link GoFuncData} structure from the pclntable.
*
* @param offset relative to the pclntable
* @return {@link GoFuncData}
* @throws IOException if error reading data
*/
public GoFuncData getFuncDataInstance(long offset) throws IOException {
return programContext.readStructure(GoFuncData.class, pclntable.getArrayOffset() + offset);
}
/**
* Returns true if this GoModuleData is the module data that contains the specified
* GoFuncData structure.
*
* @param offset offset of a GoFuncData structure
* @return true if this GoModuleData is the module data that contains the specified GoFuncData
* structure
*/
public boolean containsFuncDataInstance(long offset) {
return pclntable.isOffsetWithinData(offset, 1);
return pclntable.containsOffset(offset, 1);
}
/**
@ -143,9 +184,14 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return subSlice;
}
/**
* Returns true if this module data structure contains sane values.
*
* @return true if this module data structure contains sane values
*/
public boolean isValid() {
MemoryBlock txtBlock = programContext.getProgram().getMemory().getBlock(".text");
if (txtBlock != null && txtBlock.getStart().getOffset() != text) {
if (txtBlock != null && !txtBlock.contains(getText())) {
return false;
}
@ -164,10 +210,21 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return true;
}
/**
* Returns a slice that contains all the function names.
*
* @return slice that contains all the function names
*/
public GoSlice getFuncnametab() {
return funcnametab;
}
/**
* Returns a list of all functions contained in this module.
*
* @return list of all functions contained in this module
* @throws IOException if error reading data
*/
public List<GoFuncData> getAllFunctionData() throws IOException {
List<GoFunctabEntry> functabentries =
getFunctabEntriesSlice().readList(GoFunctabEntry.class);
@ -178,32 +235,86 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
return result;
}
/**
* Returns the cutab slice.
*
* @return cutab slice
*/
public GoSlice getCutab() {
return cutab;
}
/**
* Returns the filetab slice.
*
* @return filetab slice
*/
public GoSlice getFiletab() {
return filetab;
}
/**
* Returns the filename at the specified offset.
*
* @param fileoff offset in the filetab of the filename
* @return filename
* @throws IOException if error reading
*/
public String getFilename(long fileoff) throws IOException {
return programContext.getReader(filetab.getElementOffset(1, fileoff)).readNextUtf8String();
}
/**
* Returns the pctab slice.
*
* @return pctab slice
*/
public GoSlice getPctab() {
return pctab;
}
/**
* Returns a reference to the controlling {@link GoRttiMapper go binary} context.
*
* @return reference to the controlling {@link GoRttiMapper go binary} context
*/
public GoRttiMapper getGoBinary() {
return programContext;
}
@Override
public StructureContext<GoModuledata> getStructureContext() {
return structureContext;
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
typeLinks.markupArray("moduledata.typeLinks", programContext.getInt32DT(), false, session);
public void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
typeLinks.markupArray("moduledata.typeLinks", null, programContext.getInt32DT(), false,
session);
typeLinks.markupElementReferences(4, getTypeList(), session);
itablinks.markupArray("moduledata.itablinks", GoItab.class, true, session);
itablinks.markupArray("moduledata.itablinks", null, GoItab.class, true, session);
markupStringTable(funcnametab.getArrayAddress(), funcnametab.getLen(), session);
markupStringTable(filetab.getArrayAddress(), filetab.getLen(), session);
GoSlice subSlice = getFunctabEntriesSlice();
subSlice.markupArray("moduledata.ftab", GoFunctabEntry.class, false, session);
subSlice.markupArray("moduledata.ftab", null, GoFunctabEntry.class, false, session);
subSlice.markupArrayElements(GoFunctabEntry.class, session);
Structure textsectDT =
programContext.getGhidraDataType("runtime.textsect", Structure.class);
if (textsectDT != null) {
textsectmap.markupArray("runtime.textsectionmap", textsectDT, false, session);
textsectmap.markupArray("runtime.textsectionmap", null, textsectDT, false, session);
}
}
/**
* Returns a list of the GoItabs present in this module.
*
* @return list of the GoItabs present in this module
* @throws IOException if error reading data
*/
@Markup
public List<GoItab> getItabs() throws IOException {
List<GoItab> result = new ArrayList<>();
@ -238,6 +349,12 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
}
}
/**
* Returns an iterator that walks all the types contained in this module
*
* @return iterator that walks all the types contained in this module
* @throws IOException if error reading data
*/
@Markup
public Iterator<GoType> iterateTypes() throws IOException {
return getTypeList().stream()
@ -253,12 +370,18 @@ public class GoModuledata implements StructureMarkup<GoModuledata> {
.iterator();
}
/**
* Returns a list of locations of the types contained in this module.
*
* @return list of addresses of GoType structures
* @throws IOException if error reading data
*/
public List<Address> getTypeList() throws IOException {
long[] typeOffsets = typeLinks.readUIntList(4 /* always sizeof(int32) */);
Address typesBaseAddr = programContext.getDataAddress(typesOffset);
List<Address> result = Arrays.stream(typeOffsets)
.mapToObj(offset -> typesBaseAddr.add(offset))
.collect(Collectors.toList());
.toList();
return result;
}

View file

@ -15,11 +15,10 @@
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.DataType;
@ -49,7 +48,7 @@ public class GoName implements StructureReader<GoName>, StructureMarkup<GoName>
private final int flagValue;
private Flag(int flagValue) {
Flag(int flagValue) {
this.flagValue = flagValue;
}
@ -80,14 +79,14 @@ public class GoName implements StructureReader<GoName>, StructureMarkup<GoName>
int flags;
@FieldOutput(isVariableLength = true)
@EOLComment("fullNameString")
@EOLComment("getFullNameString")
GoVarlenString name;
@FieldOutput(isVariableLength = true)
GoVarlenString tag;
@FieldOutput(isVariableLength = true, getter = "pkgPathDataType")
@MarkupReference("pkgPath")
@FieldOutput(isVariableLength = true, getter = "getPkgPathDataType")
@MarkupReference("getPkgPath")
long pkgPath; // uint32, nameoffset, only present if flags.HAS_PKGPATH
@Override
@ -102,34 +101,78 @@ public class GoName implements StructureReader<GoName>, StructureMarkup<GoName>
: 0;
}
/**
* Returns the name value.
*
* @return name string
*/
public String getName() {
return name.getString();
}
/**
* Returns the tag string.
*
* @return tag string
*/
public String getTag() {
return tag != null ? tag.getString() : "";
}
/**
* Returns the package path string, or null if not present.
*
* @return package path string, or null if not present
* @throws IOException if error reading data
*/
@Markup
public GoName getPkgPath() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), pkgPath);
}
/**
* Returns the data type needed to store the pkg path offset field, called by serialization
* from the fieldoutput annotation.
*
* @return Ghidra data type needed to store the pkg path offset field, or null if not present
*/
public DataType getPkgPathDataType() {
return Flag.HAS_PKGPATH.isSet(flags)
? programContext.getInt32DT()
: null;
}
public String getFullNameString() throws IOException {
GoName pkgPathName = getPkgPath();
return (pkgPathName != null ? pkgPathName.getFullNameString() + "." : "") + getName();
/**
* Returns a descriptive string containing the full name value.
*
* @return descriptive string
*/
public String getFullNameString() {
String packagePathString = "";
try {
GoName pkgPathName = getPkgPath();
packagePathString = pkgPathName != null ? pkgPathName.getFullNameString() + "." : "";
}
catch (IOException e) {
// fall thru with empty package path
}
return packagePathString + getName();
}
/**
* Returns the flags found in this structure.
*
* @return flags, as an int
*/
public int getFlags() {
return flags;
}
/**
* Returns the flags found in this structure.
*
* @return flags, as a set of {@link Flag} enum values
*/
public Set<Flag> getFlagsSet() {
return Flag.parseFlags(flags);
}
@ -144,4 +187,28 @@ public class GoName implements StructureReader<GoName>, StructureMarkup<GoName>
return getName();
}
@Override
public String toString() {
return String.format(
"GoName [context=%s, flags=%s, name=%s, tag=%s, pkgPath=%s, getFullNameString(): %s]",
context, flags, name, tag, pkgPath, getFullNameString());
}
//---------------------------------------------------------------------------------------------
/**
* Create a GoName instance that supplies a specified name.
*
* @param fakeName string name to return from the GoName's getName()
* @return new GoName instance that can only be used to call getName()
*/
public static GoName createFakeInstance(String fakeName) {
return new GoName() {
@Override
public String getName() {
return fakeName;
}
};
}
}

View file

@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.analysis;
package ghidra.app.util.bin.format.golang.rtti;
/**
* Marker interface for program-specific state information held by {@link AnalysisStateInfo}
* An index into a GoFuncData's variable-sized pcdata array. See GoFuncData's npcdata field
* for the actual array size.
*/
public interface AnalysisState {
// marker interface
public enum GoPcDataTable {
PCDATA_UnsafePoint, // 0
PCDATA_StackMapIndex, // 1
PCDATA_InlTreeIndex, // 2
PCDATA_ArgLiveIndex // 3
}

View file

@ -148,9 +148,12 @@ public class GoPcHeader {
private StructureContext<GoPcHeader> context;
@FieldMapping
@EOLComment("goVersion")
@EOLComment("getGoVersion")
private int magic;
@FieldMapping
private byte minLC;
@FieldMapping
private byte ptrSize;
@ -159,23 +162,23 @@ public class GoPcHeader {
private long textStart; // should be same as offset of ".text"
@FieldMapping
@MarkupReference("funcnameAddress")
@MarkupReference("getFuncnameAddress")
private long funcnameOffset;
@FieldMapping
@MarkupReference("cuAddress")
@MarkupReference("getCuAddress")
private long cuOffset;
@FieldMapping
@MarkupReference("filetabAddress")
@MarkupReference("getFiletabAddress")
private long filetabOffset;
@FieldMapping
@MarkupReference("pctabAddress")
@MarkupReference("getPctabAddress")
private long pctabOffset;
@FieldMapping
@MarkupReference("pclnAddress")
@MarkupReference("getPclnAddress")
private long pclnOffset;
public GoVer getGoVersion() {
@ -190,34 +193,79 @@ public class GoPcHeader {
return ver;
}
/**
* Returns true if this pcln structure contains a textStart value (only present >= 1.18)
* @return
*/
public boolean hasTextStart() {
return textStart != 0;
}
/**
* Returns the address of where the text area starts.
*
* @return address of text starts
*/
public Address getTextStart() {
return programContext.getDataAddress(textStart);
}
/**
* Returns address of the func name slice
* @return address of func name slice
*/
public Address getFuncnameAddress() {
return programContext.getDataAddress(context.getStructureStart() + funcnameOffset);
}
/**
* Returns address of the cu tab slice, used by the cuOffset field's markup annotation.
* @return address of the cu tab slice
*/
public Address getCuAddress() {
return programContext.getDataAddress(context.getStructureStart() + cuOffset);
}
/**
* Returns the address of the filetab slice, used by the filetabOffset field's markup annotation
* @return address of the filetab slice
*/
public Address getFiletabAddress() {
return programContext.getDataAddress(context.getStructureStart() + filetabOffset);
}
/**
* Returns the address of the pctab slice, used by the pctabOffset field's markup annotation
* @return address of the pctab slice
*/
public Address getPctabAddress() {
return programContext.getDataAddress(context.getStructureStart() + pctabOffset);
}
/**
* Returns the address of the pcln slice, used by the pclnOffset field's markup annotation
* @return address of the pcln slice
*/
public Address getPclnAddress() {
return programContext.getDataAddress(context.getStructureStart() + pclnOffset);
}
/**
* Returns the min lc, used as the GoPcValueEvaluator's pcquantum
* @return minLc
*/
public byte getMinLC() {
return minLC;
}
/**
* Returns the pointer size
* @return pointer size
*/
public byte getPtrSize() {
return ptrSize;
}
//--------------------------------------------------------------------------------------------
record GoVerEndian(GoVer goVer, Endian endian) {
GoVerEndian(GoVer goVer, boolean isLittleEndian) {
@ -242,3 +290,22 @@ public class GoPcHeader {
}
}
/*
struct runtime.pcHeader
Length: 40 Alignment: 4
{
uint32 magic
uint8 pad1
uint8 pad2
uint8 minLC
uint8 ptrSize
int nfunc
uint nfiles
uintptr textStart
uintptr funcnameOffset
uintptr cuOffset
uintptr filetabOffset
uintptr pctabOffset
uintptr pclnOffset
} pack()
*/

View file

@ -0,0 +1,113 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.LEB128;
/**
* Evaluates a sequence of (value_delta,pc_delta) leb128 pairs to calculate a value for a certain
* PC location.
*/
public class GoPcValueEvaluator {
private final int pcquantum;
private final long funcEntry;
private final BinaryReader reader;
private int value = -1;
private long pc;
/**
* Creates a {@link GoPcValueEvaluator} instance, tied to the specified GoFuncData, starting
* at the specified offset in the moduledata's pctab.
*
* @param func {@link GoFuncData}
* @param offset offset in moduledata's pctab
* @throws IOException if error reading pctab
*/
public GoPcValueEvaluator(GoFuncData func, long offset) throws IOException {
GoModuledata moduledata = func.getModuledata();
this.pcquantum = moduledata.getGoBinary().getMinLC();
this.reader = moduledata.getPctab().getElementReader(1, (int) offset);
this.funcEntry = func.getFuncAddress().getOffset();
this.pc = funcEntry;
}
/**
* Returns the largest PC value calculated when evaluating the result of the table's sequence.
*
* @return largest PC value encountered
* @throws IOException if error evaluating result
*/
public long getMaxPC() throws IOException {
eval(Long.MAX_VALUE);
return pc;
}
/**
* Returns the value encoded into the table at the specified pc.
*
* @param targetPC pc
* @return value at specified pc, or -1 if error evaluating table
* @throws IOException if error reading data
*/
public int eval(long targetPC) throws IOException {
while (pc <= targetPC) {
if (!step()) {
return -1;
}
}
return value;
}
/**
* Returns the set of all values for each unique pc section.
*
* @param targetPC max pc to advance the sequence to when evaluating the table
* @return list of integer values
* @throws IOException if error reading data
*/
public List<Integer> evalAll(long targetPC) throws IOException {
List<Integer> result = new ArrayList<Integer>();
while (pc <= targetPC) {
if (!step()) {
return result;
}
result.add(value);
}
return result;
}
private boolean step() throws IOException {
int uvdelta = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
if (uvdelta == 0 && pc != funcEntry) {
// a delta of 0 is only valid on the first element
return false;
}
value += -(uvdelta & 1) ^ (uvdelta >> 1);
int pcdelta = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
pc += pcdelta * pcquantum;
return true;
}
}

View file

@ -21,14 +21,26 @@ import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.BinaryReader.ReaderFunction;
import ghidra.app.util.bin.format.golang.rtti.types.GoSliceType;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.mem.Memory;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
/**
* A structure that represents a golang slice instance (similar to a java ArrayList). Not to be
* confused with a {@link GoSliceType}, which is RTTI info about a slice type.
* <p>
* An initialized static image of a slice found in a go binary will tend to have len==cap (full).
* <p>
* Like java's type erasure for generics, a golang slice instance does not have type information
* about the elements found in the array blob (nor the size of the blob).
* <p>
*/
@StructureMapping(structureName = "runtime.slice")
public class GoSlice {
public class GoSlice implements StructureMarkup<GoSlice> {
@ContextField
private GoRttiMapper programContext;
@ -37,20 +49,24 @@ public class GoSlice {
private StructureContext<GoSlice> context;
@FieldMapping
private long array;
private long array; // pointer to data
@FieldMapping
private long len;
private long len; // number of active elements
@FieldMapping
private long cap;
private long cap; // number of elements that can be stored in the array
public GoSlice() {
// emtpy
}
public GoSlice(long array, long len, long cap) {
this(array, len, cap, null);
}
/**
* Creates an artificial slice instance using the supplied values.
*
* @param array offset of the slice's data
* @param len number of initialized elements in the slice
* @param cap total number of elements in the data array
* @param programContext the go binary that contains the slice
*/
public GoSlice(long array, long len, long cap, GoRttiMapper programContext) {
this.array = array;
this.len = len;
@ -58,6 +74,27 @@ public class GoSlice {
this.programContext = programContext;
}
/**
* Returns the {@link DataType} of elements of this slice, as detected by the type information
* contained in the struct field that contains this slice.
* <p>
* Returns null if this slice instance was not nested (contained) in a structure. If the
* slice data type wasn't a specialized slice data type (it was "runtime.slice" instead of
* "[]element"), void data type will be returned.
*
* @return data type of the elements of this slice, if possible, or null
*/
public DataType getElementDataType() {
DataType dt = context != null ? context.getContainingFieldDataType() : null;
if (dt != null && dt instanceof Structure struct && struct.getNumDefinedComponents() > 0) {
int elementPtrFieldOrdinal = 0; // hacky hard coded knowledge that the pointer to the data is the first element of the slice struct
DataTypeComponent elementPtrDTC = struct.getComponent(elementPtrFieldOrdinal);
DataType elementPtrDT = elementPtrDTC.getDataType();
return elementPtrDT instanceof Pointer ptrDT ? ptrDT.getDataType() : null;
}
return null;
}
/**
* Return a artificial view of a portion of this slice's contents.
*
@ -67,13 +104,25 @@ public class GoSlice {
* @return new {@link GoSlice} instance that is limited to a portion of this slice
*/
public GoSlice getSubSlice(long startElement, long elementCount, long elementSize) {
return new GoSlice(array + (startElement * elementSize), elementCount, elementCount, programContext);
return new GoSlice(array + (startElement * elementSize), elementCount, elementCount,
programContext);
}
/**
* Returns true if this slice seems valid.
*
* @return boolean true if array blob is a valid memory location
*/
public boolean isValid() {
return array != 0 && isValid(1);
}
/**
* Returns true if this slice seems valid.
*
* @param elementSize size of elements in this slice
* @return boolean true if array blob is a valid memory location
*/
public boolean isValid(int elementSize) {
try {
Memory memory = programContext.getProgram().getMemory();
@ -86,35 +135,83 @@ public class GoSlice {
}
}
/**
* Returns address of the array blob.
*
* @return location of the array blob
*/
public long getArrayOffset() {
return array;
}
/**
* Returns the address of the array blob
* @return address of the array blob
*/
public Address getArrayAddress() {
return programContext.getDataAddress(array);
}
/**
* Returns the address of the end of the array.
*
* @param elementClass structure mapped class
* @return location of the end of the array blob
*/
public long getArrayEnd(Class<?> elementClass) {
StructureMappingInfo<?> elementSMI =
context.getDataTypeMapper().getStructureMappingInfo(elementClass);
int elementLength = elementSMI.getStructureLength();
return array + len * elementLength;
return getElementOffset(elementSMI.getStructureLength(), len);
}
/**
* Returns the number of initialized elements
*
* @return number of initialized elements
*/
public long getLen() {
return len;
}
/**
* Returns the number of elements allocated in the array blob. (capacity)
*
* @return number of allocated elements in the array blob
*/
public long getCap() {
return cap;
}
/**
* Returns true if this slice's element count is equal to the slice's capacity. This is
* typically true for all slices that are static.
*
* @return boolean true if this slice's element count is equal to capacity
*/
public boolean isFull() {
return len == cap;
}
public boolean isOffsetWithinData(long offset, int sizeofElement) {
return array <= offset && offset < array + (cap * sizeofElement);
/**
* Returns true if this slice contains the specified offset.
*
* @param offset memory offset in question
* @param sizeofElement size of elements in this slice
* @return true if this slice contains the specified offset
*/
public boolean containsOffset(long offset, int sizeofElement) {
return array <= offset && offset < getElementOffset(sizeofElement, cap);
}
/**
* Returns the offset of the specified element
*
* @param elementSize size of elements in this slice
* @param elementIndex index of desired element
* @return offset of element
*/
public long getElementOffset(long elementSize, long elementIndex) {
return array + elementSize * elementIndex;
}
/**
@ -152,7 +249,8 @@ public class GoSlice {
}
else {
// ensure that the reader func is doing correct thing
if (elementSize > 0 && reader.getPointerIndex() != array + (i + 1) * elementSize) {
if (elementSize > 0 &&
reader.getPointerIndex() != getElementOffset(elementSize, i + 1)) {
Msg.warn(this, "Bad element size when reading slice element (size: %d) at %d"
.formatted(elementSize, reader.getPointerIndex()));
elementSize = 0;
@ -174,38 +272,65 @@ public class GoSlice {
return readUIntList(reader, array, intSize, (int) len);
}
/**
* Reads an unsigned int element from this slice.
*
* @param intSize size of ints
* @param elementIndex index of element
* @return unsigned int value
* @throws IOException if error reading element
*/
public long readUIntElement(int intSize, int elementIndex) throws IOException {
return getElementReader(intSize, elementIndex).readNextUnsignedValue(intSize);
}
/**
* Returns a {@link BinaryReader} positioned at the specified slice element.
*
* @param elementSize size of elements in this slice
* @param elementIndex index of desired element
* @return {@link BinaryReader} positioned at specified element
*/
public BinaryReader getElementReader(int elementSize, int elementIndex) {
BinaryReader reader = programContext.getReader(getElementOffset(elementSize, elementIndex));
return reader;
}
/**
* Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType,
* which has elements who's type is determined by the specified structure class.
*
* @param sliceName used to label the memory location
* @param namespaceName namespace the label symbol should be placed in
* @param elementClazz structure mapped class of the element of the array
* @param ptr boolean flag, if true the element type is really a pointer to the supplied
* data type
* @param session state and methods to assist marking up the program
* @throws IOException if error
*/
public void markupArray(String sliceName, Class<?> elementClazz, boolean ptr,
MarkupSession session) throws IOException {
public void markupArray(String sliceName, String namespaceName, Class<?> elementClazz,
boolean ptr, MarkupSession session) throws IOException {
DataType dt = programContext.getStructureDataType(elementClazz);
markupArray(sliceName, dt, ptr, session);
markupArray(sliceName, namespaceName, dt, ptr, session);
}
/**
* Marks up the memory occupied by the array elements with a name and a Ghidra ArrayDataType.
*
* @param sliceName used to label the memory location
* @param namespaceName namespace the label symbol should be placed in
* @param elementType Ghidra datatype of the array elements, null ok if ptr == true
* @param ptr boolean flag, if true the element type is really a pointer to the supplied
* data type
* @param session state and methods to assist marking up the program
* @throws IOException if error
*/
public void markupArray(String sliceName, DataType elementType, boolean ptr,
MarkupSession session) throws IOException {
public void markupArray(String sliceName, String namespaceName, DataType elementType,
boolean ptr, MarkupSession session) throws IOException {
if (len == 0 || !isValid()) {
return;
}
DataTypeManager dtm = programContext.getDTM();
if (ptr) {
elementType = new PointerDataType(elementType, programContext.getPtrSize(), dtm);
@ -215,7 +340,7 @@ public class GoSlice {
Address addr = programContext.getDataAddress(array);
session.markupAddress(addr, arrayDT);
if (sliceName != null) {
session.labelAddress(addr, sliceName);
session.labelAddress(addr, sliceName, namespaceName);
}
}
@ -227,9 +352,10 @@ public class GoSlice {
* @param session state and methods to assist marking up the program
* @return list of element instances
* @throws IOException if error reading
* @throws CancelledException if cancelled
*/
public <T> List<T> markupArrayElements(Class<T> clazz, MarkupSession session)
throws IOException {
throws IOException, CancelledException {
if (len == 0) {
return List.of();
}
@ -272,4 +398,13 @@ public class GoSlice {
return result;
}
@Override
public String getStructureLabel() throws IOException {
return "slice[%d]_%s".formatted(len, context.getStructureAddress());
}
@Override
public StructureContext<GoSlice> getStructureContext() {
return context;
}
}

View file

@ -0,0 +1,57 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
/**
* Represents a golang source file and line number tuple.
*
* @param fileName source filename
* @param lineNum source line number
*/
public record GoSourceFileInfo(String fileName, int lineNum) {
public GoSourceFileInfo(String fileName, int lineNum) {
this.fileName = fileName;
this.lineNum = lineNum;
}
public String getFileName() {
return fileName;
}
public int getLineNum() {
return lineNum;
}
/**
* Returns the source location info as a string formatted as "filename:linenum"
*
* @return "filename:linenum"
*/
public String getDescription() {
return "%s:%d".formatted(fileName, lineNum);
}
/**
* Returns the source location info as a string formatted as "File: filename Line: linenum"
*
* @return "File: filename Line: linenum"
*/
public String getVerboseDescription() {
return "File: %s Line: %d".formatted(fileName, lineNum);
}
}

View file

@ -16,35 +16,221 @@
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.util.function.Predicate;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf4.next.DWARFDataInstanceHelper;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
/**
* A structure that represents a golang string instance.
*/
@StructureMapping(structureName = "string")
public class GoString {
public class GoString implements StructureMarkup<GoString> {
public static final int MAX_SANE_STR_LEN = 1024 * 1024; // 1mb
/**
* Creates a artificial gostring instance that was not read from a memory location.
* <p>
* @param goBinary {@link GoRttiMapper}
* @param stringData location of char array
* @param len length of char array
* @return new GoString instance
*/
public static GoString createInlineString(GoRttiMapper goBinary, Address stringData, long len) {
GoString result = new GoString();
result.context = goBinary.createArtificialStructureContext(GoString.class);
result.str = stringData.getOffset();
result.len = len;
return result;
}
@ContextField
private StructureContext<GoString> context;
@FieldMapping
@MarkupReference("stringAddr")
@EOLComment("stringValue")
@MarkupReference("getStringAddr")
@EOLComment("getStringValue")
private long str;
@FieldMapping
private long len;
/**
* Returns the address of the char data, referenced via the str field's markup annotation
* @return address of the char data
*/
public Address getStringAddr() {
return context.getDataTypeMapper().getDataAddress(str);
}
/**
* Returns an AddressRange that encompasses the string char data.
*
* @return AddressRange that encompasses the string char data
*/
public AddressRange getStringDataRange() {
if (len <= 0) {
return null;
}
Address charStart = context.getDataTypeMapper().getDataAddress(str);
Address charEnd = context.getDataTypeMapper().getDataAddress(str + len - 1);
return new AddressRangeImpl(charStart, charEnd);
}
/**
* Returns the length of the string data
*
* @return length of the string data
*/
public long getLength() {
return len;
}
/**
* Returns the string value.
*
* @return string value
* @throws IOException if error reading char data
*/
public String getStringValue() throws IOException {
BinaryReader reader = context.getDataTypeMapper().getReader(str);
return reader.readNextUtf8String((int) len);
}
private DataType getStringCharDataType() {
// use char as the element data type because it renders better, even though
// it causes a type-cast to uint8* in the decompiler
return new ArrayDataType(CharDataType.dataType, (int) len, -1,
context.getDataTypeMapper().getDTM());
}
/**
* Returns true if this string instance is valid and probably contains a go string.
*
* @param charValidRange addresses that are valid locations for a string's char[] data
* @param stringContentValidator a callback that will test a recovered string for validity
* @return boolean true if valid string, false if not valid string
* @throws IOException if error reading data
*/
public boolean isValid(AddressSetView charValidRange, Predicate<String> stringContentValidator)
throws IOException {
if (len <= 0 || len > MAX_SANE_STR_LEN) {
return false;
}
Address structStartAddr = context.getStructureAddress();
AddressRange charDataRange = getStringDataRange();
if (charDataRange == null || !charValidRange.contains(charDataRange.getMinAddress(),
charDataRange.getMaxAddress())) {
return false;
}
DWARFDataInstanceHelper dihUtil =
new DWARFDataInstanceHelper(context.getDataTypeMapper().getProgram());
if (!dihUtil.isDataTypeCompatibleWithAddress(context.getStructureDataType(),
structStartAddr)) {
return false;
}
long maxValidLen =
charValidRange.getMaxAddress().subtract(charDataRange.getMinAddress()) - 1;
if (len > maxValidLen) {
return false;
}
if (!isCompatibleCharDataType(charDataRange.getMinAddress())) {
return false;
}
if (hasOffcutReferences(charDataRange)) {
return false;
}
String stringValue = getStringValue();
if (!stringContentValidator.test(stringValue)) {
return false;
}
return true;
}
private boolean hasOffcutReferences(AddressRange charDataRange) {
AddressIterator it = context.getDataTypeMapper()
.getProgram()
.getReferenceManager()
.getReferenceDestinationIterator(new AddressSet(charDataRange), true);
Address refAddr = it.hasNext() ? it.next() : null;
if (refAddr != null && refAddr.equals(charDataRange.getMinAddress())) {
refAddr = it.hasNext() ? it.next() : null;
}
return refAddr != null;
}
private boolean isCompatibleCharDataType(Address charDataAddr) {
DataType stringCharDataType = getStringCharDataType();
DWARFDataInstanceHelper dihUtil =
new DWARFDataInstanceHelper(context.getDataTypeMapper().getProgram());
return dihUtil.isDataTypeCompatibleWithAddress(stringCharDataType, charDataAddr);
}
/**
* Returns true if this string instance points to valid char[] data.
*
* @param charValidRange addresses that are valid locations for a string's char[] data
* @param stringContentValidator a callback that will test a recovered string for validity
* @return boolean true if valid string, false if not valid string
* @throws IOException if error reading data
*/
public boolean isValidInlineString(AddressSetView charValidRange,
Predicate<String> stringContentValidator) throws IOException {
if (len <= 0 || len > MAX_SANE_STR_LEN) {
return false;
}
Address charStart = getStringAddr();
try {
Address charEnd = charStart.addNoWrap(len - 1);
if (!charValidRange.contains(charStart) || !charValidRange.contains(charEnd)) {
// TODO: maybe change check to ensure both ends of char array are in same contiguous block
return false;
}
}
catch (AddressOverflowException e) {
return false;
}
long maxValidLen = charValidRange.getMaxAddress().subtract(charStart) - 1;
if (len > maxValidLen) {
return false;
}
if (!isCompatibleCharDataType(charStart)) {
return false;
}
String stringValue = getStringValue();
if (!stringContentValidator.test(stringValue)) {
return false;
}
return true;
}
@Override
public String getStructureLabel() throws IOException {
return StringDataInstance.makeStringLabel("gostr_", getStringValue(),
DataTypeDisplayOptions.DEFAULT);
}
@Override
public StructureContext<GoString> getStructureContext() {
return context;
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
session.markupAddress(getStringAddr(), getStringCharDataType());
}
}

View file

@ -0,0 +1,207 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
/**
* Represents a Golang symbol name.
*
* @param symbolName full name of the golang symbol
* @param packagePath portion the symbol name that is the packagePath (path+packagename), or null
* @param packageName portion of the symbol name that is the package name, or null
* @param receiverString portion of the symbol name that is the receiver string (only found when
* the receiver is in the form of "(*typename)"), or null
*/
public record GoSymbolName(String symbolName, String packagePath, String packageName,
String receiverString) {
/**
* Fixes the specified string if it contains any of the golang special symbolname characters:
* middle-dot and the weird slash.
*
* @param s string to fix
* @return original string, or fixed version
*/
public static String fixGolangSpecialSymbolnameChars(String s) {
// "\u00B7" -> "."
// "\u2215" -> "/"
if (s.contains("\u00B7") || s.contains("\u2215")) {
s = s.replaceAll("\u00B7", ".").replaceAll("\u2215", "/");
}
return s;
}
/**
* Matches golang symbol strings such as:
* "package/domain.name/packagename.(*ReceiverTypeName).Functionname"
* or
* "type:.eq.[39]package/domain.name/packagename.Functionname"
*/
private static final Pattern SYMBOL_INFO_PATTERN = Pattern.compile(
// "type:" ".eq." or ".hash.", optional slice "[numbers_or_dots]",
"^(type:\\.(eq|hash)\\.(\\[[0-9.]*\\])?)?" +
// package_path/package_name.(*optional_receiverstring)remainder_of_symbol_string
"(([-+_/.a-zA-Z0-9]+)(\\(\\*[^)]+\\))?.*)");
/**
* Parses a golang symbol string and returns a GoSymbolName instance.
*
* @param s string to parse
* @return new GoSymbolName instance, never null
*/
public static GoSymbolName parse(String s) {
s = fixGolangSpecialSymbolnameChars(s);
Matcher m = SYMBOL_INFO_PATTERN.matcher(s);
if (s.startsWith("go:") || !m.matches()) {
return new GoSymbolName(s);
}
String packageString = m.group(5);
String receiverString = m.group(6);
int packageNameStart = packageString.lastIndexOf('/') + 1;
int firstDot = packageString.indexOf('.', packageNameStart);
if (firstDot <= 0) {
return new GoSymbolName(s);
}
String packagePath = packageString.substring(0, firstDot);
String packageName = packageString.substring(packageNameStart, firstDot);
if (receiverString != null && !receiverString.isEmpty()) {
receiverString = receiverString.substring(1, receiverString.length() - 1);
}
return new GoSymbolName(s, packagePath, packageName, receiverString);
}
/**
* Constructs a minimal GoSymbolName instance from the supplied values.
*
* @param packageName package name, does not handle package paths, eg. "runtime"
* @param symbolName full symbol name, eg. "runtime.foo"
* @return new GoSymbolName instance
*/
public static GoSymbolName from(String packageName, String symbolName) {
return new GoSymbolName(symbolName, packageName, packageName, null);
}
/**
* Constructs a GoSymbolName instance that only has a package path / package name.
*
* @param packagePath package path to parse
* @return GoSymbolName that only has a package path and package name value
*/
public static GoSymbolName fromPackagePath(String packagePath) {
GoSymbolName tmp = parse(packagePath + ".TMP");
return new GoSymbolName(null, tmp.getPackagePath(), tmp.getPackageName(), null);
}
private GoSymbolName(String symbolName) {
this(symbolName, null, null, null);
}
/**
* Returns the portion the symbol name that is the packagePath (path+packagename), or null
* @return the portion the symbol name that is the packagePath (path+packagename), or null
*/
public String getPackagePath() {
return packagePath;
}
/**
* Returns portion of the symbol name that is the package name, or null
* @return portion of the symbol name that is the package name, or null
*/
public String getPackageName() {
return packageName;
}
/**
* Returns portion of the symbol name that is the receiver string (only found when
* the receiver is in the form of "(*typename)"), or null
* @return portion of the symbol name that is the receiver string (only found when
* the receiver is in the form of "(*typename)"), or null
*/
public String getRecieverString() {
return receiverString;
}
/**
* Returns the full name of the golang symbol
* @return full name of the golang symbol
*/
public String getSymbolName() {
return symbolName;
}
/**
* Returns the portion of the package path before the package name, eg. "internal/sys" would
* become "internal/".
*
* @return package path, without the trailing package name, or empty string if there is no path
* portion of the string
*/
public String getTruncatedPackagePath() {
return packagePath != null && packageName != null &&
packagePath.length() > packageName.length()
? packagePath.substring(0, packagePath.length() - packageName.length())
: null;
}
/**
* Returns a Ghidra {@link Namespace} based on the golang package path.
*
* @param program {@link Program} that will contain the namespace
* @return {@link Namespace} cooresponding to the golang package path, or the program's root
* namespace if no package path information is present
*/
public Namespace getSymbolNamespace(Program program) {
Namespace rootNS = program.getGlobalNamespace();
if (packagePath != null && !packagePath.isBlank()) {
try {
return program.getSymbolTable()
.getOrCreateNameSpace(rootNS, packagePath, SourceType.IMPORTED);
}
catch (DuplicateNameException | InvalidInputException e) {
// ignore, fall thru
}
}
return rootNS;
}
/**
* Returns the matching Ghidra function (based on namespace and symbol name).
*
* @param program {@link Program} containing the function
* @return Ghidra {@link Function}
*/
public Function getFunction(Program program) {
Namespace ns = getSymbolNamespace(program);
Symbol sym = SymbolUtilities.getUniqueSymbol(program, getSymbolName(), ns);
Function func = sym instanceof FunctionSymbol ? (Function) sym.getObject() : null;
return func;
}
}

View file

@ -17,6 +17,7 @@ package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.golang.structmapping.*;
@ -57,30 +58,66 @@ public class GoVarlenString implements StructureReader<GoVarlenString> {
this.bytes = reader.readNextByteArray(strLen);
}
/**
* Returns the string's length
*
* @return string's length
*/
public int getStrlen() {
return bytes.length;
}
/**
* Returns the string length's length (length of the leb128 number)
*
* @return string length's length
*/
public int getStrlenLen() {
return strlenLen;
}
/**
* Returns the raw bytes of the string
*
* @return raw bytes of the string
*/
public byte[] getBytes() {
return bytes;
}
/**
* Returns the string value.
*
* @return string value
*/
public String getString() {
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* Returns the data type that is needed to hold the string length field.
*
* @return data type needed to hold the string length field
*/
public DataTypeInstance getStrlenDataType() {
return DataTypeInstance.getDataTypeInstance(UnsignedLeb128DataType.dataType, strlenLen,
false);
}
/**
* Returns the data type that holds the raw string value.
*
* @return data type that holds the raw string value.
*/
public DataType getValueDataType() {
return new ArrayDataType(CharDataType.dataType, bytes.length, -1,
context.getDataTypeMapper().getDTM());
}
@Override
public String toString() {
return String.format("GoVarlenString [context=%s, strlenLen=%s, bytes=%s, getString()=%s]",
context, strlenLen, Arrays.toString(bytes), getString());
}
}

View file

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
import java.io.IOException;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.FunctionDefinition;
/**
* Abstract base for information about type methods and interface methods
*/
public abstract class MethodInfo {
final Address address;
public MethodInfo(Address address) {
this.address = address;
}
/**
* Entry point of the method
*
* @return {@link Address}
*/
public Address getAddress() {
return address;
}
/**
* Function signature of the method.
*
* @return {@link FunctionDefinition}
* @throws IOException if error reading method information
*/
abstract public FunctionDefinition getSignature() throws IOException;
}

View file

@ -15,34 +15,51 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
/**
* {@link GoType} structure that defines an array.
*/
@StructureMapping(structureName = "runtime.arraytype")
public class GoArrayType extends GoType {
@FieldMapping
@MarkupReference("getElement")
private long elem; // pointer to element type
@FieldMapping
@MarkupReference("getSliceType")
private long slice; // pointer to slice type
@FieldMapping
private long len;
public GoArrayType() {
// empty
}
/**
* Returns a reference to the {@link GoType} of the elements of this array.
*
* @return reference to the {@link GoType} of the elements of this array
* @throws IOException if error reading data
*/
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
/**
* Returns a reference to the {@link GoType} that defines the slice version of this array.
* @return reference to the {@link GoType} that defines the slice version of this array
* @throws IOException if error reading data
*/
@Markup
public GoType getSliceType() throws IOException {
return programContext.getGoType(slice);
@ -74,4 +91,30 @@ public class GoArrayType extends GoType {
return true;
}
@Override
public String getStructureNamespace() throws IOException {
String packagePath = getPackagePathString();
if (packagePath != null && !packagePath.isEmpty()) {
return packagePath;
}
GoType elementType = getElement();
if (elementType != null) {
return elementType.getStructureNamespace();
}
return super.getStructureNamespace();
}
@Override
protected String getTypeDeclString() throws IOException {
// type CustomArraytype [elementcount]elementType
String selfName = typ.getName();
String elemName = programContext.getGoTypeName(elem);
String arrayDefStr = "[%d]%s".formatted(len, elemName);
String defStrWithLinks = "[%d]%s".formatted(len,
AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName));
boolean hasName = !arrayDefStr.equals(selfName);
return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks);
}
}

View file

@ -15,9 +15,8 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.util.Set;
import java.io.IOException;
import java.util.Set;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
@ -29,7 +28,7 @@ import ghidra.app.util.bin.format.golang.structmapping.*;
* The in-memory instance will typically be part of a specialized type structure, depending
* on the 'kind' of this type.
* <p>
* Additionally, there will be an GoUncommonType structure immediately after this type, if
* Additionally, there can be an {@link GoUncommonType} structure immediately after this type, if
* the uncommon bit is set in tflag.
* <p>
* <pre>
@ -60,43 +59,85 @@ public class GoBaseType {
private int kind;
@FieldMapping
@MarkupReference("name")
@MarkupReference("getGoName")
private long str; // an offset relative to containing moduledata's type base addr
@FieldMapping
@MarkupReference
private long ptrToThis; // an offset relative to containing moduledata's type base addr
/**
* Returns the size of the type being defined by this structure.
*
* @return size of the type being defined
*/
public long getSize() {
return size;
}
/**
* Returns the {@link GoKind} enum assigned to this type definition.
*
* @return {@link GoKind} enum assigned to this type definition
*/
public GoKind getKind() {
return GoKind.parseByte(kind);
}
/**
* Returns the {@link GoTypeFlag}s assigned to this type definition.
* @return {@link GoTypeFlag}s assigned to this type definition
*/
public Set<GoTypeFlag> getFlags() {
return GoTypeFlag.parseFlags(tflag);
}
/**
* Returns the raw flag value.
*
* @return raw flag value
*/
public int getTflag() {
return tflag;
}
/**
* Returns true if this type definition's flags indicate there is a following GoUncommon
* structure.
*
* @return true if this type definition's flags indicate there is a following GoUncommon struct
*/
public boolean hasUncommonType() {
return GoTypeFlag.Uncommon.isSet(tflag);
}
/**
* Returns the name of this type.
*
* @return name of this type, as a {@link GoName}
* @throws IOException if error reading data
*/
@Markup
public GoName getName() throws IOException {
public GoName getGoName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), str);
}
public String getNameString() throws IOException {
String s = getName().getName();
return GoTypeFlag.ExtraStar.isSet(tflag) ? s.substring(1) : s;
/**
* Returns the name of this type.
*
* @return String name of this type
*/
public String getName() {
String s = programContext.getSafeName(this::getGoName, this, "").getName();
return GoTypeFlag.ExtraStar.isSet(tflag) && s.startsWith("*") ? s.substring(1) : s;
}
/**
* Returns a reference to the {@link GoType} that represents a pointer to this type.
*
* @return reference to the {@link GoType} that represents a pointer to this type
* @throws IOException if error reading
*/
@Markup
public GoType getPtrToThis() throws IOException {
return programContext.resolveTypeOff(context.getStructureStart(), ptrToThis);

View file

@ -15,27 +15,35 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.util.Set;
import java.io.IOException;
import java.util.Set;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
/**
* A {@link GoType} structure that defines a go channel
*/
@StructureMapping(structureName = "runtime.chantype")
public class GoChanType extends GoType {
@FieldMapping
@MarkupReference("element")
@MarkupReference("getElement")
private long elem;
@FieldMapping
private long dir;
public GoChanType() {
// empty
}
/**
* Returns a reference to the {@link GoType} that defines the elements this channel uses
* @return reference to the {@link GoType} that defines the elements this channel uses
* @throws IOException if error reading type
*/
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
@ -50,13 +58,14 @@ public class GoChanType extends GoType {
return programContext.getDTM().getPointer(null);
}
DataType chanDT = chanGoType.recoverDataType();
DataType chanDT = programContext.getRecoveredType(chanGoType);
Pointer ptrChanDt = programContext.getDTM().getPointer(chanDT);
if (typ.getSize() != ptrChanDt.getLength()) {
Msg.warn(this, "Size mismatch between chan type and recovered type");
}
TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(),
getStructureName(), ptrChanDt, programContext.getDTM());
TypedefDataType typedef =
new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
getUniqueTypename(), ptrChanDt, programContext.getDTM());
return typedef;
}

View file

@ -17,7 +17,6 @@ package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.bin.format.golang.GoFunctionMultiReturn;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
@ -25,9 +24,24 @@ import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
/**
* A {@link GoType} structure that defines a function type.
*/
@StructureMapping(structureName = "runtime.functype")
public class GoFuncType extends GoType {
/**
* Converts a ptr-to-ptr-to-funcdef to the base funcdef type.
*
* @param dt ghidra {@link DataType}
* @return {@link FunctionDefinition} that was pointed to by specified data type, or null
*/
public static FunctionDefinition unwrapFunctionDefinitionPtrs(DataType dt) {
return dt != null && dt instanceof Pointer ptrDT &&
ptrDT.getDataType() instanceof Pointer ptrptrDT &&
ptrptrDT.getDataType() instanceof FunctionDefinition funcDef ? funcDef : null;
}
@FieldMapping
private int inCount; // uint16
@ -35,30 +49,47 @@ public class GoFuncType extends GoType {
private int outCount; // uint16
public GoFuncType() {
// empty
}
/**
* Returns true if this function type is defined to be vararg
* @return true if this function type is defined to be vararg
*/
public boolean isVarArg() {
return (outCount & 0x8000) != 0;
}
/**
* Returns the number of inbound parameters
* @return number of inbound parameters
*/
public int getInCount() {
return inCount;
}
/**
* Returns the number of outbound result values
* @return number of outbound result values
*/
public int getOutCount() {
return outCount & 0x7fff;
}
/**
* Returns the total number of in and out parameters
* @return total number of in and out parameters
*/
public int getParamCount() {
return inCount + (outCount & 0x7fff);
}
public List<Address> getParamTypeAddrs() throws IOException {
private List<Address> getParamTypeAddrs() throws IOException {
GoSlice slice = getParamListSlice();
long[] typeOffsets = slice.readUIntList(programContext.getPtrSize());
return Arrays.stream(typeOffsets)
.mapToObj(programContext::getDataAddress)
.collect(Collectors.toList());
.toList();
}
private GoSlice getParamListSlice() {
@ -66,6 +97,11 @@ public class GoFuncType extends GoType {
return new GoSlice(getOffsetEndOfFullType(), count, count, programContext);
}
/**
* Returns a list of {@link GoType}s for each parameter
* @return list of {@link GoType}s for each parameter
* @throws IOException if error read type info
*/
@Markup
public List<GoType> getParamTypes() throws IOException {
return getParamTypeAddrs().stream()
@ -77,18 +113,31 @@ public class GoFuncType extends GoType {
return null;
}
})
.collect(Collectors.toList());
.toList();
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
GoSlice slice = getParamListSlice();
slice.markupArray(getStructureLabel() + "_paramlist", GoBaseType.class, true, session);
slice.markupArray(getStructureLabel() + "_paramlist", getStructureNamespace(),
GoBaseType.class, true, session);
}
public String getFuncPrototypeString(String funcName) throws IOException {
/**
* Returns a string that describes the function type as a golang-ish function decl.
*
* @param funcName optional name of a function
* @param receiverString optional receiver decl string
* @return golang func decl string
* @throws IOException if error reading parameter type info
*/
public String getFuncPrototypeString(String funcName, String receiverString)
throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("func");
if (receiverString != null && !receiverString.isBlank()) {
sb.append(" (").append(receiverString).append(")");
}
if (funcName != null && !funcName.isBlank()) {
sb.append(" ").append(funcName);
}
@ -102,7 +151,7 @@ public class GoFuncType extends GoType {
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getNameString());
sb.append(paramType.getName());
}
sb.append(")");
if (!outParamTypes.isEmpty()) {
@ -112,7 +161,7 @@ public class GoFuncType extends GoType {
if (i != 0) {
sb.append(", ");
}
sb.append(paramType.getNameString());
sb.append(paramType.getName());
}
sb.append(")");
}
@ -121,13 +170,15 @@ public class GoFuncType extends GoType {
@Override
public DataType recoverDataType() throws IOException {
String name = typ.getNameString();
String name = getUniqueTypename();
DataTypeManager dtm = programContext.getDTM();
FunctionDefinitionDataType funcDef =
new FunctionDefinitionDataType(programContext.getRecoveredTypesCp(), name, dtm);
FunctionDefinitionDataType funcDef = new FunctionDefinitionDataType(
programContext.getRecoveredTypesCp(getPackagePathString()), name, dtm);
Pointer funcDefPtr = dtm.getPointer(funcDef);
programContext.cacheRecoveredDataType(this, funcDefPtr);
Pointer funcDefPtrPtr = dtm.getPointer(funcDefPtr);
// pre-push empty funcdef type into cache to prevent endless recursive loops
programContext.cacheRecoveredDataType(this, funcDefPtrPtr);
List<GoType> paramTypes = getParamTypes();
List<GoType> inParamTypes = paramTypes.subList(0, inCount);
@ -136,7 +187,7 @@ public class GoFuncType extends GoType {
List<ParameterDefinition> params = new ArrayList<>();
for (int i = 0; i < inParamTypes.size(); i++) {
GoType paramType = inParamTypes.get(i);
DataType paramDT = paramType.recoverDataType();
DataType paramDT = programContext.getRecoveredType(paramType);
params.add(new ParameterDefinitionImpl(null, paramDT, null));
}
DataType returnDT;
@ -144,25 +195,26 @@ public class GoFuncType extends GoType {
returnDT = VoidDataType.dataType;
}
else if (outParamTypes.size() == 1) {
returnDT = outParamTypes.get(0).recoverDataType();
returnDT = programContext.getRecoveredType(outParamTypes.get(0));
}
else {
List<DataType> paramDataTypes = recoverTypes(outParamTypes);
GoFunctionMultiReturn multiReturn = new GoFunctionMultiReturn(
programContext.getRecoveredTypesCp(), name, paramDataTypes, dtm, null);
programContext.getRecoveredTypesCp(getPackagePathString()), name, paramDataTypes,
dtm, null);
returnDT = multiReturn.getStruct();
}
funcDef.setArguments(params.toArray(ParameterDefinition[]::new));
funcDef.setReturnType(returnDT);
return funcDefPtr;
return funcDefPtrPtr;
}
private List<DataType> recoverTypes(List<GoType> types) throws IOException {
List<DataType> result = new ArrayList<>();
for (GoType type : types) {
result.add(type.recoverDataType());
result.add(programContext.getRecoveredType(type));
}
return result;
}
@ -181,13 +233,8 @@ public class GoFuncType extends GoType {
}
@Override
public String toString() {
try {
return getFuncPrototypeString(null);
}
catch (IOException e) {
return super.toString();
}
protected String getTypeDeclString() throws IOException {
return getFuncPrototypeString(null, null);
}
}

View file

@ -17,9 +17,10 @@ package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.FunctionDefinition;
@StructureMapping(structureName = "runtime.imethod")
public class GoIMethod implements StructureMarkup<GoIMethod> {
@ -31,22 +32,22 @@ public class GoIMethod implements StructureMarkup<GoIMethod> {
private StructureContext<GoIMethod> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
@MarkupReference("getGoName")
@EOLComment("getName")
private long name;
@FieldMapping
@MarkupReference("type")
@MarkupReference("getType")
private long ityp;
@Markup
public GoName getName() throws IOException {
public GoName getGoName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), name);
}
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : "_blank_";
public String getName() {
GoName n = programContext.getSafeName(this::getGoName, this, "unnamed_imethod");
return n.getName();
}
@Markup
@ -61,9 +62,38 @@ public class GoIMethod implements StructureMarkup<GoIMethod> {
@Override
public String getStructureName() throws IOException {
return getNameString();
return getName();
}
@Override
public String toString() {
return String.format("GoIMethod [getName()=%s, getStructureContext()=%s]", getName(),
getStructureContext());
}
public static class GoIMethodInfo extends MethodInfo {
GoItab itab;
GoIMethod imethod;
public GoIMethodInfo(GoItab itab, GoIMethod imethod, Address address) {
super(address);
this.itab = itab;
this.imethod = imethod;
}
public GoItab getItab() {
return itab;
}
public GoIMethod getImethod() {
return imethod;
}
@Override
public FunctionDefinition getSignature() throws IOException {
return itab.getSignatureFor(imethod);
}
}
}
/*
struct runtime.imethod // Length: 8 Alignment: 4

View file

@ -23,41 +23,57 @@ import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.TypedefDataType;
import ghidra.util.exception.CancelledException;
/**
* A {@link GoType} structure that defines a golang interface.
*/
@StructureMapping(structureName = "runtime.interfacetype")
public class GoInterfaceType extends GoType {
@FieldMapping
@MarkupReference("pkgPath")
@MarkupReference("getPkgPath")
private long pkgpath; // pointer to name
@FieldMapping
private GoSlice mhdr;
public GoInterfaceType() {
// empty
}
/**
* Returns the package path of this type, referenced via the pkgpath field's markup annotation
*
* @return package path {@link GoName}a
* @throws IOException if error reading
*/
@Markup
public GoName getPkgPath() throws IOException {
return programContext.getGoName(pkgpath);
}
public String getPkgPathString() throws IOException {
GoName n = getPkgPath();
return n != null ? n.getName() : "";
}
/**
* Returns a slice containing the methods of this interface.
*
* @return slice containing the methods of this interface
*/
public GoSlice getMethodsSlice() {
return mhdr;
}
/**
* Returns the methods defined by this interface
* @return methods defined by this interface
* @throws IOException if error reading data
*/
public List<GoIMethod> getMethods() throws IOException {
return mhdr.readList(GoIMethod.class);
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
mhdr.markupArray(null, GoIMethod.class, false, session);
public void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
mhdr.markupArray(null, getStructureNamespace(), GoIMethod.class, false, session);
mhdr.markupArrayElements(GoIMethod.class, session);
}
@ -65,10 +81,10 @@ public class GoInterfaceType extends GoType {
public DataType recoverDataType() throws IOException {
DataType dt = programContext.getStructureDataType(GoIface.class);
String name = typ.getNameString();
String name = getUniqueTypename();
if (!dt.getName().equals(name)) {
dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt,
programContext.getDTM());
dt = new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
name, dt, programContext.getDTM());
}
return dt;
}
@ -76,14 +92,15 @@ public class GoInterfaceType extends GoType {
@Override
public String getMethodListString() throws IOException {
StringBuilder sb = new StringBuilder();
String ifaceName = getNameWithPackageString();
for (GoIMethod imethod : getMethods()) {
if (!sb.isEmpty()) {
sb.append("\n");
}
String methodStr = imethod.getNameString();
String methodStr = imethod.getName();
GoType type = imethod.getType();
if (type instanceof GoFuncType funcType) {
methodStr = funcType.getFuncPrototypeString(methodStr);
methodStr = funcType.getFuncPrototypeString(methodStr, ifaceName);
}
else {
methodStr = "func %s()".formatted(methodStr);

View file

@ -15,6 +15,9 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
/**
* Enum defining the various golang primitive types
*/
public enum GoKind {
invalid,
Bool, // 1

View file

@ -20,6 +20,7 @@ import java.util.Set;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
@ -33,12 +34,15 @@ import ghidra.util.Msg;
public class GoMapType extends GoType {
@FieldMapping
@MarkupReference("getKey")
private long key; // ptr to type
@FieldMapping
@MarkupReference("getElement")
private long elem; // ptr to type
@FieldMapping
@MarkupReference("getBucket")
private long bucket; // ptr to type
@FieldMapping
@ -60,16 +64,34 @@ public class GoMapType extends GoType {
// empty
}
/**
* Returns the GoType that defines the map's key, referenced by the key field's markup annotation
*
* @return GoType that defines the map's key
* @throws IOException if error reading data
*/
@Markup
public GoType getKey() throws IOException {
return programContext.getGoType(key);
}
/**
* Returns the GoType that defines the map's element, referenced by the element field's markup annotation
*
* @return GoType that defines the map's element
* @throws IOException if error reading data
*/
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
}
/**
* Returns the GoType that defines the map's bucket, referenced by the bucket field's markup annotation
*
* @return GoType that defines the map's bucket
* @throws IOException if error reading data
*/
@Markup
public GoType getBucket() throws IOException {
return programContext.getGoType(bucket);
@ -83,13 +105,14 @@ public class GoMapType extends GoType {
// a void*
return programContext.getDTM().getPointer(null);
}
DataType mapDT = mapGoType.recoverDataType();
DataType mapDT = programContext.getRecoveredType(mapGoType);
Pointer ptrMapDt = programContext.getDTM().getPointer(mapDT);
if (typ.getSize() != ptrMapDt.getLength()) {
Msg.warn(this, "Size mismatch between map type and recovered type");
}
TypedefDataType typedef = new TypedefDataType(programContext.getRecoveredTypesCp(),
getStructureName(), ptrMapDt, programContext.getDTM());
TypedefDataType typedef =
new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
getUniqueTypename(), ptrMapDt, programContext.getDTM());
return typedef;
}
@ -112,4 +135,20 @@ public class GoMapType extends GoType {
}
return true;
}
@Override
protected String getTypeDeclString() throws IOException {
// type CustomMaptype map[keykey]valuetype
String selfName = typ.getName();
String keyName = programContext.getGoTypeName(key);
String elemName = programContext.getGoTypeName(elem);
String defStr = "map[%s]%s".formatted(keyName, elemName);
String defStrWithLinks = "map[%s]%s".formatted(
AddressAnnotatedStringHandler.createAddressAnnotationString(key, keyName),
AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName));
boolean hasName = !defStr.equals(selfName);
return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks);
}
}

View file

@ -16,12 +16,18 @@
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.util.NumericUtilities;
/**
* Structure that defines a method for a GoType, found in the type's {@link GoUncommonType} struct.
*/
@StructureMapping(structureName = "runtime.method")
public class GoMethod implements StructureMarkup<GoMethod> {
@ContextField
@ -31,32 +37,58 @@ public class GoMethod implements StructureMarkup<GoMethod> {
private StructureContext<GoMethod> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
@MarkupReference("getGoName")
@EOLComment("getName")
private long name; // nameOff
@FieldMapping
@MarkupReference("type")
private long mtyp; // typeOff
@MarkupReference("getType")
private long mtyp; // typeOff - function definition
@FieldMapping
@MarkupReference
private long ifn; // textOff
private long ifn; // textOff, address of version of method called via the interface
@FieldMapping
@MarkupReference
private long tfn; // textOff
private long tfn; // textOff, address of version of method called normally
/**
* Returns the name of this method.
*
* @return name of this method as a raw GoName value
* @throws IOException if error reading
*/
@Markup
public GoName getName() throws IOException {
public GoName getGoName() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), name);
}
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : "_blank_";
/**
* Returns the name of this method.
*
* @return name of this method
*/
public String getName() {
GoName n = programContext.getSafeName(this::getGoName, this, "unnamed_method");
return n.getName();
}
/**
* Returns true if the funcdef is missing for this method.
*
* @return true if the funcdef is missing for this method
*/
public boolean isSignatureMissing() {
return mtyp == 0 || mtyp == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG || mtyp == -1;
}
/**
* Return the {@link GoType} that defines the funcdef / func signature.
*
* @return {@link GoType} that defines the funcdef / func signature
* @throws IOException if error reading data
*/
@Markup
public GoType getType() throws IOException {
return programContext.resolveTypeOff(context.getStructureStart(), mtyp);
@ -68,16 +100,90 @@ public class GoMethod implements StructureMarkup<GoMethod> {
}
@Override
public String getStructureName() throws IOException {
return getNameString();
public String getStructureName() {
return getName();
}
/**
* Returns the address of the version of the function that is called via the interface.
*
* @return address of the version of the function that is called via the interface
*/
public Address getIfn() {
return programContext.resolveTextOff(context.getStructureStart(), ifn);
}
/**
* Returns the address of the version of the function that is called normally.
*
* @return address of the version of the function that is called normally
*/
public Address getTfn() {
return programContext.resolveTextOff(context.getStructureStart(), tfn);
}
/**
* Returns a list of {@link GoMethodInfo}s containing the ifn and tfn values (if present).
*
* @param containingType {@link GoType} that contains this method
* @return list of {@link GoMethodInfo} instances representing the ifn and tfn values if present
*/
public List<GoMethodInfo> getMethodInfos(GoType containingType) {
List<GoMethodInfo> results = new ArrayList<>(2);
Address addr = getTfn();
if (addr != null) {
results.add(new GoMethodInfo(containingType, this, addr));
}
addr = getIfn();
if (addr != null) {
results.add(new GoMethodInfo(containingType, this, addr));
}
return results;
}
@Override
public String toString() {
return String.format(
"GoMethod [context=%s, getName()=%s, getIfn()=%s, getTfn()=%s]", context, getName(),
getIfn(), getTfn());
}
//----------------------------------------------------------------------------------------
public class GoMethodInfo extends MethodInfo {
GoType type;
GoMethod method;
public GoMethodInfo(GoType type, GoMethod method, Address address) {
super(address);
this.type = type;
this.method = method;
}
public GoType getType() {
return type;
}
public GoMethod getMethod() {
return method;
}
public boolean isIfn(Address funcAddr) {
return funcAddr.equals(method.getIfn());
}
public boolean isTfn(Address funcAddr) {
return funcAddr.equals(method.getTfn());
}
@Override
public FunctionDefinition getSignature() throws IOException {
return type.getMethodSignature(method, false);
}
public FunctionDefinition getPartialSignature() throws IOException {
return type.getMethodSignature(method, true);
}
}
}

View file

@ -29,6 +29,8 @@ import ghidra.util.Msg;
* <p>
* To coerce java inheritance and structmapping features to match the layout of go rtti type structs,
* this class is constructed strangely.
* <p>
* {@link GoType} structure that defines a built-in primitive type.
*/
@StructureMapping(structureName = "runtime._type")
public class GoPlainType extends GoType implements StructureReader<GoType> {
@ -65,9 +67,10 @@ public class GoPlainType extends GoType implements StructureReader<GoType> {
dt = super.recoverDataType();
}
String name = typ.getNameString();
String name = getUniqueTypename();
if (!dt.getName().equalsIgnoreCase(name)) {
dt = new TypedefDataType(programContext.getRecoveredTypesCp(), name, dt, dtm);
dt = new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
name, dt, dtm);
}
if (dt.getLength() != typ.getSize()) {
Msg.warn(this,
@ -82,4 +85,5 @@ public class GoPlainType extends GoType implements StructureReader<GoType> {
return super.discoverGoTypes(discoveredTypes);
}
}

View file

@ -15,23 +15,33 @@
*/
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Set;
import java.io.IOException;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.PointerDataType;
/**
* {@link GoType} structure that defines a pointer.
*/
@StructureMapping(structureName = "runtime.ptrtype")
public class GoPointerType extends GoType {
@FieldMapping
@MarkupReference("element")
@MarkupReference("getElement")
private long elem;
public GoPointerType() {
// empty
}
/**
* Returns a reference to the element's type.
*
* @return reference to the element's type
* @throws IOException if error reading data
*/
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
@ -58,4 +68,25 @@ public class GoPointerType extends GoType {
}
return true;
}
@Override
public String getStructureNamespace() throws IOException {
GoType elementType = getElement();
return elementType != null
? elementType.getStructureNamespace()
: super.getStructureNamespace();
}
@Override
protected String getTypeDeclString() throws IOException {
// type PointerTypeName *elementType
String selfName = getName();
String elemName = programContext.getGoTypeName(elem);
String defStr = "*" + elemName;
String defStrWithLinks =
"*" + AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName);
boolean hasName = !defStr.equals(selfName);
return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks);
}
}

View file

@ -20,6 +20,7 @@ import java.util.Set;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.data.*;
/**
@ -32,12 +33,18 @@ import ghidra.program.model.data.*;
public class GoSliceType extends GoType {
@FieldMapping
@MarkupReference("element")
@MarkupReference("getElement")
private long elem;
public GoSliceType() {
// empty
}
/**
* Returns a reference to the element's type.
* @return reference to the element's type
* @throws IOException if error reading data
*/
@Markup
public GoType getElement() throws IOException {
return programContext.getGoType(elem);
@ -45,13 +52,14 @@ public class GoSliceType extends GoType {
@Override
public DataType recoverDataType() throws IOException {
StructureDataType sliceDT = new StructureDataType(programContext.getRecoveredTypesCp(),
typ.getNameString(), 0, programContext.getDTM());
StructureDataType sliceDT =
new StructureDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
getUniqueTypename(), 0, programContext.getDTM());
programContext.cacheRecoveredDataType(this, sliceDT);
// fixup the generic void* field with the specific element* type
GoType elementType = getElement();
DataType elementDT = elementType.recoverDataType();
DataType elementDT = programContext.getRecoveredType(elementType);
Pointer elementPtrDT = programContext.getDTM().getPointer(elementDT);
Structure genericSliceDT = programContext.getGenericSliceDT();
@ -77,4 +85,29 @@ public class GoSliceType extends GoType {
return true;
}
@Override
public String getStructureNamespace() throws IOException {
String packagePath = getPackagePathString();
if (packagePath != null) {
return packagePath;
}
GoType elementType = getElement();
return elementType != null
? elementType.getStructureNamespace()
: super.getStructureNamespace();
}
@Override
protected String getTypeDeclString() throws IOException {
// type CustomSliceType []elementType
String selfName = typ.getName();
String elemName = programContext.getGoTypeName(elem);
String defStr = "[]%s".formatted(elemName);
String defStrWithLinks = "[]%s".formatted(
AddressAnnotatedStringHandler.createAddressAnnotationString(elem, elemName));
boolean hasName = !defStr.equals(selfName);
return "type %s%s".formatted(hasName ? selfName + " " : "", defStrWithLinks);
}
}

View file

@ -21,6 +21,9 @@ import ghidra.app.util.bin.format.golang.rtti.GoName;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.structmapping.*;
/**
* Structure used to define a field in a {@link GoStructType struct type}.
*/
@StructureMapping(structureName = "runtime.structfield")
public class GoStructField {
@ -31,12 +34,12 @@ public class GoStructField {
private StructureContext<GoStructField> context;
@FieldMapping
@MarkupReference
@EOLComment("nameString")
@MarkupReference("getGoName")
@EOLComment("getName")
private long name; // direct ptr to GoName
@FieldMapping
@MarkupReference("type")
@MarkupReference("getType")
private long typ; // direct ptr to GoType
@FieldMapping(optional = true) //<=1.18
@ -45,23 +48,44 @@ public class GoStructField {
@FieldMapping(optional = true) //>=1.19
private long offset;
/**
* Returns the name of this field.
*
* @return name of this field as it's raw GoName value
* @throws IOException if error reading
*/
@Markup
public GoName getName() throws IOException {
public GoName getGoName() throws IOException {
return name != 0
? context.getDataTypeMapper().readStructure(GoName.class, name)
: null;
}
/**
* Returns the type of this field.
*
* @return type of this field
* @throws IOException if error reading
*/
@Markup
public GoType getType() throws IOException {
return programContext.getGoType(typ);
}
/**
* Setter called by offsetAnon field's serialization, referred by fieldmapping annotation.
*
* @param offsetAnon value
*/
public void setOffsetAnon(long offsetAnon) {
this.offsetAnon = offsetAnon;
this.offset = offsetAnon >> 1;
}
/**
* Returns the offset of this field.
* @return offset of this field
*/
public long getOffset() {
return offset;
}
@ -70,9 +94,13 @@ public class GoStructField {
// return (offsetAnon & 0x1) != 0;
// }
public String getNameString() throws IOException {
GoName n = getName();
return n != null ? n.getName() : null;
/**
* Returns the name of this field.
*
* @return name of this field
*/
public String getName() {
return programContext.getSafeName(this::getGoName, this, null).getName();
}
}
/*

View file

@ -24,6 +24,7 @@ import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
/**
* Golang type information about a specific structure type.
@ -42,16 +43,45 @@ public class GoStructType extends GoType {
// empty
}
/**
* Returns the package path of this structure type.
*
* @return package path of this structure type
* @throws IOException if error reading
*/
@Markup
public GoName getPkgPath() throws IOException {
return programContext.getGoName(pkgPath);
}
public String getPkgPathString() throws IOException {
GoName n = getPkgPath();
return n != null ? n.getName() : "";
/**
* Returns the package path of this structure type
*
* @return package path of this structure type, as a string
*/
@Override
public String getPackagePathString() {
String s = super.getPackagePathString(); // from uncommontype
if (s == null || s.isEmpty()) {
try {
GoName structPP = getPkgPath();
if (structPP != null) {
s = structPP.getName();
}
}
catch (IOException e) {
// fall thru, return existing s
}
}
return s;
}
/**
* Returns the fields defined by this struct type.
*
* @return list of fields defined by this struct type
* @throws IOException if error reading
*/
public List<GoStructField> getFields() throws IOException {
return fields.readList(GoStructField.class);
}
@ -62,33 +92,30 @@ public class GoStructType extends GoType {
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
public void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
super.additionalMarkup(session);
fields.markupArray(getStructureLabel() + "_fields", GoStructField.class, false, session);
fields.markupArray(getStructureLabel() + "_fields", getStructureNamespace(),
GoStructField.class, false, session);
fields.markupArrayElements(GoStructField.class, session);
}
@Override
public String getTypeDeclString() throws IOException {
String methodListStr = getMethodListString();
if (methodListStr == null || methodListStr.isEmpty()) {
methodListStr = "// No methods";
}
else {
methodListStr = "// Methods\n" + methodListStr;
String pps = getPackagePathString();
if (pps == null || pps.isEmpty()) {
pps = "<None>";
}
return """
// size: %d
// package: %s
type %s struct {
%s
%s
}
""".formatted(
%s}""".formatted(
typ.getSize(),
typ.getNameString(),
getFieldListString().indent(2),
methodListStr.indent(2));
pps,
typ.getName(),
getFieldListString().indent(2));
}
private String getFieldListString() throws IOException {
@ -99,16 +126,19 @@ public class GoStructType extends GoType {
}
long offset = field.getOffset();
long fieldSize = field.getType().getBaseType().getSize();
sb.append("%s %s // %d..%d".formatted(field.getNameString(),
field.getType().getNameString(), offset, offset + fieldSize));
sb.append("%s %s // %d..%d".formatted(field.getName(),
field.getType().getName(), offset, offset + fieldSize));
}
return sb.toString();
}
@Override
public DataType recoverDataType() throws IOException {
StructureDataType struct = new StructureDataType(programContext.getRecoveredTypesCp(),
typ.getNameString(), (int) typ.getSize(), programContext.getDTM());
StructureDataType struct =
new StructureDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
getUniqueTypename(), (int) typ.getSize(), programContext.getDTM());
// pre-push an empty struct into the cache to prevent endless recursive loops
programContext.cacheRecoveredDataType(this, struct);
List<GoStructField> skippedFields = new ArrayList<>();
@ -131,7 +161,7 @@ public class GoStructType extends GoType {
try {
DataType fieldDT = programContext.getRecoveredType(fieldType);
struct.replaceAtOffset((int) field.getOffset(), fieldDT, (int) fieldSize,
field.getNameString(), null);
field.getName(), null);
}
catch (IllegalArgumentException e) {
Msg.warn(this,
@ -145,8 +175,8 @@ public class GoStructType extends GoType {
if (dtc != null) {
String comment = dtc.getComment();
comment = comment == null ? "" : (comment + "\n");
comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getNameString(),
skippedFieldType.getNameString());
comment += "Omitted zero-len field: %s=%s".formatted(skippedField.getName(),
skippedFieldType.getName());
dtc.setComment(comment);
}
}

View file

@ -16,19 +16,22 @@
package ghidra.app.util.bin.format.golang.rtti.types;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.*;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.golang.rtti.GoSlice;
import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.rtti.types.GoMethod.GoMethodInfo;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.app.util.viewer.field.AddressAnnotatedStringHandler;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.util.exception.CancelledException;
/**
* Common abstract base class for GoType classes
*/
@PlateComment()
public abstract class GoType implements StructureMarkup<GoType> {
//@formatter:off
private static final Map<GoKind, Class<? extends GoType>> specializedTypeClasses =
Map.ofEntries(
Map.entry(GoKind.Struct, GoStructType.class),
@ -39,6 +42,7 @@ public abstract class GoType implements StructureMarkup<GoType> {
Map.entry(GoKind.Chan, GoChanType.class),
Map.entry(GoKind.Map, GoMapType.class),
Map.entry(GoKind.Interface, GoInterfaceType.class));
//@formatter:on
/**
* Returns the specific GoType derived class that will handle the go type located at the
@ -70,26 +74,59 @@ public abstract class GoType implements StructureMarkup<GoType> {
@FieldOutput
protected GoBaseType typ;
protected GoUncommonType uncommonType;
protected GoBaseType getBaseType() {
return typ;
}
public String getNameString() throws IOException {
return typ.getNameString();
/**
* Returns the starting offset of this type, used as an identifier.
*
* @return starting offset of this type
*/
public long getTypeOffset() {
return context.getStructureStart();
}
/**
* Returns the name of this type.
*
* @return name of this type
*/
public String getName() {
return typ.getName();
}
public String getNameWithPackageString() {
GoSymbolName parsedPackagePath = GoSymbolName.fromPackagePath(getPackagePathString());
String tpp = Objects.requireNonNullElse(parsedPackagePath.getTruncatedPackagePath(), "");
return tpp + getName();
}
/**
* Returns the package path of this type.
*
* @return package path of this type
*/
public String getPackagePathString() {
try {
return typ.hasUncommonType() ? getUncommonType().getPackagePathString() : "";
}
catch (IOException e) {
return "";
}
}
public String getDebugId() {
return "%s@%s".formatted(
context.getMappingInfo().getDescription(),
return "%s@%s".formatted(context.getMappingInfo().getDescription(),
context.getStructureAddress());
}
protected long getOffsetEndOfFullType() {
return context.getStructureEnd() +
(typ.hasUncommonType()
? programContext.getStructureMappingInfo(GoUncommonType.class)
.getStructureLength()
: 0);
return context.getStructureEnd() + (typ.hasUncommonType()
? programContext.getStructureMappingInfo(GoUncommonType.class).getStructureLength()
: 0);
}
/**
@ -107,9 +144,30 @@ public abstract class GoType implements StructureMarkup<GoType> {
@Markup
public GoUncommonType getUncommonType() throws IOException {
return typ.hasUncommonType()
? programContext.readStructure(GoUncommonType.class, context.getStructureEnd())
: null;
if (uncommonType == null && typ.hasUncommonType()) {
uncommonType =
programContext.readStructure(GoUncommonType.class, context.getStructureEnd());
}
return uncommonType;
}
/**
* Returns a list of all methods defined on this type. Methods that specify both a
* "tfn" address as well as a "ifn" address will be represented twice.
*
* @return list of MethodInfo's
* @throws IOException if error reading
*/
public List<GoMethodInfo> getMethodInfoList() throws IOException {
List<GoMethodInfo> results = new ArrayList<>();
GoUncommonType ut = getUncommonType();
List<GoMethod> methods;
if (ut != null && (methods = ut.getMethods()) != null) {
for (GoMethod method : methods) {
results.addAll(method.getMethodInfos(this));
}
}
return results;
}
@Override
@ -119,64 +177,153 @@ public abstract class GoType implements StructureMarkup<GoType> {
@Override
public String getStructureName() throws IOException {
return typ.getNameString();
return getNameWithPackageString();
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException {
GoUncommonType uncommonType = getUncommonType();
if (uncommonType != null) {
GoSlice slice = uncommonType.getMethodsSlice();
slice.markupArray(getStructureName() + "_methods", GoMethod.class, false, session);
public String getStructureNamespace() throws IOException {
return getPackagePathString();
}
@Override
public void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
GoUncommonType ut = getUncommonType();
if (ut != null) {
GoSlice slice = ut.getMethodsSlice();
slice.markupArray(getStructureName() + "_methods", getStructureNamespace(),
GoMethod.class, false, session);
slice.markupArrayElements(GoMethod.class, session);
session.labelStructure(uncommonType, typ.getNameString() + "_" +
programContext.getStructureDataTypeName(GoUncommonType.class));
session.labelStructure(ut,
typ.getName() + "_" +
programContext.getStructureDataTypeName(GoUncommonType.class),
getStructureNamespace());
}
}
public String getMethodListString() throws IOException {
GoUncommonType uncommonType = getUncommonType();
if (uncommonType == null || uncommonType.mcount == 0) {
return "";
}
protected String getImplementsInterfaceString() {
StringBuilder sb = new StringBuilder();
for (GoMethod method : uncommonType.getMethods()) {
for (GoItab goItab : programContext.getInterfacesImplementedByType(this)) {
if (!sb.isEmpty()) {
sb.append("\n");
}
String methodStr = method.getNameString();
GoType type = method.getType();
if (type instanceof GoFuncType funcType) {
methodStr = funcType.getFuncPrototypeString(methodStr);
try {
sb.append(AddressAnnotatedStringHandler.createAddressAnnotationString(
goItab.getInterfaceType().getStructureContext().getStructureAddress(),
goItab.getInterfaceType().getNameWithPackageString()));
sb.append(" ");
sb.append(AddressAnnotatedStringHandler.createAddressAnnotationString(
goItab.getStructureContext().getStructureAddress(), "[itab]"));
}
else {
methodStr = "func %s()".formatted(methodStr);
catch (IOException e) {
sb.append("unknown_interface");
}
sb.append(methodStr);
}
return sb.toString();
}
protected String getTypeDeclString() throws IOException {
String s = "type " + typ.getNameString() + " " + typ.getKind();
String methodListString = getMethodListString();
if (!methodListString.isEmpty()) {
s += "\n// Methods\n" + methodListString;
protected String getMethodListString() throws IOException {
GoUncommonType ut = getUncommonType();
if (ut == null || uncommonType.mcount == 0) {
return "";
}
return s;
String typeName = getName();
StringBuilder sb = new StringBuilder();
for (GoMethod method : ut.getMethods()) {
GoType ptrType = typ.getPtrToThis();
String tfnStr = makeMethodStr(method.getType(), method.getName(), typeName);
String ifnStr = ptrType != null
? makeMethodStr(method.getType(), method.getName(), ptrType.getName())
: null;
Address tfnAddr = method.getTfn();
if (tfnAddr != null) {
sb.append(!sb.isEmpty() ? "\n" : "")
.append(AddressAnnotatedStringHandler.createAddressAnnotationString(tfnAddr,
tfnStr));
}
Address ifnAddr = method.getIfn();
if (ifnAddr != null && ifnStr != null) {
sb.append(!sb.isEmpty() ? "\n" : "")
.append(AddressAnnotatedStringHandler.createAddressAnnotationString(ifnAddr,
ifnStr));
}
if (tfnAddr == null && ifnAddr == null) {
String methodStr = makeMethodStr(method.getType(), method.getName(), typeName);
sb.append(!sb.isEmpty() ? "\n" : "").append(methodStr);
}
}
return sb.toString();
}
private String makeMethodStr(GoType methodFuncType, String methodName,
String containingTypeName) throws IOException {
return methodFuncType instanceof GoFuncType funcdefType
? funcdefType.getFuncPrototypeString(methodName, containingTypeName)
: "func (%s) %s(???)".formatted(containingTypeName, methodName);
}
/**
* Return a funcdef signature for a method that is attached to this type.
*
* @param method {@link GoMethod}
* @param allowPartial boolean flag, if true, allow returning a partially defined signature
* when the method's funcdef type is not specified
* @return {@link FunctionDefinition} (that contains a receiver parameter), or null if
* the method's funcdef type was not specified and allowPartial was not true
* @throws IOException if error reading type info
*/
public FunctionDefinition getMethodSignature(GoMethod method, boolean allowPartial)
throws IOException {
return programContext.getSpecializedMethodSignature(method.getName(),
method.getType(), programContext.getRecoveredType(this), allowPartial);
}
/**
* Returns a descriptive string that defines the declaration of this type.
* <p>
* This method should be overloaded by more specific types.
*
* @return descriptive string
* @throws IOException if error reading data
*/
protected String getTypeDeclString() throws IOException {
return "type " + typ.getName() + " " + typ.getKind();
}
@Override
public String toString() {
try {
return getTypeDeclString();
String s = getTypeDeclString();
String methodListString = getMethodListString();
if (!methodListString.isEmpty()) {
s += "\n\n// Methods\n" + methodListString;
}
String interfaceString = getImplementsInterfaceString();
if (!interfaceString.isEmpty()) {
s += "\n\n// Interfaces implemented\n" + interfaceString;
}
return s;
}
catch (IOException e) {
return super.toString();
}
}
/**
* Returns the name of this type, after being uniqified against all other types defined in the
* program.
* <p>
* See {@link GoRttiMapper#getUniqueGoTypename(GoType)}.
*
* @return name of this type
*/
public String getUniqueTypename() {
return programContext.getUniqueGoTypename(this);
}
/**
* Converts a golang RTTI type structure into a Ghidra data type.
*
@ -185,17 +332,29 @@ public abstract class GoType implements StructureMarkup<GoType> {
*/
public DataType recoverDataType() throws IOException {
DataType dt = Undefined.getUndefinedDataType((int) typ.getSize());
return new TypedefDataType(programContext.getRecoveredTypesCp(), typ.getNameString(), dt,
programContext.getDTM());
return new TypedefDataType(programContext.getRecoveredTypesCp(getPackagePathString()),
getUniqueTypename(), dt, programContext.getDTM());
}
/**
* Iterates this type, and any types this type refers to, while registering the types with
* the {@link GoRttiMapper} context.
* <p>
* This method should be overloaded by derived type classes to add any additional types
* referenced by the derived type.
*
* @param discoveredTypes set of already iterated types
* @return boolean boolean flag, if false the type has already been discovered, if true
* the type was encountered for the first time
* @throws IOException if error reading type info
*/
public boolean discoverGoTypes(Set<Long> discoveredTypes) throws IOException {
if (!discoveredTypes.add(context.getStructureStart())) {
return false;
}
GoUncommonType uncommonType = getUncommonType();
if (uncommonType != null) {
for (GoMethod method : uncommonType.getMethods()) {
GoUncommonType ut = getUncommonType();
if (ut != null) {
for (GoMethod method : ut.getMethods()) {
GoType methodType = method.getType();
if (methodType != null) {
methodType.discoverGoTypes(discoveredTypes);

View file

@ -18,6 +18,9 @@ package ghidra.app.util.bin.format.golang.rtti.types;
import java.util.EnumSet;
import java.util.Set;
/**
* Enum defining the various bitflags held in a GoType's tflag
*/
public enum GoTypeFlag {
Uncommon(1 << 0), // 1
ExtraStar(1 << 1), // 2
@ -26,7 +29,7 @@ public enum GoTypeFlag {
private final int value;
private GoTypeFlag(int i) {
GoTypeFlag(int i) {
this.value = i;
}

View file

@ -22,6 +22,10 @@ import ghidra.app.util.bin.format.golang.rtti.*;
import ghidra.app.util.bin.format.golang.structmapping.*;
import ghidra.util.Msg;
/**
* Structure found immediately after a {@link GoType} structure, if it has the uncommon flag
* set.
*/
@StructureMapping(structureName = "runtime.uncommontype")
public class GoUncommonType {
@ -32,8 +36,8 @@ public class GoUncommonType {
private StructureContext<GoUncommonType> context;
@FieldMapping(fieldName = "pkgpath")
@MarkupReference("pkgPath")
@EOLComment("packagePathString")
@MarkupReference("getPkgPath")
@EOLComment("getPackagePathString")
long pkgpath_nameOff;
@FieldMapping
@ -45,20 +49,42 @@ public class GoUncommonType {
@FieldMapping
long moff;
/**
* Returns the package path of the type.
*
* @return package path of the type
* @throws IOException if error reading data
*/
@Markup
public GoName getPkgPath() throws IOException {
return programContext.resolveNameOff(context.getStructureStart(), pkgpath_nameOff);
}
/**
* Returns the package path of the type.
* @return package path of the type, as a string
* @throws IOException if error reading data
*/
public String getPackagePathString() throws IOException {
GoName pkgPath = getPkgPath();
return pkgPath != null ? pkgPath.getName() : null;
return pkgPath != null ? pkgPath.getName() : "";
}
/**
* Returns a slice containing the methods defined by the type.
*
* @return slice containing the methods defined by the type
*/
public GoSlice getMethodsSlice() {
return new GoSlice(context.getFieldLocation(moff), mcount, mcount, programContext);
}
/**
* Returns a list of the methods defined by the type.
*
* @return list of the methods defined by the type
* @throws IOException if error reading data
*/
public List<GoMethod> getMethods() throws IOException {
GoSlice slice = getMethodsSlice();
if (!slice.isValid(

View file

@ -88,9 +88,8 @@ public class DataTypeMapper implements AutoCloseable {
protected DataTypeMapper(Program program, ResourceFile archiveGDT) throws IOException {
this.program = program;
this.programDTM = program.getDataTypeManager();
this.archiveDTM = archiveGDT != null
? FileDataTypeManager.openFileArchive(archiveGDT, false)
: null;
this.archiveDTM =
archiveGDT != null ? FileDataTypeManager.openFileArchive(archiveGDT, false) : null;
}
@Override
@ -180,8 +179,7 @@ public class DataTypeMapper implements AutoCloseable {
structName = "<missing>";
}
throw new IOException(
"Missing struct definition %s - %s".formatted(clazz.getSimpleName(),
structName));
"Missing struct definition %s - %s".formatted(clazz.getSimpleName(), structName));
}
StructureMappingInfo<T> structMappingInfo = StructureMappingInfo.fromClass(clazz, structDT);
@ -320,9 +318,8 @@ public class DataTypeMapper implements AutoCloseable {
* a structure mapped object
*/
public <T> StructureContext<T> getStructureContextOfInstance(T structureInstance) {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
StructureMappingInfo<T> smi =
structureInstance != null ? getStructureMappingInfo(structureInstance) : null;
return smi != null ? smi.recoverStructureContext(structureInstance) : null;
}
@ -336,14 +333,30 @@ public class DataTypeMapper implements AutoCloseable {
* @return {@link Address} of the object, or null if not found or not a supported object
*/
public <T> Address getAddressOfStructure(T structureInstance) {
StructureMappingInfo<T> smi = structureInstance != null
? getStructureMappingInfo(structureInstance)
: null;
StructureContext<T> structureContext = smi != null
? smi.recoverStructureContext(structureInstance)
: null;
StructureMappingInfo<T> smi =
structureInstance != null ? getStructureMappingInfo(structureInstance) : null;
StructureContext<T> structureContext =
smi != null ? smi.recoverStructureContext(structureInstance) : null;
return structureContext != null ? structureContext.getStructureAddress() : null;
}
/**
* Returns the address of the last byte of a structure.
*
* @param <T> type of object
* @param structureInstance instance of an object that represents something in the program's
* memory
* @return {@link Address} of the last byte of the object, or null if not found
* or not a supported object
*/
public <T> Address getMaxAddressOfStructure(T structureInstance) {
StructureMappingInfo<T> smi =
structureInstance != null ? getStructureMappingInfo(structureInstance) : null;
StructureContext<T> structureContext =
smi != null ? smi.recoverStructureContext(structureInstance) : null;
return structureContext != null
? structureContext.getStructureAddress()
.add(structureContext.getStructureLength() - 1)
: null;
}
@ -359,7 +372,26 @@ public class DataTypeMapper implements AutoCloseable {
*/
public <T> T readStructure(Class<T> structureClass, BinaryReader structReader)
throws IOException {
StructureContext<T> structureContext = createStructureContext(structureClass, structReader);
return readStructure(structureClass, null, structReader);
}
/**
* Reads a structure mapped object from the current position of the specified BinaryReader.
*
* @param <T> type of object
* @param structureClass structure mapped object class
* @param containingFieldDataType optional, data type of the structure field that contained the
* object instance that is being read (may be different than the data type that was specified in
* the matching {@link StructureMappingInfo})
* @param structReader {@link BinaryReader} positioned at the start of an object
* @return new object instance of type T
* @throws IOException if error reading
* @throws IllegalArgumentException if specified structureClass is not valid
*/
public <T> T readStructure(Class<T> structureClass, DataType containingFieldDataType,
BinaryReader structReader) throws IOException {
StructureContext<T> structureContext =
createStructureContext(structureClass, containingFieldDataType, structReader);
T result = structureContext.readNewInstance();
return result;
@ -454,13 +486,29 @@ public class DataTypeMapper implements AutoCloseable {
}
private <T> StructureContext<T> createStructureContext(Class<T> structureClass,
BinaryReader reader) throws IllegalArgumentException {
DataType containingFieldDataType, BinaryReader reader) throws IllegalArgumentException {
StructureMappingInfo<T> smi = getStructureMappingInfo(structureClass);
if (smi == null) {
throw new IllegalArgumentException(
"Unknown structure mapped class: " + structureClass.getSimpleName());
}
return new StructureContext<>(this, smi, reader);
return new StructureContext<>(this, smi, containingFieldDataType, reader);
}
/**
* Creates an artificial structure context to be used in some limited situations.
*
* @param <T> type of structure mapped object
* @param structureClass class of structure mapped object
* @return new {@link StructureContext}
*/
public <T> StructureContext<T> createArtificialStructureContext(Class<T> structureClass) {
StructureMappingInfo<T> smi = getStructureMappingInfo(structureClass);
if (smi == null) {
throw new IllegalArgumentException(
"Unknown structure mapped class: " + structureClass.getSimpleName());
}
return new StructureContext<>(this, smi, null);
}
}

View file

@ -21,9 +21,9 @@ import java.lang.reflect.Method;
import java.util.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.CodeUnit;
import ghidra.util.exception.CancelledException;
/**
* Immutable information needed to deserialize a field in a structure mapped class.
@ -205,11 +205,8 @@ public class FieldMappingInfo<T> {
Class<?> fieldType = field.getType();
// TODO: be more strict about setter name, if specified it must be found or error
Method setterMethod = ReflectionHelper.findSetter(field.getName(), setterNameOverride,
this.setterMethod = ReflectionHelper.findSetter(field.getName(), setterNameOverride,
structTargetClass, fieldType);
if (setterMethod != null) {
this.setterMethod = setterMethod;
}
// setup the logic for deserializing the value from the in-memory structure
if (fieldReadValueClass != FieldReadFunction.class) {
@ -274,13 +271,14 @@ public class FieldMappingInfo<T> {
}
private Object readStructureMappedTypeFunc(FieldContext<T> context) throws IOException {
DataType fieldDT = context.dtc() != null ? context.dtc().getDataType() : null;
return context.structureContext()
.getDataTypeMapper()
.readStructure(field.getType(), context.reader());
.readStructure(field.getType(), fieldDT, context.reader());
}
private void markupNestedStructure(FieldContext<T> fieldContext, MarkupSession markupSession)
throws IOException {
throws IOException, CancelledException {
markupSession.markup(fieldContext.getValue(Object.class), true);
}

View file

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

View file

@ -20,6 +20,7 @@ import java.lang.reflect.Array;
import java.util.*;
import ghidra.app.util.bin.format.dwarf4.DWARFUtil;
import ghidra.app.util.bin.format.dwarf4.next.DWARFDataInstanceHelper;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
@ -29,7 +30,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
@ -40,6 +41,7 @@ public class MarkupSession {
protected Program program;
protected DataTypeMapper mappingContext;
protected Set<Address> markedupStructs = new HashSet<>();
protected AddressSet markedupAddrs = new AddressSet();
protected TaskMonitor monitor;
/**
@ -72,6 +74,10 @@ public class MarkupSession {
return mappingContext;
}
public AddressSet getMarkedupAddresses() {
return markedupAddrs;
}
/**
* Decorates the specified object's memory using the various structure mapping tags that
* were applied the object's class definition.
@ -85,12 +91,11 @@ public class MarkupSession {
* who's data type has already been laid down in memory, removing the need for this object's
* data type to be applied to memory
* @throws IOException if error or cancelled
* @throws CancelledException if cancelled
* @throws IllegalArgumentException if object instance is not a supported type
*/
public <T> void markup(T obj, boolean nested) throws IOException {
if (monitor.isCancelled()) {
throw new IOException("Markup canceled");
}
public <T> void markup(T obj, boolean nested) throws IOException, CancelledException {
monitor.checkCancelled();
if (obj == null) {
return;
}
@ -112,11 +117,12 @@ public class MarkupSession {
}
}
else {
StructureContext<T> structureContext = mappingContext.getStructureContextOfInstance(obj);
StructureContext<T> structureContext =
mappingContext.getStructureContextOfInstance(obj);
if (structureContext == null) {
throw new IllegalArgumentException();
}
monitor.incrementProgress(1);
monitor.increment();
markupStructure(structureContext, nested);
}
}
@ -141,14 +147,19 @@ public class MarkupSession {
* @throws IOException if error marking up address
*/
public void markupAddress(Address addr, DataType dt, int length) throws IOException {
try {
DataUtilities.createData(program, addr, dt, length, false,
ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA);
}
catch (CodeUnitInsertionException e) {
throw new IOException(e);
}
DWARFDataInstanceHelper dihUtil =
new DWARFDataInstanceHelper(program).setAllowTruncating(false);
if (dihUtil.isDataTypeCompatibleWithAddress(dt, addr)) {
try {
Data data = DataUtilities.createData(program, addr, dt, length, false,
ClearDataMode.CLEAR_ALL_CONFLICT_DATA);
markedupAddrs.add(data.getMinAddress(), data.getMaxAddress());
}
catch (CodeUnitInsertionException e) {
throw new IOException(e);
}
}
}
/**
@ -171,11 +182,13 @@ public class MarkupSession {
* @param <T> structure mapped object type
* @param obj structure mapped object
* @param symbolName name
* @param namespaceName name of namespace to place the label symbol in, or null if root
* @throws IOException if error
*/
public <T> void labelStructure(T obj, String symbolName) throws IOException {
public <T> void labelStructure(T obj, String symbolName, String namespaceName)
throws IOException {
Address addr = mappingContext.getAddressOfStructure(obj);
labelAddress(addr, symbolName);
labelAddress(addr, symbolName, namespaceName);
}
/**
@ -186,13 +199,35 @@ public class MarkupSession {
* @throws IOException if error
*/
public void labelAddress(Address addr, String symbolName) throws IOException {
labelAddress(addr, symbolName, null);
}
/**
* Places a label at the specified address.
*
* @param addr {@link Address}
* @param symbolName name
* @param namespaceName name of namespace to place the label symbol in, or null if root
* @throws IOException if error
*/
public void labelAddress(Address addr, String symbolName, String namespaceName)
throws IOException {
try {
SymbolTable symbolTable = program.getSymbolTable();
Symbol[] symbols = symbolTable.getSymbols(addr);
if (symbols.length == 0 || symbols[0].isDynamic()) {
symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true);
symbolTable.createLabel(addr, symbolName, SourceType.IMPORTED);
Namespace ns = null;
try {
ns = (namespaceName == null || namespaceName.isBlank())
? program.getGlobalNamespace()
: program.getSymbolTable()
.getOrCreateNameSpace(program.getGlobalNamespace(), namespaceName,
SourceType.IMPORTED);
}
catch (DuplicateNameException e) {
ns = program.getGlobalNamespace();
}
symbolName = SymbolUtilities.replaceInvalidChars(symbolName, true);
Symbol newLabelSym = symbolTable.createLabel(addr, symbolName, ns, SourceType.IMPORTED);
newLabelSym.setPrimary();
}
catch (InvalidInputException e) {
throw new IOException(e);
@ -237,6 +272,13 @@ public class MarkupSession {
prefix, comment, sep);
}
public void appendComment(Function func, String prefix, String comment) {
if (func != null) {
DWARFUtil.appendComment(program, func.getEntryPoint(), CodeUnit.PLATE_COMMENT, prefix,
comment, "\n");
}
}
/**
* Decorates a structure mapped structure, and everything it contains.
*
@ -245,9 +287,10 @@ public class MarkupSession {
* @param nested if true, it is assumed that the Ghidra data types have already been
* placed and only markup needs to be performed.
* @throws IOException if error marking up structure
* @throws CancelledException if cancelled
*/
public <T> void markupStructure(StructureContext<T> structureContext, boolean nested)
throws IOException {
throws IOException, CancelledException {
Address addr = structureContext.getStructureAddress();
if (!nested && !markedupStructs.add(addr)) {
return;
@ -257,6 +300,9 @@ public class MarkupSession {
if (!nested) {
try {
Structure structDT = structureContext.getStructureDataType();
if (structDT.isDeleted()) {
throw new IOException("Structure mapping data type invalid: " + structDT);
}
markupAddress(addr, structDT);
}
catch (IOException e) {
@ -268,8 +314,9 @@ public class MarkupSession {
if (instance instanceof StructureMarkup<?> sm) {
String structureLabel = sm.getStructureLabel();
String namespaceName = sm.getStructureNamespace();
if (structureLabel != null && !structureLabel.isBlank()) {
labelAddress(addr, structureLabel);
labelAddress(addr, structureLabel, namespaceName);
}
}
}
@ -282,7 +329,8 @@ public class MarkupSession {
}
<T> void markupFields(StructureContext<T> structureContext) throws IOException {
<T> void markupFields(StructureContext<T> structureContext)
throws IOException, CancelledException {
T structureInstance = structureContext.getStructureInstance();
StructureMappingInfo<T> mappingInfo = structureContext.getMappingInfo();
for (FieldMappingInfo<T> fmi : mappingInfo.getFields()) {
@ -330,31 +378,36 @@ public class MarkupSession {
* Creates a default function at the specified address.
*
* @param name name of the new function
* @param ns namespace function should be in
* @param addr address of the new function
* @return {@link Function} that was created
*/
public Function createFunctionIfMissing(String name, Address addr) {
public Function createFunctionIfMissing(String name, Namespace ns, Address addr) {
Function function = program.getListing().getFunctionAt(addr);
if (function == null) {
try {
if (!program.getMemory()
.getLoadedAndInitializedAddressSet()
.contains(addr)) {
Msg.warn(this,
"Unable to create function not contained within loaded memory: %s@%s"
.formatted(name, addr));
return null;
}
function = program.getFunctionManager()
.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED);
if (!program.getMemory().getLoadedAndInitializedAddressSet().contains(addr)) {
Msg.warn(this, "Unable to create function not contained within loaded memory: %s@%s"
.formatted(name, addr));
return null;
}
catch (OverlappingFunctionException | InvalidInputException e) {
try {
function = program.getFunctionManager()
.createFunction(name, ns, addr, new AddressSet(addr), SourceType.IMPORTED);
}
catch (OverlappingFunctionException | InvalidInputException
| IllegalArgumentException e) {
Msg.error(this, e);
return null;
}
}
else {
// TODO: this does nothing. re-evalulate this logic
//mappingContext.labelAddress(addr, name);
try {
function.setName(name, SourceType.IMPORTED);
function.setParentNamespace(ns);
}
catch (InvalidInputException | DuplicateNameException | CircularDependencyException e) {
Msg.error(this, e);
}
}
return function;
}

View file

@ -19,8 +19,7 @@ import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.*;
/**
* Information about an instance of a structure that has been read from the memory of a
@ -45,6 +44,7 @@ import ghidra.program.model.data.Structure;
public class StructureContext<T> {
protected final DataTypeMapper dataTypeMapper;
protected final StructureMappingInfo<T> mappingInfo;
protected final DataType containingFieldDataType;
protected final BinaryReader reader;
protected final long structureStart;
protected T structureInstance;
@ -55,14 +55,31 @@ public class StructureContext<T> {
*
* @param dataTypeMapper mapping context for the program
* @param mappingInfo mapping information about this structure
* @param reader {@link BinaryReader} positioned at the start of the structure to be read
* @param reader {@link BinaryReader} positioned at the start of the structure to be read, or
* null if this is a limited-use context object
*/
public StructureContext(DataTypeMapper dataTypeMapper, StructureMappingInfo<T> mappingInfo,
BinaryReader reader) {
this(dataTypeMapper, mappingInfo, null, reader);
}
/**
* Creates an instance of a {@link StructureContext}.
*
* @param dataTypeMapper mapping context for the program
* @param mappingInfo mapping information about this structure
* @param containingFieldDataType optional, the DataType of the field that contained the
* instance being deserialized
* @param reader {@link BinaryReader} positioned at the start of the structure to be read, or
* null if this is a limited-use context object
*/
public StructureContext(DataTypeMapper dataTypeMapper, StructureMappingInfo<T> mappingInfo,
DataType containingFieldDataType, BinaryReader reader) {
this.dataTypeMapper = dataTypeMapper;
this.mappingInfo = mappingInfo;
this.containingFieldDataType = containingFieldDataType;
this.reader = reader;
this.structureStart = reader.getPointerIndex();
this.structureStart = reader != null ? reader.getPointerIndex() : -1;
this.structureDataType = mappingInfo.getStructureDataType();
}
@ -108,6 +125,22 @@ public class StructureContext<T> {
return dataTypeMapper;
}
/**
* Returns the {@link DataType} of the field that this object instance was contained inside of,
* or null if this instance was not a field inside another structure.
* <p>
* For instance, if a structure was being deserialized because it was a field inside
* another structure, the actual Ghidra data type of the field may be slightly different
* than the structure data type defined at the top of the structmapped
* class (ie. {@code @StructureMapping(structureName='struct')}. The containing field's
* data type could allow custom logic to enrich or modify this struct's behavior.
*
* @return {@link DataType} of the field that this object instance was contained inside of
*/
public DataType getContainingFieldDataType() {
return containingFieldDataType;
}
/**
* Returns the address in the program of this structure instance.
*

View file

@ -18,6 +18,8 @@ package ghidra.app.util.bin.format.golang.structmapping;
import java.io.IOException;
import java.util.List;
import ghidra.util.exception.CancelledException;
/**
* Optional interface that structure mapped classes can implement that allows them to control how
* their class is marked up.
@ -43,8 +45,13 @@ public interface StructureMarkup<T> {
/**
* Returns a string that can be used to place a label on the instance.
* <p>
* This default implementation will query the {@link #getStructureName()} method, and if
* it provides a value, will produce a string that looks like "name___mappingstructname", where
* "mappingstructname" will be the {@code structureName} value in the {@code @StructureMapping}
* annotation.
*
* @return string to be used as a labe, or null if there is not a valid label for the instance
* @return string to be used as a label, or null if there is not a valid label for the instance
* @throws IOException if error getting label
*/
default String getStructureLabel() throws IOException {
@ -55,13 +62,24 @@ public interface StructureMarkup<T> {
: null;
}
/**
* Returns the namespace that any labels should be placed in.
*
* @return name of namespace to place the label for this structure mapped type, or null
* @throws IOException if error generating namespace name
*/
default String getStructureNamespace() throws IOException {
return null;
}
/**
* Called to allow the implementor to perform custom markup of itself.
*
* @param session state and methods to assist marking up the program
* @throws IOException if error during markup
* @throws CancelledException if cancelled
*/
default void additionalMarkup(MarkupSession session) throws IOException {
default void additionalMarkup(MarkupSession session) throws IOException, CancelledException {
// empty
}

View file

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

View file

@ -15,10 +15,9 @@
*/
package ghidra.app.util.opinion;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import com.google.common.primitives.Bytes;
@ -26,8 +25,10 @@ import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.info.ElfInfoItem.ItemWithAddress;
import ghidra.app.util.bin.format.golang.GoBuildInfo;
import ghidra.app.util.bin.format.golang.PEGoBuildId;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.mz.DOSHeader;
import ghidra.app.util.bin.format.pe.*;
import ghidra.app.util.bin.format.pe.ImageCor20Header.ImageCor20Flags;
@ -141,7 +142,7 @@ public class PeLoader extends AbstractPeDebugLoader {
processDelayImports(optionalHeader, program, monitor, log);
processRelocations(optionalHeader, program, monitor, log);
processDebug(optionalHeader, ntHeader, sectionToAddress, program, options, monitor);
processProperties(optionalHeader, program, monitor);
processProperties(optionalHeader, ntHeader, program, monitor);
processComments(program.getListing(), monitor);
processSymbols(ntHeader, sectionToAddress, program, monitor, log);
@ -275,7 +276,7 @@ public class PeLoader extends AbstractPeDebugLoader {
}
}
private void processProperties(OptionalHeader optionalHeader, Program prog,
private void processProperties(OptionalHeader optionalHeader, NTHeader ntHeader, Program prog,
TaskMonitor monitor) {
if (monitor.isCancelled()) {
return;
@ -284,6 +285,24 @@ public class PeLoader extends AbstractPeDebugLoader {
props.setInt("SectionAlignment", optionalHeader.getSectionAlignment());
props.setBoolean(RelocationTable.RELOCATABLE_PROP_NAME,
prog.getRelocationTable().getSize() > 0);
if (GoRttiMapper.isGolangProgram(prog)) {
processGolangProperties(optionalHeader, ntHeader, prog, monitor);
}
}
private void processGolangProperties(OptionalHeader optionalHeader, NTHeader ntHeader,
Program prog, TaskMonitor monitor) {
ItemWithAddress<PEGoBuildId> buildId = PEGoBuildId.findBuildId(prog);
if (buildId != null) {
buildId.item().markupProgram(prog, buildId.address());
}
ItemWithAddress<GoBuildInfo> buildInfo = GoBuildInfo.findBuildInfo(prog);
if (buildInfo != null) {
buildInfo.item().markupProgram(prog, buildInfo.address());
}
}
private void processRelocations(OptionalHeader optionalHeader, Program prog,
@ -894,7 +913,7 @@ public class PeLoader extends AbstractPeDebugLoader {
public final String label; // value stored as ProgramInformation.Compiler property
public final String family; // used for Opinion secondary query param
private CompilerEnum(String label, String secondary) {
CompilerEnum(String label, String secondary) {
this.label = label;
this.family = secondary;
}

View file

@ -35,6 +35,33 @@ public class AddressAnnotatedStringHandler implements AnnotatedStringHandler {
"@address annotation must have an address" + "string";
private static final String[] SUPPORTED_ANNOTATIONS = { "address", "addr" };
/**
* Constructs a well-formed Address Annotation comment string.
*
* @param destinationAddress destination of the annotation
* @param displayText text that will be used as the body of the annotation. Problematic
* characters will be escaped
* @return string
*/
public static String createAddressAnnotationString(Address destinationAddress,
String displayText) {
return "{@address %s %s}".formatted(destinationAddress.toString(false),
AnnotatedStringHandler.escapeAnnotationPart(displayText));
}
/**
* Constructs a well-formed Address Annotation comment string.
*
* @param addressOffset destination of the annotation
* @param displayText text that will be used as the body of the annotation. Problematic
* characters will be escaped
* @return string
*/
public static String createAddressAnnotationString(long addressOffset, String displayText) {
return "{@address 0x%x %s}".formatted(addressOffset,
AnnotatedStringHandler.escapeAnnotationPart(displayText));
}
@Override
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
Program program) throws AnnotationException {

View file

@ -15,6 +15,11 @@
*/
package ghidra.app.util.viewer.field;
import java.awt.event.MouseEvent;
import java.util.Objects;
import docking.widgets.fieldpanel.field.AttributedString;
import docking.widgets.fieldpanel.field.FieldElement;
import ghidra.app.nav.Navigatable;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
@ -22,11 +27,6 @@ import ghidra.program.util.ProgramLocation;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import ghidra.util.classfinder.ExtensionPoint;
import java.awt.event.MouseEvent;
import docking.widgets.fieldpanel.field.AttributedString;
import docking.widgets.fieldpanel.field.FieldElement;
/**
* NOTE: ALL AnnotatedStringHandler CLASSES MUST END IN "StringHandler". If not,
* the ClassSearcher will not find them.
@ -36,7 +36,27 @@ import docking.widgets.fieldpanel.field.FieldElement;
*/
public interface AnnotatedStringHandler extends ExtensionPoint {
public static final AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() {
/**
* Escape a string that is intended to be used as a annotated string portion.
* <p>
* Quotes are escaped, '}' and ' ' are placed inside quotes.
*
* @param s string to escape
* @return escaped string
*/
static String escapeAnnotationPart(String s) {
s = Objects.requireNonNullElse(s, "");
String origStr = s;
if (s.indexOf('"') >= 0) {
s = s.replace("\"", "\\\"");
}
if (origStr != s || s.indexOf('}') != -1 || s.indexOf(' ') != -1) {
s = "\"" + s + "\"";
}
return s;
}
AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() {
@Override
public boolean handleMouseClick(ProgramLocation location, MouseEvent mouseEvent,
ServiceProvider serviceProvider) {
@ -60,7 +80,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
* @throws AnnotationException if the given text data does not fit the expected format for
* the given handler implementation.
*/
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
Program program) throws AnnotationException;
/**
@ -69,7 +89,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
*
* @return the annotation string names that this AnnotatedStringHandler supports.
*/
public String[] getSupportedAnnotations();
String[] getSupportedAnnotations();
/**
* A method that is notified when an annotation is clicked. Returns true if this annotation
@ -81,27 +101,28 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
* @return true if this annotation handles the click; return false if this annotation does
* not do anything with the click.
*/
public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable,
boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable,
ServiceProvider serviceProvider);
/**
* Returns the String that represents the GUI presence of this option
* @return the String to display in GUI components.
*/
public String getDisplayString();
String getDisplayString();
/**
* Returns an example string of how the annotation is used
* @return the example of how this is used.
*/
public String getPrototypeString();
String getPrototypeString();
/**
* Returns an example string of how the annotation is used
* @param displayText The text that may be wrapped, cannot be null
* @return the example of how this is used.
*/
public default String getPrototypeString(String displayText) {
default String getPrototypeString(String displayText) {
return getPrototypeString();
}
}

View file

@ -0,0 +1,103 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.bin.format.golang.rtti;
import static org.junit.Assert.*;
import org.junit.Test;
public class GoSymbolNameTest {
@Test
public void testParse() {
GoSymbolName sni = GoSymbolName.parse("internal/fmtsort.(*SortedMap).Len");
assertEquals("internal/fmtsort", sni.getPackagePath());
assertEquals("fmtsort", sni.getPackageName());
assertEquals("*SortedMap", sni.getRecieverString());
sni = GoSymbolName.parse("runtime..inittask");
assertEquals("runtime", sni.getPackagePath());
assertEquals("runtime", sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("runtime.addOneOpenDeferFrame.func1");
assertEquals("runtime", sni.getPackagePath());
assertEquals("runtime", sni.getPackageName());
assertNull(sni.getRecieverString());
// test dots in the packagepath string
sni = GoSymbolName
.parse("vendor/golang.org/x/text/unicode/norm.(*reorderBuffer).compose");
assertEquals("vendor/golang.org/x/text/unicode/norm", sni.getPackagePath());
assertEquals("norm", sni.getPackageName());
assertEquals("*reorderBuffer", sni.getRecieverString());
// test slashes/dots in the receiver string
sni = GoSymbolName.parse("sync/atomic.(*Pointer[net/http.response]).Store");
assertEquals("sync/atomic", sni.getPackagePath());
assertEquals("atomic", sni.getPackageName());
assertEquals("*Pointer[net/http.response]", sni.getRecieverString());
sni = GoSymbolName.parse("crypto/ecdsa.inverse[go.shape.*uint8]");
assertEquals("crypto/ecdsa", sni.getPackagePath());
assertEquals("ecdsa", sni.getPackageName());
assertNull(sni.getRecieverString());
sni =
GoSymbolName.parse("go:(*struct_{_runtime.gList;_runtime.n_int32_}).runtime.empty");
assertNull(sni.getPackagePath());
assertNull(sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("time.parseRFC3339[go.shape.[]uint8]");
assertEquals("time", sni.getPackagePath());
assertEquals("time", sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("sync/atomic.(*Pointer[interface_{}]).Load");
assertEquals("sync/atomic", sni.getPackagePath());
assertEquals("atomic", sni.getPackageName());
assertEquals("*Pointer[interface_{}]", sni.getRecieverString());
}
@Test
public void testParseTypeSymbol() {
GoSymbolName sni = GoSymbolName.parse("type:.eq.runtime/internal/atomic.Int64");
assertEquals("runtime/internal/atomic", sni.getPackagePath());
assertEquals("atomic", sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("type:.eq.struct_{_runtime.gList;_runtime.n_int32_}");
assertNull(sni.getPackagePath());
assertNull(sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("type:.eq.sync/atomic.Pointer[interface_{}]");
assertEquals("sync/atomic", sni.getPackagePath());
assertEquals("atomic", sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("type:.eq.[...]internal/cpu.option");
assertEquals("internal/cpu", sni.getPackagePath());
assertEquals("cpu", sni.getPackageName());
assertNull(sni.getRecieverString());
sni = GoSymbolName.parse("type:.eq.[39]vendor/golang.org/x/sys/cpu.option");
assertEquals("vendor/golang.org/x/sys/cpu", sni.getPackagePath());
assertEquals("cpu", sni.getPackageName());
assertNull(sni.getRecieverString());
}
}

View file

@ -1,191 +0,0 @@
/* ###
* 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.util.*;
import java.util.stream.Collectors;
import ghidra.app.cmd.comments.SetCommentCmd;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.decompiler.parallel.DecompilerCallback;
import ghidra.app.decompiler.parallel.ParallelDecompiler;
import ghidra.app.services.*;
import ghidra.app.util.bin.format.golang.GoConstants;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeBlockBasic;
import ghidra.program.model.symbol.*;
import ghidra.program.util.FunctionUtility;
import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class GolangDuffFixupAnalyzer extends AbstractAnalyzer {
private final static String NAME = "Golang Duff Function Fixup";
private final static String DESCRIPTION = """
Propagates function signature information from the base runtime.duffcopy \
and runtime.duffzero functions to the other entry points that were discovered \
during analysis.""";
private Program program;
private TaskMonitor monitor;
private MessageLog log;
public GolangDuffFixupAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
setPriority(AnalysisPriority.FUNCTION_ANALYSIS.after());
setDefaultEnablement(true);
}
@Override
public boolean canAnalyze(Program program) {
return GoConstants.GOLANG_CSPEC_NAME.equals(
program.getCompilerSpec().getCompilerSpecDescription().getCompilerSpecName());
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
this.program = program;
this.monitor = monitor;
this.log = log;
Symbol duffzeroSym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffzero");
Function duffzeroFunc = duffzeroSym != null ? (Function) duffzeroSym.getObject() : null;
Symbol duffcopySym = SymbolUtilities.getUniqueSymbol(program, "runtime.duffcopy");
Function duffcopyFunc = duffcopySym != null ? (Function) duffcopySym.getObject() : null;
List<Function> funcs = new ArrayList<>();
if (duffzeroFunc != null && duffzeroFunc.getCallingConvention() != null) {
funcs.add(duffzeroFunc);
}
if (duffcopyFunc != null && duffcopyFunc.getCallingConvention() != null) {
funcs.add(duffcopyFunc);
}
if (funcs.isEmpty()) {
return true;
}
Map<Address, AddressSetView> map = getFunctionActualRanges(funcs);
if (duffzeroFunc != null) {
updateDuffFuncs(duffzeroFunc, map.get(duffzeroFunc.getEntryPoint()));
}
if (duffcopyFunc != null) {
updateDuffFuncs(duffcopyFunc, map.get(duffcopyFunc.getEntryPoint()));
}
return true;
}
/**
* Copy details from the base duff function to any other unnamed functions that start within
* the base duff function's range.
*
* @param duffFunc base duff function
* @param duffFuncBody the addresses the base function occupies
*/
private void updateDuffFuncs(Function duffFunc, AddressSetView duffFuncBody) {
if (duffFunc == null || duffFuncBody == null) {
return;
}
String duffComment = program.getListing()
.getCodeUnitAt(duffFunc.getEntryPoint())
.getComment(CodeUnit.PLATE_COMMENT);
for (FunctionIterator funcIt =
program.getFunctionManager().getFunctions(duffFuncBody, true); funcIt.hasNext();) {
Function func = funcIt.next();
if (!FunctionUtility.isDefaultFunctionName(func)) {
continue;
}
try {
func.setName(duffFunc.getName() + "_" + func.getEntryPoint(), SourceType.ANALYSIS);
func.updateFunction(duffFunc.getCallingConventionName(), duffFunc.getReturn(),
Arrays.asList(duffFunc.getParameters()),
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
if (duffComment != null && !duffComment.isBlank()) {
new SetCommentCmd(func.getEntryPoint(), CodeUnit.PLATE_COMMENT, duffComment)
.applyTo(program);
}
}
catch (DuplicateNameException | InvalidInputException e) {
log.appendMsg("Error updating duff functions");
log.appendException(e);
}
}
}
private void configureDecompiler(DecompInterface decompiler) {
decompiler.toggleCCode(false); //only need syntax tree
decompiler.toggleSyntaxTree(true); // Produce syntax tree
decompiler.setSimplificationStyle("normalize");
}
record HighFunctionAddresses(Address functionEntry, AddressSetView functionAddresses) {}
/**
* Returns the addresses that a function occupies (as determined by the decompiler instead of
* the disassembler).
*
* @param funcs list of functions
* @return map of function entry point and addresses for that function
*/
private Map<Address, AddressSetView> getFunctionActualRanges(List<Function> funcs) {
DecompilerCallback<HighFunctionAddresses> callback =
new DecompilerCallback<>(program, this::configureDecompiler) {
@Override
public HighFunctionAddresses process(DecompileResults results, TaskMonitor tMonitor)
throws Exception {
tMonitor.checkCancelled();
if (results == null) {
return null;
}
Function func = results.getFunction();
HighFunction highFunc = results.getHighFunction();
if (func == null || highFunc == null) {
return null;
}
AddressSet funcAddrs = new AddressSet();
for (PcodeBlockBasic bb : highFunc.getBasicBlocks()) {
funcAddrs.add(bb.getStart(), bb.getStop());
}
return new HighFunctionAddresses(func.getEntryPoint(), funcAddrs);
}
};
try {
List<HighFunctionAddresses> funcAddresses =
ParallelDecompiler.decompileFunctions(callback, funcs, monitor);
Map<Address, AddressSetView> results = funcAddresses.stream()
.collect(
Collectors.toMap(hfa -> hfa.functionEntry, hfa -> hfa.functionAddresses));
return results;
}
catch (Exception e) {
Msg.error(this, "Error: could not decompile functions with ParallelDecompiler", e);
return Map.of();
}
finally {
callback.dispose();
}
}
}

View file

@ -19,8 +19,9 @@ import java.io.IOException;
import java.util.List;
import java.util.TreeMap;
import ghidra.app.plugin.core.analysis.AnalysisState;
import ghidra.app.plugin.core.analysis.AnalysisStateInfo;
import ghidra.app.plugin.core.analysis.TransientProgramProperties;
import ghidra.app.plugin.core.analysis.TransientProgramProperties.PropertyValueSupplier;
import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE;
import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
@ -31,10 +32,10 @@ import ghidra.util.SystemUtilities;
/**
* This class is used to cache the {@link DexHeader} which holds constant pool and method information.
* These do not change for a given .dex Program and can be shared (by this {@link AnalysisState})
* between plug-ins that need to do analysis.
* These do not change for a given .dex Program and can be shared between plug-ins that need to
* do analysis.
*/
final public class DexAnalysisState implements AnalysisState {
final public class DexAnalysisState {
private Program program;
private DexHeader header; // Collection of raw records parsed from .dex file
@ -100,33 +101,39 @@ final public class DexAnalysisState implements AnalysisState {
}
/**
* Return persistent <code>DexAnalysisState</code> which corresponds to the specified program instance.
* Return shared/persistent {@link DexAnalysisState} which corresponds to the
* specified program instance.
*
* @param program is the specified program instance
* @return <code>DexAnalysisState</code> for specified program instance
* @throws IOException if there are problems during construction of the state object
*/
public synchronized static DexAnalysisState getState(Program program) throws IOException {
DexAnalysisState analysisState =
AnalysisStateInfo.getAnalysisState(program, DexAnalysisState.class);
if (analysisState == null) {
DexHeader dexHeader = DexHeaderFactory.getDexHeader(program);
analysisState = new DexAnalysisState(program, dexHeader);
AnalysisStateInfo.putAnalysisState(program, analysisState);
}
return analysisState;
return TransientProgramProperties.getProperty(program, DexAnalysisState.class,
SCOPE.PROGRAM, DexAnalysisState.class, () -> {
DexHeader dexHeader = DexHeaderFactory.getDexHeader(program);
return new DexAnalysisState(program, dexHeader);
});
}
/**
* Return shared/persistent {@link DexAnalysisState} which corresponds to the
* specified program instance.
*
* @param program is the specified program instance
* @param address address of the dex header
* @return <code>DexAnalysisState</code> for specified program instance
* @throws IOException if there are problems during construction of the state object
*/
public static DexAnalysisState getState(Program program, Address address) throws IOException {
DexAnalysisState analysisState =
AnalysisStateInfo.getAnalysisState(program, DexAnalysisState.class);
if (SystemUtilities.isInDevelopmentMode()) {
analysisState = null; //always generate when in debug mode
}
if (analysisState == null) {
PropertyValueSupplier<DexAnalysisState, IOException> supplier = () -> {
DexHeader dexHeader = DexHeaderFactory.getDexHeader(program, address);
analysisState = new DexAnalysisState(program, dexHeader);
AnalysisStateInfo.putAnalysisState(program, analysisState);
}
return analysisState;
return new DexAnalysisState(program, dexHeader);
};
return !SystemUtilities.isInDevelopmentMode()
? TransientProgramProperties.getProperty(program, DexAnalysisState.class,
SCOPE.PROGRAM, DexAnalysisState.class, supplier)
: supplier.get();
}
}

View file

@ -21,6 +21,10 @@ package ghidra.util.task;
*/
public class UnknownProgressWrappingTaskMonitor extends WrappingTaskMonitor {
public UnknownProgressWrappingTaskMonitor(TaskMonitor delegate) {
this(delegate, 0);
}
public UnknownProgressWrappingTaskMonitor(TaskMonitor delegate, long startMaximum) {
super(delegate);
delegate.setMaximum(startMaximum);
@ -44,7 +48,8 @@ public class UnknownProgressWrappingTaskMonitor extends WrappingTaskMonitor {
long _75_percent = currentMaximum - (currentMaximum / 4);
if (progress > _75_percent) {
delegate.setMaximum(Math.max(progress, currentMaximum + currentMaximum / 4));
delegate.setMaximum(
Math.max(Math.max(progress, 4), currentMaximum + currentMaximum / 4));
}
}

View file

@ -48,4 +48,53 @@ public class UnknownProgressWrappingTaskMonitorTest extends AbstractGenericTest
}
@Test
public void testUPWTM_startAtZero() throws CancelledException {
UnknownProgressWrappingTaskMonitor upwtm =
new UnknownProgressWrappingTaskMonitor(new TaskMonitorAdapter(true) {
long max;
long progress;
@Override
public long getMaximum() {
return max;
}
@Override
public void setMaximum(long max) {
this.max = max;
}
@Override
public void initialize(long max) {
this.max = max;
progress = 0;
}
@Override
public void setProgress(long value) {
progress = value;
}
@Override
public void incrementProgress(long incrementAmount) {
progress += incrementAmount;
}
@Override
public long getProgress() {
return progress;
}
});
upwtm.initialize(0, "message");
assertEquals(0, upwtm.getProgress());
assertEquals(0, upwtm.getMaximum());
while (upwtm.getProgress() < 16) {
upwtm.increment();
assertTrue(upwtm.getMaximum() > upwtm.getProgress());
}
}
}

View file

@ -205,6 +205,37 @@ public class StringDataInstance {
return NULL_INSTANCE;
}
/**
* Formats a string value so that it is in the form of a symbol label.
*
* @param prefixStr data type prefix, see {@link AbstractStringDataType#getDefaultLabelPrefix()}
* @param str string value
* @param options display options
* @return string, suitable to be used as a label
*/
public static String makeStringLabel(String prefixStr, String str,
DataTypeDisplayOptions options) {
boolean needsUnderscore = false;
StringBuilder buffer = new StringBuilder();
for (int i = 0, strLength = str.length(); i < strLength &&
buffer.length() < options.getLabelStringLength();) {
int codePoint = str.codePointAt(i);
if (StringUtilities.isDisplayable(codePoint) && (codePoint != ' ')) {
if (needsUnderscore) {
buffer.append('_');
needsUnderscore = false;
}
buffer.appendCodePoint(codePoint);
}
else {
needsUnderscore = true;
// discard character
}
i += Character.charCount(codePoint);
}
return prefixStr + buffer.toString();
}
//-----------------------------------------------------------------------------
/**
* A {@link StringDataInstance} that represents a non-existent string.
@ -1017,25 +1048,7 @@ public class StringDataInstance {
return prefixStr;
}
boolean needsUnderscore = false;
StringBuilder buffer = new StringBuilder();
for (int i = 0, strLength = str.length(); i < strLength &&
buffer.length() < options.getLabelStringLength();) {
int codePoint = str.codePointAt(i);
if (StringUtilities.isDisplayable(codePoint) && (codePoint != ' ')) {
if (needsUnderscore) {
buffer.append('_');
needsUnderscore = false;
}
buffer.appendCodePoint(codePoint);
}
else {
needsUnderscore = true;
// discard character
}
i += Character.charCount(codePoint);
}
return prefixStr + buffer.toString();
return makeStringLabel(prefixStr, str, options);
}
public String getOffcutLabelString(String prefixStr, String abbrevPrefixStr, String defaultStr,

View file

@ -18,8 +18,8 @@ package ghidra.javaclass.format;
import java.io.IOException;
import java.util.HashMap;
import ghidra.app.plugin.core.analysis.AnalysisState;
import ghidra.app.plugin.core.analysis.AnalysisStateInfo;
import ghidra.app.plugin.core.analysis.TransientProgramProperties;
import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.program.model.address.*;
@ -32,10 +32,10 @@ import ghidra.util.Msg;
* Class for holding the {@link ClassFileJava} and {@link MethodInfoJava} in memory
* for a particular .class file Program. These describe the objects in the constant pool and
* signatures of individual methods. They are parsed directly from the .class
* file (and so can't really change) and are shared via this {@link AnalysisState} with
* any plug-in that needs to do p-code analysis.
* file (and so can't really change) and are shared with any plug-in that needs to do
* p-code analysis.
*/
public class ClassFileAnalysisState implements AnalysisState {
public class ClassFileAnalysisState {
private Program program;
private ClassFileJava classFile; // Constant-pool and method descriptions
@ -55,7 +55,7 @@ public class ClassFileAnalysisState implements AnalysisState {
}
/**
* @return the class file information {@link ClassFileJava} held by this {@link AnalysisState}
* @return the class file information {@link ClassFileJava}
*/
public ClassFileJava getClassFile() {
return classFile;
@ -100,17 +100,15 @@ public class ClassFileAnalysisState implements AnalysisState {
}
/**
* Return persistent <code>ClassFileAnalysisState</code> which corresponds to the specified program instance.
* @param program
* @return <code>ClassFileAnalysisState</code> for specified program instance
* Return persistent <code>ClassFileAnalysisState</code> which corresponds to the specified
* program instance.
*
* @param program {@link Program}
* @return shared/persistent {@link ClassFileAnalysisState} for specified program instance
* @throws IOException if error reading java class file metadata
*/
public static synchronized ClassFileAnalysisState getState(Program program) throws IOException {
ClassFileAnalysisState analysisState =
AnalysisStateInfo.getAnalysisState(program, ClassFileAnalysisState.class);
if (analysisState == null) {
analysisState = new ClassFileAnalysisState(program);
AnalysisStateInfo.putAnalysisState(program, analysisState);
}
return analysisState;
return TransientProgramProperties.getProperty(program, ClassFileAnalysisState.class,
SCOPE.PROGRAM, ClassFileAnalysisState.class, () -> new ClassFileAnalysisState(program));
}
}