When talking about virtual functions, I think many of my friends should know the virtual function table pointer vptr and virtual function table vtable. If you are not clear about it, it is recommended that you first take a look at the deep exploration C ++ object model translated by Mr. Hou Jie:
At the beginning, I only knew that the polymorphism mechanism of virtual functions was controlled by vptr and vtable. I completely believed what was described in the book and did not prove it myself, maybe it was because I was not touched by Reverse Analysis at that time :)
Raise several questions:
1. How can I determine the vptr size?
2. What is the Offset Value of vptr in a class instance?
3. How does vptr index the function to be called?
4. How is the order of functions in vtable determined?
If you are familiar with the above questions, you can close this window :)
Consider the following code:
#include <cstdio>class Base{public:int i;char c;double d;Base(){i = 4;c = 'A';d = 2.0;}virtual void Virtual_Func_A(){printf("Virtual_Func_A()\n");}virtual void Virtual_Func_B(){printf("Virtual_Func_B()\n");}};int main(void){Base* b = new Base;b->Virtual_Func_A();b->Virtual_Func_A();return 0;}
Analysis Tool: VC 6.0
Analysis process:
Before answering this question, it is necessary for the reader to understand the concept of "Data Alignment". If you do not know it, refer to the following :)
Http://blog.csdn.net/yeweiouyang/article/details/8636458
Note that Inter CPU uses the small-end Method
Vptr is invisible to programmers. On the Win32 platform, vptr occupies 4 bytes to store the vtable address, regardless of Data Alignment.
Consider the following program:
#include <cstdio>class Base{public:virtual void Common_Func(){printf("Base::Common_Func()\n");}virtual void Base_Func(){printf("Base::Func()\n");}};class Derived : public Base{public:virtual void Common_Func(){printf("Derived::Common_Func()\n");}virtual void Derived_Func(){printf("Derived::Func()\n");}};int main(void){Base* b = new Base;b->Common_Func();b->Base_Func();Derived* d = new Derived;d->Common_Func();d->Base_Func();d->Derived_Func();return 0;}
Analysis Tool: Ida pro
Static disassembly:
. Text: 00401000 push ESI. Text: 00401001 Push 4; apply for 4 bytes of stack space for base: vptr. Text: 00401003 call ?? 2 @ yapaxi @ Z; operator new (uint ). text: 00401008 add ESP, 4. text: 0040100b test eax, eax. text: 0040100d JZ short loc_401019.text: 0040100f mov dword ptr [eax], offset base_vptr; * B's heap space is placed in base: vptr. text: 00401015 mov ESI, eax. text: 00401017 JMP short loc_40101b
Corresponding offset base_vptr:
.rdata:004060BC Base_VPTR dd offset Base_Common_Func ; DATA XREF: _main+Fo.rdata:004060C0 dd offset Base_Func.rdata:004060C4 align 8
It is not difficult to find that base_vptr is the first address of vtable, that is, the point pointed by base: vptr.
By this sentence:
.text:0040100F mov dword ptr [eax], offset Base_VPTR ;
It is not difficult to find that the vptr is stored at the beginning of base class instance B, that is, the offset value is 0.
. Text: 00401019. text: 00401019 loc_401019:; Code xref: _ main + DJ. text: 00401019 xor esi, ESI. text: 0040101b. text: 0040101b loc_40101b:; Code xref: _ main + 17j. text: 0040101b mov eax, [esi]; eax = base: vtable. text: 0040101d mov ECx, ESI. text: 0040101f call dword ptr [eax]; Call base: common_func (). text: 00401021 mov edX, [esi]; edX = base: vtable. text: 00401023 mov ECx, ESI. text: 00401025 call dword ptr [Ed X + 4]; Call base: base_func (). Text: 00401028 Push 4; apply for 4 bytes of stack space for derived: vptr. Text: 0040102a call ?? 2 @ yapaxi @ Z; operator new (uint ). text: 0040102f add ESP, 4. text: 00401032 test eax, eax. text: 00401034 JZ short loc_401040.text: 00401036 mov dword ptr [eax], offset derived_vptr; * D heap space put into derived_vptr.text: 0040103c mov ESI, eax. text: 0040103e JMP short loc_401_2.text: 00401040 ;---------------------------------------------------------------------------. text: 00401040. text: 00401040 loc_401040:; Code xref: _ main + 34j. text: 00401040 xor esi, ESI. text: 00401042. text: 00401042 loc_401042:; Code xref: _ main + 3ej. text: 00401042 mov eax, [esi]; eax = derived: vtable. text: 00401044 mov ECx, ESI. text: 00401046 call dword ptr [eax]; call derived: common_func (). text: 00401048 mov edX, [esi]; edX = derived: vtable. text: 0040104a mov ECx, ESI. text: 0040104c call dword ptr [edX + 4]; call base_func (), base_func () is inherited from the base class. text: 0040104f mov eax, [esi]; eax = derived: vtable. text: 00401051 mov ECx, ESI. text: 00401053 call dword ptr [eax + 8]; call derived: derived_func (). text: 00401056 XOR eax, eax. text: 00401058 pop ESI. text: 00401059 retn. text: 00401059 _ main endp
Corresponding derived_vptr:
. RDATA: 004060b0 derived_vptr dd offset derived_common_func; Data xref: _ main + 36o ...... (IDA does not display all function pointers)
After analysis, the vtable structure of base and derived is roughly as follows:
In vtable, the function address (that is, the function pointer) is stored, instead of the function itself or function name. On the Win32 platform, the function pointer occupies 4 bytes, when a virtual function is called through a class instance, vptr indexes the vtable. If the corresponding virtual function is found, the function pointer is retrieved and called.