https://tenzir.com/blog/production-debugging-bpftrace-uprobes/
https://shaharmike.com/cpp/vtable-part1/
|
|
$ # compile our code with debug symbols and start debugging using gdb
$ clang++ -std=c++14 -stdlib=libc++ -g main.cpp && gdb ./a.out
...
(gdb) # ask gdb to automatically demangle C++ symbols
(gdb) set print asm-demangle on
(gdb) set print demangle on
(gdb) # set breakpoint at main
(gdb) b main
Breakpoint 1 at 0x4009ac: file main.cpp, line 15.
(gdb) run
Starting program: /home/shmike/cpp/a.out
Breakpoint 1, main () at main.cpp:15
15 Parent p1, p2;
(gdb) # skip to next line
(gdb) n
16 Derived d1, d2;
(gdb) # skip to next line
(gdb) n
18 std::cout << "done" << std::endl;
(gdb) # print p1, p2, d1, d2 - we'll talk about what the output means soon
(gdb) p p1
$1 = {_vptr$Parent = 0x400bb8 <vtable for Parent+16>}
(gdb) p p2
$2 = {_vptr$Parent = 0x400bb8 <vtable for Parent+16>}
(gdb) p d1
$3 = {<Parent> = {_vptr$Parent = 0x400b50 <vtable for Derived+16>}, <No data fields>}
(gdb) p d2
$4 = {<Parent> = {_vptr$Parent = 0x400b50 <vtable for Derived+16>}, <No data fields>}
Here’s what we learned from the above:
- Even though the classes have no data members, there’s a hidden pointer to a vtable;
- vtable for
p1
andp2
is the same. vtables are static data per-type; d1
andd2
inherit a vtable-pointer fromParent
which points toDerived
’s vtable;- All vtables point to an offset of 16 (0x10) bytes into the vtable. We’ll also discuss this later.
Let’s continue with our gdb session to see the contents of the vtables. I will use the x
command, which dumps memory to the screen. I ask it to print 300 bytes in hex format, starting at 0x400b40. Why this address? Because above we saw that the vtable pointer points to 0x400b50, and the symbol for that address is vtable for Derived+16
(16 == 0x10).
(gdb) x/300xb 0x400b40
0x400b40 <vtable for Derived>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400b48 <vtable for Derived+8>: 0x90 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400b50 <vtable for Derived+16>: 0x80 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
0x400b58 <vtable for Derived+24>: 0x90 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
0x400b60 <typeinfo name for Derived>: 0x37 0x44 0x65 0x72 0x69 0x76 0x65 0x64
0x400b68 <typeinfo name for Derived+8>: 0x00 0x36 0x50 0x61 0x72 0x65 0x6e 0x74
0x400b70 <typeinfo name for Parent+7>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400b78 <typeinfo for Parent>: 0x90 0x20 0x60 0x00 0x00 0x00 0x00 0x00
0x400b80 <typeinfo for Parent+8>: 0x69 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400b88: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400b90 <typeinfo for Derived>: 0x10 0x22 0x60 0x00 0x00 0x00 0x00 0x00
0x400b98 <typeinfo for Derived+8>: 0x60 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400ba0 <typeinfo for Derived+16>: 0x78 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400ba8 <vtable for Parent>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400bb0 <vtable for Parent+8>: 0x78 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400bb8 <vtable for Parent+16>: 0xa0 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
0x400bc0 <vtable for Parent+24>: 0x90 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
...
Here’s Parent
’s vtable layout:
Address | Value | Meaning |
---|---|---|
0x400ba8 | 0x0 | top_offset (more on this later) |
— | — | — |
0x400bb0 | 0x400b78 | Pointer to typeinfo for Parent (also part of the above memory dump) |
0x400bb8 | 0x400aa0 | Pointer to Parent::Foo() ^1^. Parent ’s _vptr points here. |
0x400bc0 | 0x400a90 | Pointer to Parent::FooNotOverridden() ^2^ |
Here’s Derived
’s vtable layout:
Address | Value | Meaning |
---|---|---|
0x400b40 | 0x0 | top_offset (more on this later) |
— | — | — |
0x400b48 | 0x400b90 | Pointer to typeinfo for Derived (also part of the above memory dump) |
0x400b50 | 0x400a80 | Pointer to Derived::Foo() ^3^. Derived ’s _vptr points here. |
0x400b58 | 0x400a90 | Pointer to Parent::FooNotOverridden() (same as Parent ’s) |
1:
|
|
2:
|
|
3:
|
|
Remember that the vtable pointer in Derived
pointed to a +16 bytes offset into the vtable? The 3rd pointer is the address of the first method pointer. Want the 3rd method? No problem - add 2 * sizeof(void*)
to vtable-pointer. Want the typeinfo record? jump to the pointer before.
Moving on - what about the typeinfo records layout?
Parent
’s:
Address | Value | Meaning |
---|---|---|
0x400b78 | 0x602090 | Helper class for type_info methods1 |
0x400b80 | 0x400b69 | String representing type name2 |
0x400b88 | 0x0 | 0 meaning no parent typeinfo record |
And here’s Derived
’s typeinfo record:
Address | Value | Meaning |
---|---|---|
0x400b90 | 0x602210 | Helper class for type_info methods3 |
0x400b98 | 0x400b60 | String representing type name4 |
0x400ba0 | 0x400b78 | Pointer to Parent ’s typeinfo record |