Toolkit: pe-walker · pe-diff · rtti-dump · vtable-dump · pattern-scan · unwind-map
vtable-dump finds the virtual function tables of C++ classes in x64 Windows binaries built with MSVC and lists the function in every slot. In a stripped binary, vtables are the fastest way from "there is a class here" to "here is its code": every virtual method of every polymorphic class, grouped by class, with no disassembly involved.
Real output for the test fixture, built from tests/fixtures/sample.cpp. io::Buffer inherits from both io::Reader and io::Writer, so it has two vtables, one per base subobject:
$ vtable-dump --filter Buffer sample64.dll
[*] scanning for Complete Object Locators ...
found 14 COL(s)
[*] scanning for vtables that reference them ...
found 14 vtable(s); filtering by 'Buffer'
.?AUBuffer@io@@
vtable RVA : 0x00003208
slot count : 2
[ 0] fn @ RVA 0x00001010
[ 1] fn @ RVA 0x000011c0
.?AUBuffer@io@@
vtable RVA : 0x00003220
slot count : 3
[ 0] fn @ RVA 0x00001000
[ 1] fn @ RVA 0x000011f0
[ 2] fn @ RVA 0x00001160
[*] 2 of 14 vtable(s) matched the filterThe first table is the Reader view: the destructor and read(). The second is the Writer view: the destructor, write() and flush(). Jump to any of those RVAs in Ghidra, IDA or x64dbg to see the implementation.
MSVC places a pointer to the class's Complete Object Locator in the slot just before each vtable:
- Find every Complete Object Locator in
.rdata: signature1, and apSelffield equal to its own RVA (the same validation as rtti-dump). - Compute each COL's absolute address (
ImageBase + RVA) and scan.rdatafor 8-byte values equal to it. Each hit is the slot just before a vtable. - Walk forward while the entries point into an executable section; those are the virtual functions. Stop at the first null or non-code pointer.
Download a prebuilt binary for Windows x64, Linux x64 (statically linked) or macOS arm64 from the latest release, or build from source with CMake 3.15+ and any C++17 compiler:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build -C Release # optional: run the test suitevtable-dump [--max-slots N] [--filter|-f SUBSTRING] [--demangle|-d] [--color auto|always|never] <file.exe|file.dll>
| Flag | Effect |
|---|---|
--max-slots N |
Print at most N slots per vtable (default 32). The full slot count is still shown. |
--filter SUB, -f SUB |
Only show vtables whose class name, mangled or demangled, contains SUB (-f io::Buffer works). |
--demangle, -d |
Print io::Buffer instead of .?AUBuffer@io@@. |
--color WHEN |
auto (default) colors output only on a terminal; always and never force it. Setting NO_COLOR turns colors off. |
Exit status is 0 on success, 1 for an unreadable, malformed or non-x64 file, and 2 for a usage error.
rtti-dump --demangle target.exeshows the classes and their inheritance.vtable-dump --filter Widget target.exeshows where those classes' vtables live and what is in each slot.- In your disassembler, jump to a slot's RVA and name the function after its class.
- x64 only. 32-bit MSVC uses absolute pointers in RTTI and is rejected with a clear error.
- MSVC only. The Itanium ABI used by MinGW and Clang has no Complete Object Locator before the vtable.
- Needs RTTI. Classes compiled with
/GR-have no COL, so their vtables are not found. - Slot order is MSVC's. A base class's virtual functions keep their slot numbers in derived vtables; new virtual functions are appended.
ctest compares the output for the fixture DLL with golden files: all vtables, --filter, --max-slots, --demangle and colored output. It also checks that x86 input and missing arguments fail with the right exit codes. CI runs on Windows (MSVC), Linux (GCC) and macOS (Clang), and cross-builds with MinGW-w64. When the tool moved from <windows.h> to a portable PE header, its output was compared with the previous build on 250 System32 DLLs, and there were no differences. Header, section table and name reads are bounds-checked against the file.
MIT, see LICENSE.