Please enable Javascript to view the contents

 ·  ☕ 4 分钟

https://tenzir.com/blog/production-debugging-bpftrace-uprobes/
https://shaharmike.com/cpp/vtable-part1/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

class Parent {
 public:
  virtual void Foo() {}
  virtual void FooNotOverridden() {}
};

class Derived : public Parent {
 public:
  void Foo() override {}
};

int main() {
  Parent p1, p2;
  Derived d1, d2;

  std::cout << "done" << std::endl;
}
$ # 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 and p2 is the same. vtables are static data per-type;
  • d1 and d2 inherit a vtable-pointer from Parent which points to Derived’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:

1
2
3
(gdb) # find out what debug symbol we have for address 0x400aa0
(gdb) info symbol 0x400aa0
Parent::Foo() in section .text of a.out

2:

1
2
(gdb) info symbol 0x400a90
Parent::FooNotOverridden() in section .text of a.out

3:

1
2
(gdb) info symbol 0x400a80
Derived::Foo() in section .text of a.out

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
分享

Mark Zhu
作者
Mark Zhu
An old developer