Upgrade function dumper for cc

This commit is contained in:
Guus Waals 2025-06-01 22:46:30 +08:00
parent ac92d40671
commit f6d2ec6efb
2 changed files with 129 additions and 21 deletions

View File

@ -46,17 +46,76 @@ public class FunctionDatabase implements AutoCloseable {
}
}
public static enum CallingConvention {
Cdecl(0),
Stdcall(1),
Fastcall(2);
private final int value;
CallingConvention(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static CallingConvention fromValue(int value) {
for (CallingConvention conv : CallingConvention.values()) {
if (conv.value == value) {
return conv;
}
}
throw new IllegalArgumentException("Unknown calling convention value: " + value);
}
public static CallingConvention fromString(String convStr) {
if (convStr == null) {
return Cdecl; // Default
}
String lower = convStr.toLowerCase();
if (lower.contains("fastcall")) {
return Fastcall;
} else if (lower.contains("stdcall")) {
return Stdcall;
} else {
return Cdecl; // Default
}
}
@Override
public String toString() {
switch (this) {
case Fastcall:
return "fastcall";
case Stdcall:
return "stdcall";
case Cdecl:
default:
return "cdecl";
}
}
}
public static class FunctionEntry {
public Address address;
public String name;
public File file;
public Type type;
public CallingConvention callingConvention;
public FunctionEntry(Address address, String name, File file, Type type) {
this(address, name, file, type, CallingConvention.Cdecl);
}
public FunctionEntry(Address address, String name, File file, Type type, CallingConvention callingConvention) {
this.address = address;
this.name = name;
this.file = file;
this.type = type;
this.callingConvention = callingConvention;
}
}
@ -84,6 +143,7 @@ public class FunctionDatabase implements AutoCloseable {
private PreparedStatement findByAddressFunctions;
private PreparedStatement findByAddressImports;
private PreparedStatement insertOrReplaceFunctions;
private PreparedStatement insertOrReplaceImports;
private PreparedStatement deleteByFilepathFunctions;
private PreparedStatement deleteByFilepathImports;
private PreparedStatement loadAllFunctions;
@ -151,19 +211,21 @@ public class FunctionDatabase implements AutoCloseable {
private void prepareCachedStatements() throws SQLException {
// Find by name
findByNameFunctions = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Functions WHERE name = ?");
"SELECT filepath, name, address, type, calling_convention FROM Functions WHERE name = ?");
findByNameImports = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Imports WHERE name = ?");
"SELECT filepath, name, address, type, calling_convention FROM Imports WHERE name = ?");
// Find by address
findByAddressFunctions = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Functions WHERE address = ?");
"SELECT filepath, name, address, type, calling_convention FROM Functions WHERE address = ?");
findByAddressImports = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Imports WHERE address = ?");
"SELECT filepath, name, address, type, calling_convention FROM Imports WHERE address = ?");
// Insert or replace
insertOrReplaceFunctions = connection.prepareStatement(
"INSERT OR REPLACE INTO Functions (filepath, name, address, type) VALUES (?, ?, ?, ?)");
"INSERT OR REPLACE INTO Functions (filepath, name, address, type, calling_convention) VALUES (?, ?, ?, ?, ?)");
insertOrReplaceImports = connection.prepareStatement(
"INSERT OR REPLACE INTO Imports (filepath, name, address, type, calling_convention) VALUES (?, ?, ?, ?, ?)");
// Delete by filepath
deleteByFilepathFunctions = connection.prepareStatement(
@ -173,9 +235,9 @@ public class FunctionDatabase implements AutoCloseable {
// Load all entries
loadAllFunctions = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Functions");
"SELECT filepath, name, address, type, calling_convention FROM Functions");
loadAllImports = connection.prepareStatement(
"SELECT filepath, name, address, type FROM Imports");
"SELECT filepath, name, address, type, calling_convention FROM Imports");
// Global statements
findByNameGlobals = connection.prepareStatement(
@ -201,6 +263,8 @@ public class FunctionDatabase implements AutoCloseable {
findByAddressImports.close();
if (insertOrReplaceFunctions != null)
insertOrReplaceFunctions.close();
if (insertOrReplaceImports != null)
insertOrReplaceImports.close();
if (deleteByFilepathFunctions != null)
deleteByFilepathFunctions.close();
if (deleteByFilepathImports != null)
@ -250,13 +314,15 @@ public class FunctionDatabase implements AutoCloseable {
String name = rs.getString("name");
String addressStr = rs.getString("address");
int typeValue = rs.getInt("type");
int callingConventionValue = rs.getInt("calling_convention");
if (addressStr != null && !addressStr.isEmpty()) {
Address address = script.getCurrentProgram().getAddressFactory().getAddress(addressStr);
File file = new File(RemanConfig.INSTANCE.outputDir, filepath);
Type type = Type.fromValue(typeValue);
CallingConvention callingConvention = CallingConvention.fromValue(callingConventionValue);
return new FunctionEntry(address, name, file, type);
return new FunctionEntry(address, name, file, type, callingConvention);
}
return null;
}
@ -267,7 +333,8 @@ public class FunctionDatabase implements AutoCloseable {
filepath TEXT,
name TEXT,
address TEXT,
type INTEGER,
type INTEGER DEFAULT 0,
calling_convention INTEGER DEFAULT 0,
PRIMARY KEY (name, filepath)
)""";
@ -276,7 +343,8 @@ public class FunctionDatabase implements AutoCloseable {
filepath TEXT,
name TEXT,
address TEXT,
type INTEGER,
type INTEGER DEFAULT 0,
calling_convention INTEGER DEFAULT 0,
PRIMARY KEY (name, filepath)
)""";
@ -377,15 +445,40 @@ public class FunctionDatabase implements AutoCloseable {
insertOrReplaceFunctions.setString(2, entry.name);
insertOrReplaceFunctions.setString(3, entry.address.toString());
insertOrReplaceFunctions.setInt(4, entry.type.getValue());
insertOrReplaceFunctions.setInt(5, entry.callingConvention.getValue());
insertOrReplaceFunctions.executeUpdate();
script.println("Added/updated entry: " + entry.name + " at " + entry.address + " in " + relativePath);
script.println("Added/updated entry: " + entry.name + " at " + entry.address + " in " + relativePath
+ " (calling convention: " + entry.callingConvention + ")");
} catch (SQLException e) {
script.println("Error adding entry: " + e.getMessage());
throw new Exception("Failed to add entry", e);
}
}
// Helper method to add import entry
public void addImportEntry(FunctionEntry entry) throws Exception {
ensureConnection();
String relativePath = new File(RemanConfig.INSTANCE.outputDir).toPath()
.relativize(entry.file.toPath()).toString().replace('\\', '/');
try {
insertOrReplaceImports.setString(1, relativePath);
insertOrReplaceImports.setString(2, entry.name);
insertOrReplaceImports.setString(3, entry.address.toString());
insertOrReplaceImports.setInt(4, entry.type.getValue());
insertOrReplaceImports.setInt(5, entry.callingConvention.getValue());
insertOrReplaceImports.executeUpdate();
script.println("Added/updated import entry: " + entry.name + " at " + entry.address + " in " + relativePath
+ " (calling convention: " + entry.callingConvention + ")");
} catch (SQLException e) {
script.println("Error adding import entry: " + e.getMessage());
throw new Exception("Failed to add import entry", e);
}
}
// Helper method to remove entry by file path
public void removeEntryAt(String filePath) throws Exception {
ensureConnection();

View File

@ -131,6 +131,11 @@ public class FunctionDumper {
File f4 = new File(RemanConfig.INSTANCE.dirDecompStub, fileName);
script.println("Generating function stub for " + externalFunction.getName() + " => " + f4.toString());
// Extract calling convention from Ghidra function
String callingConventionName = externalFunction.getCallingConventionName();
FunctionDatabase.CallingConvention callingConvention = FunctionDatabase.CallingConvention.fromString(callingConventionName);
script.println("Detected calling convention: " + callingConventionName + " -> " + callingConvention);
try (PrintWriter writer2 = new PrintWriter(f4, "UTF-8")) {
writer2.println("// AUTO-GENERATED FILE!!!!");
writer2.println("// This function has yet to be decompiled using 'Dump Current Function' in ghidra");
@ -141,14 +146,13 @@ public class FunctionDumper {
writer2.println("#include <gh_global.h>");
writer2.println();
writer2.println("// " + externalFunction.getEntryPoint());
writer2.println("// " + externalFunction.getName());
writer2.println("// " + externalFunction.getName() + "(" + callingConvention + ")");
// Parse function signature to extract calling convention, return type, and
// parameters
String signature = externalFunction.getSignature().getPrototypeString(false);
signature = signature.replace(externalFunction.getName(), sanitizedExtFunctionName);
script.println("Santized Signature: " + signature);
String callingConvention = externalFunction.getCallingConventionName();
String returnType = externalFunction.getReturnType().toString();
// Generate function stub using appropriate forwarding function
@ -156,8 +160,10 @@ public class FunctionDumper {
// Determine which stub function to use based on calling convention
String stubFunction;
if (callingConvention != null && callingConvention.equals("__stdcall")) {
if (callingConvention == FunctionDatabase.CallingConvention.Stdcall) {
stubFunction = "gh_stub_impl_stdcall";
} else if (callingConvention == FunctionDatabase.CallingConvention.Fastcall) {
stubFunction = "gh_stub_impl_fastcall";
} else {
// Default to cdecl for most cases
stubFunction = "gh_stub_impl_cdecl";
@ -189,9 +195,9 @@ public class FunctionDumper {
createdFile = true;
}
// Add stub function to database
// Add stub function to database with calling convention
FunctionDatabase.FunctionEntry newEntry = new FunctionDatabase.FunctionEntry(externalFunction.getEntryPoint(),
externalFunction.getName(), f4, FunctionDatabase.Type.Stub);
externalFunction.getName(), f4, FunctionDatabase.Type.Stub, callingConvention);
functionDatabase.addEntryAt(newEntry);
}
@ -204,6 +210,11 @@ public class FunctionDumper {
List<FunctionDatabase.FunctionEntry> entries = functionDatabase.findEntriesByAddress(entrypoint);
FunctionDatabase.Type targetType = FunctionDatabase.Type.Auto;
// Extract calling convention from Ghidra function
String callingConventionName = function.getCallingConventionName();
FunctionDatabase.CallingConvention callingConvention = FunctionDatabase.CallingConvention.fromString(callingConventionName);
script.println("Detected calling convention: " + callingConventionName + " -> " + callingConvention);
// Handle forceFixType flag
if (forceFixType) {
targetType = FunctionDatabase.Type.Fix;
@ -271,9 +282,9 @@ public class FunctionDumper {
File f0 = targetFilename;
script.println("Processing " + function.getName() + " => " + f0.toString());
// Update database
// Update database with calling convention
FunctionDatabase.FunctionEntry newEntry = new FunctionDatabase.FunctionEntry(entrypoint, function.getName(), f0,
targetType);
targetType, callingConvention);
functionDatabase.addEntryAt(newEntry);
List<Function> externalFunctionCalls = new ArrayList<>();
@ -305,8 +316,8 @@ public class FunctionDumper {
// Parse preliminary line tokens
for (int i = 0; i < line.getNumTokens(); i++) {
ClangToken token = line.getToken(i);
if (token.getText().equals("__cdecl") || token.getText().equals("__thiscall")
|| token.getText().equals("__stdcall")) {
// Remove some of the calling conventions, keep __stdcall/fastcall
if (token.getText().equals("__cdecl") || token.getText().equals("__thiscall")) {
// Remove function declaration
continue;
}
@ -410,9 +421,13 @@ public class FunctionDumper {
String proto = externalFunction.getSignature().getPrototypeString(false);
String name = externalFunction.getName();
proto = proto.replace(name, Utils.sanitizeIdentifier(name));
// Add calling convention information to the comment
String callingConv = externalFunction.getCallingConventionName();
FunctionDatabase.CallingConvention conv = FunctionDatabase.CallingConvention.fromString(callingConv);
headers.add("" + proto
+ "; // " + externalFunction.getEntryPoint() + " // "
+ name);
+ name + " // " + conv);
}
for (String header : headers) {