@@ -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