Assuming the following C ++ source file:
#include <stdio.h> class BaseTest { public: int a; BaseTest(): a(2){} virtual int gB() { return a; }; }; class SubTest: public BaseTest { public: int b; SubTest(): b(4){} }; class TriTest: public BaseTest { public: int c; TriTest(): c(42){} }; class EvilTest: public SubTest, public TriTest { public: virtual int gB(){ return b; } }; int main(){ EvilTest * t2 = new EvilTest; TriTest * t3 = t2; printf("%d\n",t3->gB()); printf("%d\n",t2->gB()); return 0; }
-fdump-class-hierarchy gives me:
[...] Vtable for EvilTest EvilTest::_ZTV8EvilTest: 6u entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI8EvilTest) 16 (int (*)(...))EvilTest::gB 24 (int (*)(...))-16 32 (int (*)(...))(& _ZTI8EvilTest) 40 (int (*)(...))EvilTest::_ZThn16_N8EvilTest2gBEv Class EvilTest size=32 align=8 base size=32 base align=8 EvilTest (0x0x7f1ba98a8150) 0 vptr=((& EvilTest::_ZTV8EvilTest) + 16u) SubTest (0x0x7f1ba96df478) 0 primary-for EvilTest (0x0x7f1ba98a8150) BaseTest (0x0x7f1ba982ba80) 0 primary-for SubTest (0x0x7f1ba96df478) TriTest (0x0x7f1ba96df4e0) 16 vptr=((& EvilTest::_ZTV8EvilTest) + 40u) BaseTest (0x0x7f1ba982bae0) 16 primary-for TriTest (0x0x7f1ba96df4e0)
Dismantling shows:
34 int main(){ 0x000000000040076d <+0>: push rbp 0x000000000040076e <+1>: mov rbp,rsp 0x0000000000400771 <+4>: push rbx 0x0000000000400772 <+5>: sub rsp,0x18 35 EvilTest * t2 = new EvilTest; 0x0000000000400776 <+9>: mov edi,0x20 0x000000000040077b <+14>: call 0x400670 < _Znwm@plt > 0x0000000000400780 <+19>: mov rbx,rax 0x0000000000400783 <+22>: mov rdi,rbx 0x0000000000400786 <+25>: call 0x4008a8 <EvilTest::EvilTest()> 0x000000000040078b <+30>: mov QWORD PTR [rbp-0x18],rbx 36 37 TriTest * t3 = t2; 0x000000000040078f <+34>: cmp QWORD PTR [rbp-0x18],0x0 0x0000000000400794 <+39>: je 0x4007a0 <main()+51> 0x0000000000400796 <+41>: mov rax,QWORD PTR [rbp-0x18] 0x000000000040079a <+45>: add rax,0x10 0x000000000040079e <+49>: jmp 0x4007a5 <main()+56> 0x00000000004007a0 <+51>: mov eax,0x0 0x00000000004007a5 <+56>: mov QWORD PTR [rbp-0x20],rax 38 39 printf("%d\n",t3->gB()); 0x00000000004007a9 <+60>: mov rax,QWORD PTR [rbp-0x20] 0x00000000004007ad <+64>: mov rax,QWORD PTR [rax] 0x00000000004007b0 <+67>: mov rax,QWORD PTR [rax] 0x00000000004007b3 <+70>: mov rdx,QWORD PTR [rbp-0x20] 0x00000000004007b7 <+74>: mov rdi,rdx 0x00000000004007ba <+77>: call rax 0x00000000004007bc <+79>: mov esi,eax 0x00000000004007be <+81>: mov edi,0x400984 0x00000000004007c3 <+86>: mov eax,0x0 0x00000000004007c8 <+91>: call 0x400640 < printf@plt > 40 printf("%d\n",t2->gB()); 0x00000000004007cd <+96>: mov rax,QWORD PTR [rbp-0x18] 0x00000000004007d1 <+100>: mov rax,QWORD PTR [rax] 0x00000000004007d4 <+103>: mov rax,QWORD PTR [rax] 0x00000000004007d7 <+106>: mov rdx,QWORD PTR [rbp-0x18] 0x00000000004007db <+110>: mov rdi,rdx 0x00000000004007de <+113>: call rax 0x00000000004007e0 <+115>: mov esi,eax 0x00000000004007e2 <+117>: mov edi,0x400984 0x00000000004007e7 <+122>: mov eax,0x0 0x00000000004007ec <+127>: call 0x400640 < printf@plt > 41 return 0; 0x00000000004007f1 <+132>: mov eax,0x0 42 } 0x00000000004007f6 <+137>: add rsp,0x18 0x00000000004007fa <+141>: pop rbx 0x00000000004007fb <+142>: pop rbp 0x00000000004007fc <+143>: ret
Now that you had the right time to recover from a deadly diamond in the first block of code, an urgent question.
When t3->gB() is called, I see the following disas ( t3 is type TriTest , gB() is the virtual method of EvilTest::gB() ):
0x00000000004007a9 <+60>: mov rax,QWORD PTR [rbp-0x20] 0x00000000004007ad <+64>: mov rax,QWORD PTR [rax] 0x00000000004007b0 <+67>: mov rax,QWORD PTR [rax] 0x00000000004007b3 <+70>: mov rdx,QWORD PTR [rbp-0x20] 0x00000000004007b7 <+74>: mov rdi,rdx 0x00000000004007ba <+77>: call rax
The first mov moves the vtable to rax, the next play it (we are now in the vtable)
After these dereferences, to get a pointer to the function and below, insert its call ed.
So far, so good, but that raises a few questions.
Where is this ?
I assume that this loaded into rdi via mov at +70 and +74, but the same pointer as vtable, which means its pointer to the TriTest class, which should not have SubTest b at all. Does linux thiscall match conditional virtual casting inside the called method, as opposed to external?
Rodrigo answered here
How to parse a virtual method?
If I knew this, I could answer the previous question myself. disas EvilTest::gB gives me:
Cannot reference virtual member function "gB"
setting a breakpoint before call , starting info reg rax and disas singing, which gives me:
(gdb) info reg rax rax 0x4008a1 4196513 (gdb) disas 0x4008a14196513 No function contains specified address. (gdb) disas *0x4008a14196513 Cannot access memory at address 0x4008a14196513
Why are vtables (apparently) only 8 bytes from eachother?
fdump says that between the first and second &vtable (which corresponds to a 64-bit pointer and 2 targets) there are 16 bytes, but disassembly from the second call to gB() :
0x00000000004007cd <+96>: mov rax,QWORD PTR [rbp-0x18] 0x00000000004007d1 <+100>: mov rax,QWORD PTR [rax] 0x00000000004007d4 <+103>: mov rax,QWORD PTR [rax] 0x00000000004007d7 <+106>: mov rdx,QWORD PTR [rbp-0x18] 0x00000000004007db <+110>: mov rdi,rdx 0x00000000004007de <+113>: call rax
[rbp-0x18] is only 8 bytes from the previous call ( [rbp-0x20] ). What's happening?
Answered by 500 in the comments
I forgot that objects were allocated in a heap, only their pointers are on the stack