【反汇编分析】C++成员函数和虚函数

来源:互联网 发布:变声软件怎么实现 编辑:程序博客网 时间:2024/06/04 22:59

       本节通过反汇编研究C++非static成员函数和虚函数的执行流程;


代码片段如下

class Animal{public:  virtual void print()  {    cout << "Animal::print "<< endl;  }    void print2()  {    cout << "Animal::print2 "<< endl;  }};class Dog : public Animal{public:  Dog():_age(1)  {  }  virtual void print()  {    cout << "Dog::print " << _age << endl;  }    void print2()  {    cout << "Dog::print2 "<< endl;  }private:  int _age;};int main(void){  Dog d;  Animal* p1 = &d;  p1->print();  p1->print2();    cout << "_age address: "<< ((int*)&d+1) << " value: " << *((int*)&d+1)<< endl;    cout << "virtual fun address: "<< (int*)&d << endl;    cout << "virtual fun[1] address: "<< (int*)*(int*)&d << endl;    cout << "virtual fun[2] address: "<< ((int*)*(int*)&d+1) << endl;  }
输出如下:

sykpour@sykpour:~/Desktop$ ./testDog::print 1Animal::print2 _age address: 0xbfc5c7f8 value: 1virtual fun address: 0xbfc5c7f4virtual fun[1] address: 0x8048c30virtual fun[2] address: 0x8048c34
说明几点:

(1)本节只会研究p1->print()和p1->print2()的反汇编代码,对于mian函数的下面cout输出部分,只是为了验证;
(2)我们发现在x86-32下,参数传递主要还是存放在栈中来传递,而在x86-64中由于通用寄存器由8个增加到16个,更多的参数是通过寄存器来传递的;

Dog类的虚函数表示意图如下:




print和print2调用的函数栈帧如下:





main函数的反汇编

print和print2相关部分反汇编如下

0804882c <main>: 804882c:8d 4c 24 04          lea    0x4(%esp),%ecx 8048830:83 e4 f0             and    $0xfffffff0,%esp 8048833:ff 71 fc             pushl  -0x4(%ecx) 8048836:55                   push   %ebp         #放入ebp 8048837:89 e5                mov    %esp,%ebp 8048839:53                   push   %ebx 804883a:51                   push   %ecx 804883b:83 ec 10             sub    $0x10,%esp        804883e:83 ec 0c             sub    $0xc,%esp 8048841:8d 45 ec             lea    -0x14(%ebp),%eax 8048844:50                   push   %eax 8048845:e8 0a 02 00 00       call   8048a54 <_ZN3DogC1Ev>    #调用dog的构造函数 804884a:83 c4 10             add    $0x10,%esp 804884d:8d 45 ec             lea    -0x14(%ebp),%eax 8048850:89 45 f4             mov    %eax,-0xc(%ebp)     #将Dog d的地址赋值给p1 8048853:8b 45 f4             mov    -0xc(%ebp),%eax     #取p1 8048856:8b 00                mov    (%eax),%eax         #取virtual虚函数表的地址 8048858:8b 00                mov    (%eax),%eax         #取第一个虚函数的函数地址 804885a:83 ec 0c             sub    $0xc,%esp            804885d:ff 75 f4             pushl  -0xc(%ebp)          #将p1压入作为参数  #取0x8048c3处的内容执行,即执行第一个虚函数,其实是函数地址08048a7e,即_ZN3Dog5printEv的函数地址,调用Dog::print 8048860:ff d0                call   *%eax   8048862:83 c4 10             add    $0x10,%esp 8048865:83 ec 0c             sub    $0xc,%esp 8048868:ff 75 f4             pushl  -0xc(%ebp) 804886b:e8 a8 01 00 00       call   8048a18 <_ZN6Animal6print2Ev>  #调用Animal::print2 8048870:83 c4 10             add    $0x10,%esp 8048873:8d 45 ec             lea    -0x14(%ebp),%eax


Dog的构造函数反汇编如下

08048a54 <_ZN3DogC1Ev>: 8048a54:55                   push   %ebp 8048a55:89 e5                mov    %esp,%ebp 8048a57:83 ec 08             sub    $0x8,%esp 8048a5a:8b 45 08             mov    0x8(%ebp),%eax     #取Dog d的地址 8048a5d:83 ec 0c             sub    $0xc,%esp 8048a60:50                   push   %eax 8048a61:e8 e0 ff ff ff       call   8048a46 <_ZN6AnimalC1Ev> #调用Animal的构造函数 8048a66:83 c4 10             add    $0x10,%esp 8048a69:8b 45 08             mov    0x8(%ebp),%eax 8048a6c:c7 00 30 8c 04 08    movl   $0x8048c30,(%eax)  #Dog虚函数表地址 8048a72:8b 45 08             mov    0x8(%ebp),%eax 8048a75:c7 40 04 01 00 00 00 movl   $0x1,0x4(%eax)   #_age为1 8048a7c:c9                   leave   8048a7d:c3                   ret    

Animal的构造函数反汇编如下

08048a46 <_ZN6AnimalC1Ev>: 8048a46:55                   push   %ebp 8048a47:89 e5                mov    %esp,%ebp 8048a49:8b 45 08             mov    0x8(%ebp),%eax 8048a4c:c7 00 40 8c 04 08    movl   $0x8048c40,(%eax)  #Animal虚函数表地址 8048a52:5d                   pop    %ebp 8048a53:c3                   ret    


Dog虚函数表反汇编如下

08048c28 <_ZTV3Dog>: 8048c28:00 00                add    %al,(%eax) 8048c2a:00 00                add    %al,(%eax) 8048c2c:4c                   dec    %esp 8048c2d:8c 04 08             mov    %es,(%eax,%ecx,1) 8048c30:7e 8a                jle    8048bbc <_IO_stdin_used+0x28> 8048c32:04 08                add    $0x8,%al 8048c34:00 00                add    %al,(%eax)
说明几点:

(1)gcc采用小端存放,因此0x8048c30处的内容为0x08048a7e;具体如下

 8048c30:7e 8a                jle    8048bbc <_IO_stdin_used+0x28> 8048c32:04 08                add    $0x8,%al                小  大

(2)我们知道虚函数表的地址为0x8048c30,而print为Dog虚函数表的第一个函数,因此call   *%eax实际上执行的是0x8048c30地址存放的函数地址,即为08048a7e地址,正好对应Dog::print的函数地址;


Dog::print函数反汇编如下

08048a7e <_ZN3Dog5printEv>: 8048a7e:55                   push   %ebp 8048a7f:89 e5                mov    %esp,%ebp 8048a81:53                   push   %ebx 8048a82:83 ec 04             sub    $0x4,%esp 8048a85:8b 45 08             mov    0x8(%ebp),%eax   #获得Dog d的地址 8048a88:8b 58 04             mov    0x4(%eax),%ebx   #此时_age的值在ebx中 8048a8b:83 ec 08             sub    $0x8,%esp 8048a8e:68 b7 8b 04 08       push   $0x8048bb7 8048a93:68 80 91 04 08       push   $0x8049180   #调用cout的地址 8048a98:e8 1f fc ff ff       call   80486bc <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 8048a9d:83 c4 10             add    $0x10,%esp 8048aa0:83 ec 08             sub    $0x8,%esp 8048aa3:53                   push   %ebx 8048aa4:50                   push   %eax 8048aa5:e8 b2 fb ff ff       call   804865c <_ZNSolsEi@plt> 8048aaa:83 c4 10             add    $0x10,%esp 8048aad:83 ec 08             sub    $0x8,%esp 8048ab0:68 ec 86 04 08       push   $0x80486ec 8048ab5:50                   push   %eax 8048ab6:e8 21 fc ff ff       call   80486dc <_ZNSolsEPFRSoS_E@plt> 8048abb:83 c4 10             add    $0x10,%esp 8048abe:8b 5d fc             mov    -0x4(%ebp),%ebx 8048ac1:c9                   leave   8048ac2:c3                   ret    


Animal::print2函数反汇编如下

08048a18 <_ZN6Animal6print2Ev>: 8048a18:55                   push   %ebp 8048a19:89 e5                mov    %esp,%ebp 8048a1b:83 ec 08             sub    $0x8,%esp 8048a1e:83 ec 08             sub    $0x8,%esp 8048a21:68 a7 8b 04 08       push   $0x8048ba7 8048a26:68 80 91 04 08       push   $0x8049180 8048a2b:e8 8c fc ff ff       call   80486bc <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 8048a30:83 c4 10             add    $0x10,%esp 8048a33:83 ec 08             sub    $0x8,%esp 8048a36:68 ec 86 04 08       push   $0x80486ec 8048a3b:50                   push   %eax 8048a3c:e8 9b fc ff ff       call   80486dc <_ZNSolsEPFRSoS_E@plt> 8048a41:83 c4 10             add    $0x10,%esp 8048a44:c9                   leave   8048a45:c3                   ret    

0 0
原创粉丝点击