如下代码:
#include "stdafx.h"
class A {
public:
A():n(0xf1f1){}
virtual ~A(){}
virtual void show(){ printf("a"); }
virtual void show1(){ printf("a1"); }
void showA(){}
private:
int n;
};
class A0 {
public:
A0(){}
virtual ~A0(){}
virtual void show(){ printf("a0"); }
virtual void show1(){ printf("a01"); }
void showA0(){}
};
class B : public A , public A0 {
public:
B(){}
virtual ~B(){}
virtual void show(){ printf("b"); }
virtual void show1(){ printf("b1"); }
void showB(){}
};
typedef void (A0::*PFUNA0)();
typedef void (B::*PFUNB)();
int _tmain(int argc, _TCHAR* argv[])
{
printf("%p, %p\n", &A::show, &B::show);
return 0;
}
会发现&A::show==&B::show.
把_tmain改一下:
int _tmain(int argc, _TCHAR* argv[])
{
PFUNA0 lpProcA0 = (&A0::show1);
A0 *lpa0 = &b0;
(lpa0->*lpProcA0)();
return 0;
}
反汇编:
; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401260 _main proc near ; CODE XREF: __tmainCRTStartup+10Ap
.text:00401260
.text:00401260 var_28 = dword ptr -28h
.text:00401260 var_24 = dword ptr -24h
.text:00401260 lpProcA0 = dword ptr -20h
.text:00401260 lpa0 = dword ptr -1Ch
.text:00401260 b0 = B ptr -18h
.text:00401260 var_C = dword ptr -0Ch
.text:00401260 var_4 = dword ptr -4
.text:00401260 argc = dword ptr 8
.text:00401260 argv = dword ptr 0Ch
.text:00401260 envp = dword ptr 10h
.text:00401260
.text:00401260 push ebp
.text:00401261 mov ebp, esp
.text:00401263 push 0FFFFFFFFh
.text:00401265 push offset __ehhandler$_wmain
.text:0040126A mov eax, large fs:0
.text:00401270 push eax
.text:00401271 sub esp, 1Ch
.text:00401274 mov eax, ___security_cookie
.text:00401279 xor eax, ebp
.text:0040127B push eax
.text:0040127C lea eax, [ebp+var_C]
.text:0040127F mov large fs:0, eax
.text:00401285 lea ecx, [ebp+b0] ; this
.text:00401288 call ??0B@@QAE@XZ ; B::B(void)
.text:0040128D mov [ebp+var_4], 0
.text:00401294 mov [ebp+lpProcA0], offset ??_9A0@@$B7AE ; [thunk]: A0::`vcall'{8,{flat}}
.text:0040129B lea eax, [ebp+b0]
.text:0040129E test eax, eax
.text:004012A0 jz short loc_4012AD
.text:004012A2 lea ecx, [ebp+b0]
.text:004012A5 add ecx, 8
.text:004012A8 mov [ebp+var_28], ecx
.text:004012AB jmp short loc_4012B4
.text:004012AD ; ---------------------------------------------------------------------------
.text:004012AD
.text:004012AD loc_4012AD: ; CODE XREF: _main+40j
.text:004012AD mov [ebp+var_28], 0
.text:004012B4
.text:004012B4 loc_4012B4: ; CODE XREF: _main+4Bj
.text:004012B4 mov edx, [ebp+var_28]
.text:004012B7 mov [ebp+lpa0], edx
.text:004012BA mov ecx, [ebp+lpa0]
.text:004012BD call [ebp+lpProcA0]
.text:004012C0 mov [ebp+var_24], 0
.text:004012C7 mov [ebp+var_4], 0FFFFFFFFh
.text:004012CE lea ecx, [ebp+b0] ; this
.text:004012D1 call ??1B@@UAE@XZ ; B::~B(void)
.text:004012D6 mov eax, [ebp+var_24]
.text:004012D9 mov ecx, [ebp+var_C]
.text:004012DC mov large fs:0, ecx
.text:004012E3 pop ecx
.text:004012E4 mov esp, ebp
.text:004012E6 pop ebp
.text:004012E7 retn
.text:004012E7 _main endp
会发现编译器做了不少事情,简单总结如下:
1.修正虚函数表指针偏移
2.修正虚函数指针偏移
3.修正this指针
4.调用类成员函数
大概流程:
1.修正虚函数表指针:
.text:004012A5 add ecx, 8
2.跳转到&A0::show1地址处处,修正虚函数指针:
.text:00401300 ; [thunk]: __thiscall A0::`vcall'{8,{flat}}
.text:00401300 ??_9A0@@$B7AE proc near ; DATA XREF: _main+34o
.text:00401300 mov eax, [ecx]
.text:00401302 jmp dword ptr [eax+8]
.text:00401302 ??_9A0@@$B7AE endp
[ecx]是修正后的虚函数表指针,然后跳到偏移为[eax + 8]处,即虚函数表第三项,但这项并不是show1的地址,而是这段代码:
3.修正this指针:
.text:004012F0 ; [thunk]:public: virtual void __thiscall B::show1`adjustor{8}' (void)
.text:004012F0 ?show1@B@@W7AEXXZ proc near ; DATA XREF: .rdata:00402150o
.text:004012F0 sub ecx, 8
.text:004012F3 jmp ?show1@B@@UAEXXZ ; B::show1(void)
.text:004012F3 ?show1@B@@W7AEXXZ endp
这段代码主要是修正了一下ecx,即是将this指针指回b0。然后跳转到?show1@B@@UAEXXZ ; B::show1(void),这个才是B::show1的地址。
对this指针做修正是为了下来的函数能正确访问到b0的成员。(个人猜测,未验证)
这些都是和编译器相关的,cl.exe.