From 6916d3a90c9508ac3be24a8f8cbfc15583e27a02 Mon Sep 17 00:00:00 2001 From: KeybadeBlox Date: Thu, 30 Apr 2026 14:51:31 -0400 Subject: [PATCH] Support name mangling method pointers ActSequence uses an array of method pointers, meaning we need to support mangling this obscure corner of the language. The name mangling code is admittedly feeling pretty messy, as this appears to require a lot of special casing. --- decompile/objdiff.json | 3 +- ghidra/ghidra_scripts/MSVC7Mangle.java | 115 ++++++++++++++++++------- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/decompile/objdiff.json b/decompile/objdiff.json index 49093d5..8000d87 100644 --- a/decompile/objdiff.json +++ b/decompile/objdiff.json @@ -30,8 +30,7 @@ }, "symbol_mappings": { "?Exec0Default__jumptable@CActSequence@@3EA": "$L1709", - "?Exec0Default__jumptargets@CActSequence@@3PAPAXA": "$L1710", - "?fSequenceMethods@CActSequence@@3PAP6EXPAU1@@ZA": "?fSequenceMethods@@3PAP8CActSequence@@AEXXZA" + "?Exec0Default__jumptargets@CActSequence@@3PAPAXA": "$L1710" } }, { diff --git a/ghidra/ghidra_scripts/MSVC7Mangle.java b/ghidra/ghidra_scripts/MSVC7Mangle.java index 317aba1..a7dba56 100644 --- a/ghidra/ghidra_scripts/MSVC7Mangle.java +++ b/ghidra/ghidra_scripts/MSVC7Mangle.java @@ -122,7 +122,7 @@ public class MSVC7Mangle extends GhidraScript { // Get mangled name final String mangled = switch (s.getObject()) { case Function f -> mangleFn (f); - case Data d -> mangleData(d, name); + case Data d -> mangleData(d, name, getClassNamespace(s)); default -> null; }; @@ -190,12 +190,12 @@ public class MSVC7Mangle extends GhidraScript { final ArrayList dict = new ArrayList<>(); - final List nameParts = Arrays.asList(nameRaw.split("::")); + final List nameParts = Arrays.asList(nameRaw.split("::")); Collections.reverse(nameParts); - final boolean isMethod = f.getCallingConventionName() - .equals("__thiscall") && - nameParts.size() >= 2; - final String name = mangleIdentifier(nameRaw, isMethod, f.getReturnType(), dict); + final boolean isMethod = f.getCallingConventionName() + .equals("__thiscall") && + nameParts.size() >= 2; + final String name = mangleIdentifier(nameRaw, isMethod, false, f.getReturnType(), dict); // Special methods with unique formats if (isMethod) { @@ -204,7 +204,7 @@ public class MSVC7Mangle extends GhidraScript { if (unqualified.equals( clsName)) { // Constructor return "?" + name + "QAE@" + - mangleArgs(f.getSignature(true), dict, nameRaw + "()") + + mangleArgs(f.getSignature(true), false, dict, nameRaw + "()") + "Z"; } else if (unqualified.equals("~" + clsName)) { // Destructor return "?" + name + (isVirtual(f) ? "UAE" : "QAE") + "@XZ"; @@ -218,6 +218,7 @@ public class MSVC7Mangle extends GhidraScript { private static String mangleIdentifier( final String ident, final boolean isMethod, + final boolean isMethodPtr, final DataType retType, // Function return type, nullable final List dict ) { @@ -231,7 +232,8 @@ public class MSVC7Mangle extends GhidraScript { names. */ // Break up names into their mangled order - final List parts = Arrays.asList(ident.split("::")); + List parts = Arrays.asList(ident.split("::")); + parts = parts.subList(isMethodPtr ? 1 : 0, parts.size()); Collections.reverse(parts); // Non-method special names @@ -386,8 +388,8 @@ public class MSVC7Mangle extends GhidraScript { ) throws Exception { /* Mangle everything in f but its name and visibility/linkage */ return mangleCallC(f) + - mangleType(f.getReturnType(), dict, loc) + - mangleArgs(f, dict, loc) + "Z"; + mangleType(f.getReturnType(), Optional.empty(), dict, loc) + + mangleArgs(f, false, dict, loc) + "Z"; } private static String mangleCallC(final FunctionSignature f) throws Exception { @@ -402,9 +404,10 @@ public class MSVC7Mangle extends GhidraScript { } private static String mangleType( - final DataType t, - final List dict, - final String loc + final DataType t, + final Optional classNamespace, + final List dict, + final String loc ) throws Exception { /* Mangle a data type in a function name All types are assumed to have no CV qualifiers. @@ -429,19 +432,23 @@ public class MSVC7Mangle extends GhidraScript { case DoubleDataType x -> "N"; case LongDoubleDataType x -> "O"; case Pointer p -> "P" + - (p.getDataType() instanceof FunctionSignature ? "6" : "A") + - mangleType(p.getDataType(), dict, loc); - case Union u -> "T" + mangleIdentifier(u.getName(), false, null, dict); - case Structure s -> "U" + mangleIdentifier(s.getName(), false, null, dict); - case Enum e -> "W4" + mangleIdentifier(e.getName(), false, null, dict); + (p.getDataType() instanceof FunctionSignature f ? ( + classNamespace.isPresent() ? "8" : "6" + ) : "A" + ) + mangleType(p.getDataType(), classNamespace, dict, loc); + case Union u -> "T" + mangleIdentifier(u.getName(), false, false, null, dict); + case Structure s -> "U" + mangleIdentifier(s.getName(), false, false, null, dict); + case Enum e -> "W4" + mangleIdentifier(e.getName(), false, false, null, dict); case VoidDataType x -> "X"; case LongLongDataType x -> "_J"; case UnsignedLongLongDataType x -> "_K"; case BooleanDataType x -> "_N"; case WideCharDataType x -> "_W"; - case Array a -> "PA" + mangleArrDims(a) + mangleType(arrType(a), dict, loc); - case FunctionSignature f -> mangleFnType(f, dict, "function typedef \"" + f.getName() + "\""); - case TypeDef d -> mangleType(d.getBaseDataType(), dict, "typedef \"" + d.getName() + "\""); + case Array a -> "PA" + mangleArrDims(a) + mangleType(arrType(a), classNamespace, dict, loc); + case FunctionSignature f -> classNamespace.isPresent() ? + mangleMethodPtr(f, classNamespace.get(), dict, loc) : + mangleFnType(f, dict, "function typedef \"" + f.getName() + "\""); + case TypeDef d -> mangleType(d.getBaseDataType(), classNamespace, dict, "typedef \"" + d.getName() + "\""); case DefaultDataType x -> throw new Exception ("Encountered data marked \"undefined\" at " + loc + ". Ensure that all data types in the code/data to mangle have been defined."); case Undefined x -> throw new Exception ("Encountered data marked \"undefined\" at " + loc + ". Ensure that all data types in the code/data to mangle have been defined."); default -> throw new Exception ("Unknown type \"" + t.getClass().getName() + "\" at " + loc); @@ -490,19 +497,33 @@ public class MSVC7Mangle extends GhidraScript { return t instanceof Array a_ ? arrType(a_) : t; } + private static String mangleMethodPtr( + final FunctionSignature f, + final Namespace classNamespace, + final List dict, + final String loc + ) throws Exception { + /* Mangle pointed-to part of a method pointer (i.e. after "3PAP8") */ + return classNamespace.getName() + "@@AE" + + mangleType(f.getReturnType(), Optional.empty(), dict, loc) + + mangleArgs(f, true, dict, loc) + "Z"; + } + private static String mangleArgs( final FunctionSignature f, + final boolean isMethodPtr, final List dict, final String loc ) throws Exception { /* Mangle the arguments for a function */ - final DataType[] args = Arrays.stream(f.getArguments()) - .map(ParameterDefinition::getDataType) - .toArray(DataType[]::new); + List args = Arrays.stream(f.getArguments()) + .map(ParameterDefinition::getDataType) + .toList(); + args = args.subList(isMethodPtr ? 1 : 0, args.size()); final ArrayList argDict = new ArrayList<>(); - if (args.length == 0) return "X"; + if (args.size() == 0) return "X"; else { // I try to be more expression-oriented, but not being // able to throw in lambdas, not having an error sum @@ -518,7 +539,7 @@ public class MSVC7Mangle extends GhidraScript { // helped us out here) String mangledArgs = ""; for (final DataType arg : args) { - final String mangledArg = mangleType(arg, dict, loc); + final String mangledArg = mangleType(arg, Optional.empty(), dict, loc); mangledArgs += mangledArg.length() == 1 ? mangledArg : @@ -528,9 +549,17 @@ public class MSVC7Mangle extends GhidraScript { } } + private Optional getClassNamespace(final Symbol s) { + /* Return the namespace of the class containing s, if there is one */ + final Namespace ns = s.getParentNamespace(); + return ns.getType() == Namespace.Type.CLASS ? Optional.of(ns) + : Optional.empty(); + } + private String mangleData( - final Data d, - final String name + final Data d, + final String name, + final Optional classNamespace ) throws Exception { /* Set the data symbol's name to its mangled version */ // String constants @@ -547,13 +576,19 @@ public class MSVC7Mangle extends GhidraScript { } final ArrayList dict = new ArrayList<>(); - final String ident = mangleIdentifier(name, false, null, dict); + final String ident = mangleIdentifier( + name, + false, + classNamespace.isPresent() && isFnPtr(d.getDataType(), "0x" + d.getAddress().toString()), + null, + dict + ); // vtable if (ident.startsWith("?_7")) return "?" + ident + "6B@"; return "?" + ident + "3" + - mangleType(d.getDataType(), dict, "0x" + d.getAddress().toString()) + + mangleType(d.getDataType(), classNamespace, dict, "0x" + d.getAddress().toString()) + "A"; } @@ -625,6 +660,26 @@ public class MSVC7Mangle extends GhidraScript { return (int)crc.getValue() ^ 0xFFFFFFFF; } + private static boolean isFnPtr( + final DataType t, + final String loc + ) throws Exception { + /* Determine whether the type is a function pointer (or wraps one) */ + if (t == null) throw new Exception ( + "A data type at " + loc + " was reported as null. " + + "Ensure that all data types in the code/data to " + + "mangle have been defined." + ); + + return switch(t) { + case Pointer p -> isFnPtr(p.getDataType() , loc); + case Array a -> isFnPtr(arrType(a) , loc); + case TypeDef d -> isFnPtr(d.getBaseDataType(), loc); + case FunctionSignature f -> true; + default -> false; + }; + } + private void mangleRefs( final Function f, final Set seenSymbols