From 639a428c1db39f72298ea6d6330f881b27dcbe18 Mon Sep 17 00:00:00 2001 From: Jackson Atassi Date: Sun, 6 Sep 2026 15:00:05 -0600 Subject: [PATCH] SDKtoGhidra: apply the XML's calling convention when importing functions ImportSporeSDK.java read each function's calling convention from the function's existing convention, which is always "unknown" after Ghidra's auto-analysis, and ignored the CONVENTION attribute the XML carries on 3,662 thiscall and 203 stdcall definitions. The this-in-ECX branch in createParameters therefore never ran: "this" landed at stack offset 4 and every stack argument of a __thiscall function was shifted by four bytes. Editors::EditorModel::SetColor labelled its real "index" argument as "this", and the decompiler read the real "this" as in_ECX. Two changes: - processFunctionDef stores the convention through FunctionDefinition.setCallingConvention with Ghidra's own names (__thiscall, __stdcall, ...). The deprecated setGenericCallingConvention stored the bare enum name ("thiscall"), which no compiler spec recognises, so every definition read as an unknown convention. This also fixes calls through vftable slots, which the decompiler resolves from the definition's convention. - setSignature passes the definition's convention into updateFunction when the compiler spec knows it, and keeps the existing one otherwise. Custom storage is kept, because the functions live in Ghidra's global namespace with "::" in their names and dynamic storage would lose the typed "this". Tested against Ghidra 12.1.3 with SporeGhidra_march2017.xml: SetColor decompiles to "this->mColors[index].r = color.r", App::Property:: GetValueInt32 reads "this->", and float-returning functions still return in st0. --- SDKtoGhidra/GhidraScript/ImportSporeSDK.java | 37 ++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/SDKtoGhidra/GhidraScript/ImportSporeSDK.java b/SDKtoGhidra/GhidraScript/ImportSporeSDK.java index d0e507d5..c6f31e39 100644 --- a/SDKtoGhidra/GhidraScript/ImportSporeSDK.java +++ b/SDKtoGhidra/GhidraScript/ImportSporeSDK.java @@ -280,15 +280,19 @@ else if (s.getSymbolType() == SymbolType.FUNCTION) { } } - private static GenericCallingConvention getCallingConvention(String text) { + // Maps the XML's CONVENTION attribute to the name Ghidra's compiler specs use. + // The deprecated FunctionDefinition.setGenericCallingConvention stores the bare enum + // name ("thiscall"), which no compiler spec recognises, so the definition reads as an + // unknown convention and the this-in-ECX branch in createParameters never runs. + private static String getCallingConvention(String text) { switch (text) { - case "cdecl": return GenericCallingConvention.cdecl; - case "fastcall": return GenericCallingConvention.fastcall; - case "stdcall": return GenericCallingConvention.stdcall; - case "thiscall": return GenericCallingConvention.thiscall; - case "vectorcall": return GenericCallingConvention.vectorcall; - case "unknown": return GenericCallingConvention.unknown; + case "cdecl": return CompilerSpec.CALLING_CONVENTION_cdecl; + case "fastcall": return CompilerSpec.CALLING_CONVENTION_fastcall; + case "stdcall": return CompilerSpec.CALLING_CONVENTION_stdcall; + case "thiscall": return CompilerSpec.CALLING_CONVENTION_thiscall; + case "vectorcall": return CompilerSpec.CALLING_CONVENTION_vectorcall; + case "unknown": return CompilerSpec.CALLING_CONVENTION_unknown; default: return null; } } @@ -574,7 +578,15 @@ private static void removeCodeSymbol(SymbolTable symbolTable, Address address, S } } + // The signature carries the convention the XML declared. Use it when the compiler spec + // knows it; otherwise keep whatever the function already has (after auto-analysis that + // is "unknown"). private static String getCallingConvention(Function function, FunctionSignature signature, CompilerSpec compilerSpec) { + String name = signature.getCallingConventionName(); + if (name != null && !signature.hasUnknownCallingConventionName() + && compilerSpec.getCallingConvention(name) != null) { + return name; + } return function.getCallingConventionName(); } @@ -712,12 +724,17 @@ private boolean processFunctionDef(XmlTreeNode root, boolean firstPass) { if (element.hasAttribute("CONVENTION")) { callingConvention = element.getAttribute("CONVENTION"); } - GenericCallingConvention convention = getCallingConvention(callingConvention); + String convention = getCallingConvention(callingConvention); if (convention == null) { - throw new IllegalArgumentException("Unknown calling convention '" + convention + "' in function " + path + "/" + name); + throw new IllegalArgumentException("Unknown calling convention '" + callingConvention + "' in function " + path + "/" + name); + } + try { + fd.setCallingConvention(convention); + } + catch (InvalidInputException e) { + throw new IllegalArgumentException("Calling convention '" + convention + "' rejected for function " + path + "/" + name, e); } - fd.setGenericCallingConvention​(convention); XmlTreeNode node = root.getChild("RETURN_TYPE"); if (node != null) {