Skip to content

Commit e83ae24

Browse files
committed
fix(cli): add vtables export and fix % escaping in ExportBaseline.java
- Add exportVtables() to scan read-only memory sections for consecutive function pointer arrays and emit vtables.yaml - Fix escapeYaml to use s.contains("%") instead of s.equals("%"), which missed % characters inside strings causing invalid YAML output - Also fix s.startsWith("-") instead of s.contains("-") to avoid over-quoting hyphenated identifiers
1 parent 0d34323 commit e83ae24

1 file changed

Lines changed: 84 additions & 1 deletion

File tree

‎ghidra-agent-cli/ghidra-scripts/ExportBaseline.java‎

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ protected void run() throws Exception {
3131
exportFunctions(baseDir);
3232
exportCallgraph(baseDir);
3333
exportTypes(baseDir);
34+
exportVtables(baseDir);
3435
exportConstants(baseDir);
3536
exportStrings(baseDir);
3637
exportImports(baseDir);
@@ -126,6 +127,88 @@ private void exportCategoryTypes(StringBuilder yaml, Category cat, String prefix
126127
}
127128
}
128129

130+
private void exportVtables(Path dir) throws IOException {
131+
StringBuilder yaml = new StringBuilder();
132+
yaml.append("target: ").append(escapeYaml(target)).append("\n");
133+
yaml.append("vtables:\n");
134+
135+
int count = 0;
136+
// Walk all defined data looking for vtable-like structures:
137+
// pointers in read-only sections that point to functions.
138+
Memory mem = currentProgram.getMemory();
139+
Listing listing = currentProgram.getListing();
140+
141+
for (MemoryBlock block : mem.getBlocks()) {
142+
String blockName = block.getName().toLowerCase();
143+
// Look in .rodata, .const, __const, .data.rel.ro, etc.
144+
if (!blockName.contains("rodata") && !blockName.contains("const")
145+
&& !blockName.contains("data.rel.ro") && !blockName.contains(".data")) {
146+
continue;
147+
}
148+
149+
Address start = block.getStart();
150+
Address end = block.getEnd();
151+
DataIterator dataIter = listing.getDefinedData(start, end);
152+
153+
while (dataIter.hasNext()) {
154+
Data data = dataIter.next();
155+
// Look for pointer arrays where the first element points to a function
156+
if (!(data.getDataType() instanceof Pointer)) {
157+
continue;
158+
}
159+
160+
// Collect consecutive function pointers starting from this address
161+
List<String> funcAddrs = new ArrayList<>();
162+
Address scanAddr = data.getAddress();
163+
int ptrSize = data.getLength();
164+
165+
for (int offset = 0; offset < 64; offset++) { // scan up to 64 slots
166+
Data slot = listing.getDefinedDataAt(scanAddr.add(offset * ptrSize));
167+
if (slot == null || !(slot.getDataType() instanceof Pointer)) {
168+
break;
169+
}
170+
Object value = slot.getValue();
171+
if (value instanceof Address) {
172+
Address targetAddr = (Address) value;
173+
Function func = listing.getFunctionAt(targetAddr);
174+
if (func != null) {
175+
funcAddrs.add(targetAddr.toString());
176+
} else {
177+
break;
178+
}
179+
} else {
180+
break;
181+
}
182+
}
183+
184+
// A vtable needs at least 2 function pointers
185+
if (funcAddrs.size() >= 2) {
186+
String className = data.getLabel();
187+
if (className == null || className.equals(data.getAddress().toString())) {
188+
// Try to derive class name from surrounding symbols
189+
Symbol sym = currentProgram.getSymbolTable().getPrimarySymbol(data.getAddress());
190+
className = sym != null ? sym.getName() : "vtable_" + data.getAddress().toString();
191+
}
192+
193+
yaml.append(" - class: ").append(escapeYaml(className)).append("\n");
194+
yaml.append(" addr: \"").append(data.getAddress().toString()).append("\"\n");
195+
yaml.append(" entries:\n");
196+
for (String fa : funcAddrs) {
197+
yaml.append(" - \"").append(fa).append("\"\n");
198+
}
199+
count++;
200+
201+
// Skip past the entries we already emitted
202+
// (the outer DataIterator will also visit them, but they
203+
// won't start a new vtable because they're mid-array)
204+
}
205+
}
206+
}
207+
208+
Files.writeString(dir.resolve("vtables.yaml"), yaml.toString());
209+
println(" exported vtables.yaml (" + count + " vtables)");
210+
}
211+
129212
private void exportConstants(Path dir) throws IOException {
130213
StringBuilder yaml = new StringBuilder();
131214
yaml.append("target: ").append(escapeYaml(target)).append("\n");
@@ -223,7 +306,7 @@ private void exportImports(Path dir) throws IOException {
223306
private String escapeYaml(String s) {
224307
if (s == null) return "\"\"";
225308
// Check if string needs quoting (special chars, leading/trailing spaces, or control chars)
226-
if (s.contains(":") || s.contains("\"") || s.contains("'") || s.contains("\n") || s.startsWith(" ") || s.endsWith(" ") || s.contains("#") || s.contains("[") || s.contains("]") || s.contains("{") || s.contains("}") || s.contains(",") || s.contains("&") || s.contains("*") || s.contains("!") || s.contains("|") || s.contains(">") || s.contains("=") || s.contains("-") || s.equals("") || s.equals("~") || s.equals("@") || s.equals("%") || s.equals("^") || hasControlChars(s)) {
309+
if (s.contains(":") || s.contains("\"") || s.contains("'") || s.contains("\n") || s.startsWith(" ") || s.endsWith(" ") || s.contains("#") || s.contains("[") || s.contains("]") || s.contains("{") || s.contains("}") || s.contains(",") || s.contains("&") || s.contains("*") || s.contains("!") || s.contains("|") || s.contains(">") || s.contains("=") || s.startsWith("-") || s.equals("") || s.equals("~") || s.equals("@") || s.contains("%") || s.contains("^") || hasControlChars(s)) {
227310
return "\"" + escapeControlChars(s) + "\"";
228311
}
229312
return s;

0 commit comments

Comments
 (0)