Ghidra 12.1.2 headless decompilation of Steam client version 1788652215: - decomp/linux64_steamclient.so, ubuntu12_32_steamclient.so, ubuntu12_32_steam: per-class pseudo-C (RTTI class recovery + Class::Method string naming), functions.tsv and strings.tsv - decomp/protos: 107 .proto files and descriptors.pb extracted from steamclient.so - decomp/steamui: prettified Library UI JS; steamclient_js_api.txt lists the SteamClient.* backend API it calls - tools/: decompile/recover/export scripts and proto extractor GCC exception-handler analysis is disabled: it truncated ~47% of function bodies on these binaries. Co-Authored-By: Claude Opus 5.5 <[email protected]>
63 lines
2.3 KiB
Java
63 lines
2.3 KiB
Java
// Names unnamed functions from embedded "Class::Method" strings (VPROF / assert
|
|
// labels). A function is renamed only when the string is referenced from that
|
|
// one function and the function references no other such string, which avoids
|
|
// mislabeling callers that inlined someone else's assert.
|
|
//@category Analysis
|
|
|
|
import java.util.*;
|
|
import java.util.regex.Pattern;
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.model.data.StringDataInstance;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.symbol.*;
|
|
import ghidra.program.util.DefinedDataIterator;
|
|
import ghidra.app.util.NamespaceUtils;
|
|
|
|
public class RecoverNamesFromStrings extends GhidraScript {
|
|
|
|
private static final Pattern QUALIFIED =
|
|
Pattern.compile("^[A-Za-z_]\\w*(::[A-Za-z_~]\\w*)+$");
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
Listing listing = currentProgram.getListing();
|
|
ReferenceManager refs = currentProgram.getReferenceManager();
|
|
Map<Function, Set<String>> namesByFunc = new HashMap<>();
|
|
Map<String, Set<Function>> funcsByName = new HashMap<>();
|
|
|
|
for (Data d : DefinedDataIterator.byDataInstance(currentProgram, StringDataInstance::isString)) {
|
|
String s = StringDataInstance.getStringDataInstance(d).getStringValue();
|
|
if (s == null || !QUALIFIED.matcher(s).matches()) {
|
|
continue;
|
|
}
|
|
for (Reference r : refs.getReferencesTo(d.getAddress())) {
|
|
Function f = listing.getFunctionContaining(r.getFromAddress());
|
|
if (f != null) {
|
|
namesByFunc.computeIfAbsent(f, k -> new HashSet<>()).add(s);
|
|
funcsByName.computeIfAbsent(s, k -> new HashSet<>()).add(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
int renamed = 0;
|
|
for (Map.Entry<Function, Set<String>> e : namesByFunc.entrySet()) {
|
|
Function f = e.getKey();
|
|
if (e.getValue().size() != 1 || f.getSymbol().getSource() != SourceType.DEFAULT) {
|
|
continue;
|
|
}
|
|
String full = e.getValue().iterator().next();
|
|
if (funcsByName.get(full).size() != 1) {
|
|
continue;
|
|
}
|
|
int split = full.lastIndexOf("::");
|
|
Namespace ns = NamespaceUtils.createNamespaceHierarchy(full.substring(0, split),
|
|
currentProgram.getGlobalNamespace(), currentProgram, SourceType.ANALYSIS);
|
|
f.setParentNamespace(ns);
|
|
f.setName(full.substring(split + 2), SourceType.ANALYSIS);
|
|
renamed++;
|
|
}
|
|
println("Renamed " + renamed + " functions from " + funcsByName.size() + " qualified-name strings");
|
|
}
|
|
}
|