When to use
Pick this skill when the task is understanding a binary's C++ object model,
not ordinary function-level disassembly/decompilation:
- listing the classes a binary defines (with their direct base class)
- reading a class's vtable layout (method name, address, vtable slot)
- walking a base/derived relationship (single inheritance)
For ordinary function analysis, use disassembly or decompiler instead —
this skill is specifically about the class-level structure radare2's RTTI
recovery produces.
The two "classes" concepts in radare2 — use the right one
radare2 has two unrelated things both called "classes":
icj (RBinClass) — a bin-format reader for Java .class,
Objective-C, and .NET class tables, populated at load time by the matching
bin plugin. Structurally empty for every native PE/ELF binary,
regardless of whether it has C++ classes. Never use it here.
aclj (anal-side RAnalClass) — a separate, sdb-backed class
database populated by avrr (vtable search + MSVC/Itanium RTTI parsing).
This is what classes/class_methods are built on.
There is also no acj command — the top-level ac dispatcher has no j
case; the JSON list verb is aclj (ac + l + j).
The avrr non-idempotency rule (read this before touching raw commands)
radare2's own aaa already runs avrr once, internally, at file-open
time. A second explicit avrr call is confirmed NOT idempotent — it
duplicates every already-recovered class under a _1-suffixed name
(Animal → Animal, Animal_1, …). r2xsql's classes/class_methods
tables never issue avrr (or the raw av vtable search) themselves, on any
code path, including a repeat query in the same session — they only ever
read whatever aaa already recovered.
If you drop to raw radare2 commands (.avrr, .av) while working with
these tables, do not run avrr a second time in the same session — it
will corrupt the very data you're trying to read, and r2xsql cannot protect
you from a manually-triggered repeat call.
If aaa hasn't run yet in the current session (e.g. opened with
--no-analyze), both tables read as honestly empty — that is the
correct, non-broken answer, not a sign anything needs to be triggered.
Tables
| table |
source |
columns |
classes |
aclj |
name, base_name (first direct base only, NULL for a root class), vtable_addr (NULL if none) |
class_methods |
aclj (methods[] per class) |
class_name (FK to classes.name), name, addr, vtable_offset (NULL for a non-virtual method) |
No predicate is required or useful — no pushdown exists, and the recovered
class set is small (tens to low hundreds of classes on a realistic binary).
Common queries
-- every recovered class and its direct base
SELECT name, base_name FROM classes ORDER BY name;
-- one class's vtable layout, in slot order
SELECT name, printf('0x%x', addr) AS at, vtable_offset
FROM class_methods WHERE class_name = 'Dog' ORDER BY vtable_offset;
-- root classes only (no base -- likely where a hierarchy starts)
SELECT name, vtable_addr FROM classes WHERE base_name IS NULL;
-- every derived class of a known base
SELECT name FROM classes WHERE base_name = 'Animal';
-- class + method count, cheapest overview query
SELECT c.name, c.base_name, COUNT(m.name) AS n_methods
FROM classes c LEFT JOIN class_methods m ON m.class_name = c.name
GROUP BY c.name ORDER BY c.name;
-- filter out CRT-internal noise (see Caveats) to see only "your" classes
SELECT name, base_name FROM classes
WHERE name NOT LIKE 'std::%' AND name <> 'type_info';
Caveats
Uncurated by design. Every class aclj reports is visible, including
CRT-internal recoveries from a statically-linked runtime (type_info,
std::exception, std::bad_alloc, std::bad_array_new_length, …). This
mirrors how the family-shared names view stays uncurated — r2xsql does
not invent a curation boundary radare2 itself never drew. Filter
client-side if you want only a target program's own classes.
Multiple inheritance is not fully modeled. base_name is a single
nullable column, not a bases[] array. A class with more than one direct
base loses every base past the first here — the first base is genuinely
correct, just narrower than the raw recovery. There is no
classes_bases junction table in this version; if a query needs full
multi-base fidelity, drop to raw acllj <name> for that one class.
The Itanium ABI recovers materially less than MSVC — measured, not
assumed. Same C++ source, same radare2 (6.1.7), compiled both ways:
|
MSVC / PE |
Itanium / ELF (gcc 13.3) |
| classes recovered |
3 (Animal, Dog, Bird) |
2 — the abstract base Animal is MISSING |
base_name |
Dog→Animal, Bird→Animal |
empty — no inheritance recovered at all |
| methods per class |
3 |
4 |
This is radare2's avrr recovery, not an r2xsql defect — r2xsql surfaces
whatever avrr produces, and both backends agree with each other on both
platforms. Practical consequence on GCC/Clang targets: do not rely on
base_name to reconstruct a hierarchy, and do not treat a missing class as
proof it does not exist. Cross-check with vtable_addr and the raw
acllj output before drawing conclusions. On MSVC targets the full ground
truth (exact method counts, exact base relationships) holds.
Names may be demangled or still-mangled, depending on how much of a
given RTTI record radare2's demangler covers — match with LIKE '%ClassName%' rather than exact equality if a lookup unexpectedly returns
nothing.
RTTI-disabled builds (/GR-, or the Itanium equivalent) recover
nothing — an empty classes/class_methods result on a binary you know
has C++ classes usually means RTTI wasn't compiled in, not a bug in the
recovery.
1---2name: classes3description: Recover C++ class hierarchies, vtables, and RTTI from a compiled binary via r2xsql's classes and class_methods tables. Use when asked about class/inheritance structure, vtable layout, virtual method tables, or 'what C++ classes does this binary define'.4---56## When to use78Pick this skill when the task is understanding a binary's C++ object model,9not ordinary function-level disassembly/decompilation:1011- listing the classes a binary defines (with their direct base class)12- reading a class's vtable layout (method name, address, vtable slot)13- walking a base/derived relationship (single inheritance)1415For ordinary function analysis, use `disassembly` or `decompiler` instead —16this skill is specifically about the class-level structure radare2's RTTI17recovery produces.1819## The two "classes" concepts in radare2 — use the right one2021radare2 has two unrelated things both called "classes":2223- **`icj` (`RBinClass`)** — a bin-format reader for Java `.class`,24 Objective-C, and .NET class tables, populated at load time by the matching25 bin plugin. **Structurally empty for every native PE/ELF binary**,26 regardless of whether it has C++ classes. Never use it here.27- **`aclj` (anal-side `RAnalClass`)** — a separate, sdb-backed class28 database populated by `avrr` (vtable search + MSVC/Itanium RTTI parsing).29 **This is what `classes`/`class_methods` are built on.**3031There is also no `acj` command — the top-level `ac` dispatcher has no `j`32case; the JSON list verb is `aclj` (`ac` + `l` + `j`).3334## The `avrr` non-idempotency rule (read this before touching raw commands)3536radare2's own `aaa` already runs `avrr` **once**, internally, at file-open37time. **A second explicit `avrr` call is confirmed NOT idempotent** — it38duplicates every already-recovered class under a `_1`-suffixed name39(`Animal` → `Animal`, `Animal_1`, …). r2xsql's `classes`/`class_methods`40tables never issue `avrr` (or the raw `av` vtable search) themselves, on any41code path, including a repeat query in the same session — they only ever42read whatever `aaa` already recovered.4344**If you drop to raw radare2 commands (`.avrr`, `.av`) while working with45these tables, do not run `avrr` a second time in the same session** — it46will corrupt the very data you're trying to read, and r2xsql cannot protect47you from a manually-triggered repeat call.4849If `aaa` hasn't run yet in the current session (e.g. opened with50`--no-analyze`), both tables read as **honestly empty** — that is the51correct, non-broken answer, not a sign anything needs to be triggered.5253## Tables5455| table | source | columns |56|---|---|---|57| `classes` | `aclj` | `name`, `base_name` (first direct base only, NULL for a root class), `vtable_addr` (NULL if none) |58| `class_methods` | `aclj` (`methods[]` per class) | `class_name` (FK to `classes.name`), `name`, `addr`, `vtable_offset` (NULL for a non-virtual method) |5960No predicate is required or useful — no pushdown exists, and the recovered61class set is small (tens to low hundreds of classes on a realistic binary).6263## Common queries6465```sql66-- every recovered class and its direct base67SELECT name, base_name FROM classes ORDER BY name;6869-- one class's vtable layout, in slot order70SELECT name, printf('0x%x', addr) AS at, vtable_offset71FROM class_methods WHERE class_name = 'Dog' ORDER BY vtable_offset;7273-- root classes only (no base -- likely where a hierarchy starts)74SELECT name, vtable_addr FROM classes WHERE base_name IS NULL;7576-- every derived class of a known base77SELECT name FROM classes WHERE base_name = 'Animal';7879-- class + method count, cheapest overview query80SELECT c.name, c.base_name, COUNT(m.name) AS n_methods81FROM classes c LEFT JOIN class_methods m ON m.class_name = c.name82GROUP BY c.name ORDER BY c.name;8384-- filter out CRT-internal noise (see Caveats) to see only "your" classes85SELECT name, base_name FROM classes86WHERE name NOT LIKE 'std::%' AND name <> 'type_info';87```8889## Caveats9091- **Uncurated by design.** Every class `aclj` reports is visible, including92 CRT-internal recoveries from a statically-linked runtime (`type_info`,93 `std::exception`, `std::bad_alloc`, `std::bad_array_new_length`, …). This94 mirrors how the family-shared `names` view stays uncurated — r2xsql does95 not invent a curation boundary radare2 itself never drew. Filter96 client-side if you want only a target program's own classes.97- **Multiple inheritance is not fully modeled.** `base_name` is a single98 nullable column, not a `bases[]` array. A class with more than one direct99 base loses every base past the first here — the first base is genuinely100 correct, just narrower than the raw recovery. There is no101 `classes_bases` junction table in this version; if a query needs full102 multi-base fidelity, drop to raw `acllj <name>` for that one class.103- **The Itanium ABI recovers materially less than MSVC — measured, not104 assumed.** Same C++ source, same radare2 (6.1.7), compiled both ways:105106 | | MSVC / PE | Itanium / ELF (gcc 13.3) |107 |---|---|---|108 | classes recovered | 3 (`Animal`, `Dog`, `Bird`) | **2 — the abstract base `Animal` is MISSING** |109 | `base_name` | `Dog`→`Animal`, `Bird`→`Animal` | **empty — no inheritance recovered at all** |110 | methods per class | 3 | 4 |111112 This is radare2's `avrr` recovery, not an r2xsql defect — r2xsql surfaces113 whatever `avrr` produces, and both backends agree with each other on both114 platforms. **Practical consequence on GCC/Clang targets: do not rely on115 `base_name` to reconstruct a hierarchy, and do not treat a missing class as116 proof it does not exist.** Cross-check with `vtable_addr` and the raw117 `acllj` output before drawing conclusions. On MSVC targets the full ground118 truth (exact method counts, exact base relationships) holds.119- **Names may be demangled or still-mangled**, depending on how much of a120 given RTTI record radare2's demangler covers — match with `LIKE121 '%ClassName%'` rather than exact equality if a lookup unexpectedly returns122 nothing.123- **RTTI-disabled builds (`/GR-`, or the Itanium equivalent) recover124 nothing** — an empty `classes`/`class_methods` result on a binary you know125 has C++ classes usually means RTTI wasn't compiled in, not a bug in the126 recovery.