Compare commits

...

3 commits

Author SHA1 Message Date
KeybadeBlox
0e84f9ab1f Refine for virtual method check in mangling script
We used the flimsy heuristic of a reference from non-executable memory
to try to guess at whether a reference was a vtable, but now we just
check whether it's been named as a vtable.
2026-02-11 21:14:46 -05:00
KeybadeBlox
53b0e82417 Include headless demangling in delink.sh
This means we get delinked objects with proper mangled names matching
our recompiled files.
2026-02-11 20:23:26 -05:00
KeybadeBlox
55046bf009 Better handle undefined types in mangling script 2026-02-11 20:17:22 -05:00
2 changed files with 55 additions and 20 deletions

View file

@ -65,8 +65,11 @@ delink() {
export MSYS_NO_PATHCONV=1 export MSYS_NO_PATHCONV=1
"$1/support/analyzeHeadless$suffix" "$2" "$3"\ "$1/support/analyzeHeadless$suffix" "$2" "$3"\
-readOnly\
-process default.xbe\ -process default.xbe\
-noanalysis\ -noanalysis\
-scriptPath ghidra_scripts\
-preScript MSVC7Mangle.java $4\
-postScript DelinkProgram.java\ -postScript DelinkProgram.java\
/exporter 'COFF relocatable object'\ /exporter 'COFF relocatable object'\
$(printf "/include-range %s " $4)\ $(printf "/include-range %s " $4)\

View file

@ -7,17 +7,20 @@
// permissive form (public, non-const, etc.). // permissive form (public, non-const, etc.).
// //
// Special symbol names like "operator new" or "scalar deleting destructor" // Special symbol names like "operator new" or "scalar deleting destructor"
// are given unique mangling. To properly demangle these, name them as they // are given unique mangling. To properly mangle these, name them as they
// appear in objdiff, replacing spaces with underscores, e.g. "operator_new" // appear in objdiff, replacing spaces with underscores, e.g. "operator_new"
// and "`scalar_deleting_destructor'" (notice the ` and '). // and "`scalar_deleting_destructor'" (notice the ` and ').
// //
// This script can be called in headless mode with the address ranges to mangle
// as arguments, e.g. 0x1234-0x5678. Any symbols referenced by functions being
// mangled will also be mangled in this mode (so that the references are
// correct if the mangling is done in preparation for exporting functions).
//
// @category Symbol // @category Symbol
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionSignature;
import ghidra.program.model.data.Array; import ghidra.program.model.data.Array;
import ghidra.program.model.data.BooleanDataType; import ghidra.program.model.data.BooleanDataType;
import ghidra.program.model.data.CharDataType; import ghidra.program.model.data.CharDataType;
@ -38,6 +41,7 @@ import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.data.Structure; import ghidra.program.model.data.Structure;
import ghidra.program.model.data.TerminatedUnicodeDataType; import ghidra.program.model.data.TerminatedUnicodeDataType;
import ghidra.program.model.data.TypeDef; import ghidra.program.model.data.TypeDef;
import ghidra.program.model.data.Undefined;
import ghidra.program.model.data.Union; import ghidra.program.model.data.Union;
import ghidra.program.model.data.UnsignedCharDataType; import ghidra.program.model.data.UnsignedCharDataType;
import ghidra.program.model.data.UnsignedIntegerDataType; import ghidra.program.model.data.UnsignedIntegerDataType;
@ -46,6 +50,9 @@ import ghidra.program.model.data.UnsignedLongLongDataType;
import ghidra.program.model.data.UnsignedShortDataType; import ghidra.program.model.data.UnsignedShortDataType;
import ghidra.program.model.data.VoidDataType; import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.data.WideCharDataType; import ghidra.program.model.data.WideCharDataType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionSignature;
import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SourceType;
@ -65,6 +72,22 @@ import java.util.zip.CRC32;
public class MSVC7Mangle extends GhidraScript{ public class MSVC7Mangle extends GhidraScript{
@Override @Override
public void run() throws Exception { public void run() throws Exception {
// Get selected ranges from arguments if invoked headless
if (isRunningHeadless()) {
final String[] args = getScriptArgs();
final AddressSet addr = new AddressSet();
for (int i = 0; i < args.length; i++) {
final String[] range = args[i].split("-");
addr.add(
currentAddress.getAddress(range[0]),
currentAddress.getAddress(range[1])
);
}
setCurrentSelection(addr);
}
final SymbolIterator iter = currentProgram.getSymbolTable() final SymbolIterator iter = currentProgram.getSymbolTable()
.getPrimarySymbolIterator(currentSelection, true); .getPrimarySymbolIterator(currentSelection, true);
@ -86,6 +109,9 @@ public class MSVC7Mangle extends GhidraScript{
s.setName(mangled, SourceType.USER_DEFINED); s.setName(mangled, SourceType.USER_DEFINED);
makeGlobal(s); makeGlobal(s);
} }
// TODO: in headless mode, also mangle everything
// referenced by functions
} }
} }
@ -238,24 +264,29 @@ public class MSVC7Mangle extends GhidraScript{
} }
private boolean isVirtual(final Function f) { private boolean isVirtual(final Function f) {
/* Attempt to determine whether a method is virtual /* Determine whether a method is virtual
We essentially try to figure out if any references are from a vtable We essentially check whether any references are from a vtable or a
by checking if they lie in non-executable memory, or from a scalar scalar deleting destructor.
deleting destructor.
*/ */
final Reference[] refs = getReferencesTo(f.getEntryPoint()); final Reference[] refs = getReferencesTo(f.getEntryPoint());
for (int i = 0; i < refs.length; i++) { for (int i = 0; i < refs.length; i++) {
final Address addr = refs[i].getFromAddress(); final Data data = getDataContaining (refs[i].getFromAddress());
final Optional<String> caller = Optional.ofNullable(getFunctionContaining(addr)) final Function func = getFunctionContaining(refs[i].getFromAddress());
.map(x -> x.getName(false));
if (data != null) {
final String name = getSymbolAt(data.getRoot()
.getAddress()).getName(false);
if ( if (
!getMemoryBlock(addr).isExecute() || name.equals("`vftable'") ||
caller.map(x -> x.equals("`scalar_deleting_destructor'")) name.startsWith("??_7")
.orElse(false) ||
caller.map(x -> x.startsWith("??_G")) // From mangled name
.orElse(false)
) return true; ) return true;
} else if (func != null) {
final String name = func.getName(false);
if (
name.equals("`scalar_deleting_destructor'") ||
name.startsWith("??_G")
) return true;
}
} }
return false; return false;
@ -305,7 +336,7 @@ public class MSVC7Mangle extends GhidraScript{
*/ */
if (t == null) throw new Exception ( if (t == null) throw new Exception (
"A data type was reported as null. Ensure that all " + "A data type was reported as null. Ensure that all " +
"data types in the demangled code/data have been " + "data types in the code/data to mangle have been " +
"defined." "defined."
); );
@ -336,7 +367,8 @@ public class MSVC7Mangle extends GhidraScript{
case Array a -> "PA" + mangleArrDims(a) + mangleType(arrType(a), dict); case Array a -> "PA" + mangleArrDims(a) + mangleType(arrType(a), dict);
case FunctionSignature f -> mangleFnType(f, dict); case FunctionSignature f -> mangleFnType(f, dict);
case TypeDef d -> mangleType(d.getBaseDataType(), dict); case TypeDef d -> mangleType(d.getBaseDataType(), dict);
case DefaultDataType _ -> throw new Exception ("Encountered data marked \"undefined\". All data types must be defined."); case DefaultDataType _ -> throw new Exception ("Encountered data marked \"undefined\". Ensure that all data types in the code/data to mangle have been defined.");
case Undefined _ -> throw new Exception ("Encountered data marked \"undefined\". Ensure that all data types in the code/data to mangle have been defined.");
default -> throw new Exception ("Unknown type \"" + t.getClass().getName() + "\""); default -> throw new Exception ("Unknown type \"" + t.getClass().getName() + "\"");
}; };
} }