从汇编层面深度剖析C++基本对象布局

发表于:2014-7-02 09:53

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:海枫    来源:51Testing软件测试网采编

分享:
  3. 编译生成汇编文件和可执行文件
  g++命令行提供了简便方式来生成这两种文件,我们在下面根据实际需要来对这两文件进行分析,从而深入理解point对象的内存布局。
  g++命令使用如下:
  [lyt@t468 ~]$ g++  -g -o object object.cpp
  [lyt@t468 ~]$ g++  -g -S -o object.s object.cpp
  object.s文件生成的汇编比较凌乱,因为它里面的符号还未重定位,只是使用一些符号来表示某些以后要分配内存的变量,编译器使用的变量或符号。因此,我们可以利用它来分析某些C++变量经编译器处理后,在汇编层面上的符号名称。
  object文件可用来供gdb调试工具来使用,gdb可以对源代码以函数为单位,对每一行语句进行反汇编。
  4. 所有与point类相关的符号
  C++源代码生成可执行文件(linux下称为ELF格式文件)后,它专门有一个符号节区来记录执行文件中各个符号的类型,地址等相关信息。为了便于分析,我们使用readelf工具对生成的object文件,找出与point类相关的所有符号,以及使用c++filt工具,将这些符号转变成C++语言级别上的语义,如下:
[lyt@t468 ~]$ readelf -s object | grep point
41: 08048530    10 FUNC    WEAK   DEFAULT   13 _ZN5point13get_instancesE
49: 0804853a    40 FUNC    WEAK   DEFAULT   13 _ZN5point4moveEii
56: 080484fa    35 FUNC    WEAK   DEFAULT   13 _ZN5pointC1Eii
58: 0804851e    18 FUNC    WEAK   DEFAULT   13 _ZN5pointD1Ev
59: 0804a01c     4 OBJECT  GLOBAL DEFAULT   25 _ZN5point7ins_cntE
[lyt@t468 ~]$ c++filt _ZN5point13get_instancesE
point::get_instances
[lyt@t468 ~]$ c++filt _ZN5point4moveEii
point::move(int, int)
[lyt@t468 ~]$ c++filt _ZN5pointC1Eii
point::point(int, int)
[lyt@t468 ~]$ c++filt _ZN5pointD1Ev
point::~point()
[lyt@t468 ~]$ c++filt _ZN5point7ins_cntE
point::ins_cnt
  从上面的结果可以看出来,point类的构造函数,析构函数,move成员函数,get_instances静态成员函数都对应一个函数符号。而令我们感到意外的是,point类的静态变量ins_cnt也对应一个全局变量符号,它的地址是0804a01c;下面对地址0804a01c 的读写汇编语言,都意味着相应的C++函数读写该变量,也即point类的静态变量。
  5. point对象的内存布局和构造函数
  对象的生命始于构造函数,而在执行构造函数之前,对象还处于混沌状态。在构造函数里面,它按对象内存所包含的每个成员依次进行初始化,因此我们从对象的构造函数就可以一窥它的内存布局。
  为了方便大家较对C++源代码和汇编代码,使用gdb对point类的构造函数按源代码行进行反汇编。结果如下:
(gdb) disassemble /m _ZN5pointC1Eii
Dump of assembler code for function point:
5               point(int x, int y)
0x080484fa <point+0>:   push   ebp
0x080484fb <point+1>:   mov    ebp,esp
6               {
7                       this->x = x;
0x080484fd <point+3>:   mov    eax,DWORD PTR [ebp+0x8]
0x08048500 <point+6>:   mov    edx,DWORD PTR [ebp+0xc]
0x08048503 <point+9>:   mov    DWORD PTR [eax],edx
8                       this->y = y;
0x08048505 <point+11>:  mov    eax,DWORD PTR [ebp+0x8]
0x08048508 <point+14>:  mov    edx,DWORD PTR [ebp+0x10]
0x0804850b <point+17>:  mov    DWORD PTR [eax+0x4],edx
9                       ins_cnt++;
0x0804850e <point+20>:  mov    eax,ds:0x804a01c
0x08048513 <point+25>:  add    eax,0x1
0x08048516 <point+28>:  mov    ds:0x804a01c,eax
10              }
0x0804851b <point+33>:  pop    ebp
0x0804851c <point+34>:  ret
End of assembler dump.
  为了让大家更清楚构造函数到底作了什么事情,我对上面的汇编语句逐行分析:
  7                       this->x = x;
  0x080484fd <point+3>:   mov    eax,DWORD PTR [ebp+0x8]
  0x08048500 <point+6>:   mov    edx,DWORD PTR [ebp+0xc]
  0x08048503 <point+9>:   mov    DWORD PTR [eax],edx
  mov    eax,DWORD PTR [ebp+0x8] 将函数第一个参数的值存放到寄存器eax中
  mov    edx,DWORD PTR [ebp+0xc] 将函数第二个参数的值存放到寄存器edx中
  mov    DWORD PTR [eax],edx        将edx寄存器的值写到eax所指向的内存中
  结合this->x = x;这个C++代码,我们可以大胆推测,point构造函数生成汇编后,它对应的函数名(或者符号名)为
  _ZN5pointC1Eii。该函数的第一个参数为this,类型为point类内存布局的表示类型,我们姑且称为struct point *类型;第二参数为int类型的x。
  接下来的this->y = y;语句的反汇编,与上面this->x = x; 语句如同一辙,唯有x和y在point对象的内存偏移量不同。
  从而得出,x成员在point对象内存的偏移量为0,而y的为4。
  比较迷惑的是最后这句:
  9                       ins_cnt++;
  0x0804850e <point+20>:  mov    eax,ds:0x804a01c
  0x08048513 <point+25>:  add    eax,0x1
  0x08048516 <point+28>:  mov    ds:0x804a01c,eax
  第一个mov是将内存0x804a01c的值读到eax中,add指令是将eax加1,最后一个mov是将eax最后的值写回到内存中。还记得0x804a01c是哪个符号的地址吗?没错,它就是point类静态变量ins_cnt的地址。
  由此,我们可以使用point类的对象在内存的布局如下:
  struct point {
  int x;
  int y;
  };
  // point::ins_cnt 变量,在汇编层面上,它是一个全局变量
  int point_ins_cnt = 0;
  它的构造函数翻译成如下:
  void point::point(struct point *this, int x, int y)
  {
  this->x = x;
  this->y = y;
  point_ins_cnt++;
  }
  正如你早已知道的秘密,C++编译器悄悄地将你写的非静态 函数 成员(当然包括构造函数的析构函数)加上this指针作为第一个参数,这就是C++资料上所说的this隐藏参数。在汇编的曝光下,这一切都真相大白了。
  下面是move成员函数反汇编的结果,如有不明白,可以对比分析一下:
(gdb) disassemble /m _ZN5point4moveEii
Dump of assembler code for function _ZN5point4moveEii:
22              point & move(int addx, int addy)
0x0804853a <_ZN5point4moveEii+0>:       push   ebp
0x0804853b <_ZN5point4moveEii+1>:       mov    ebp,esp
23              {
24                      this->x += addx;
0x0804853d <_ZN5point4moveEii+3>:       mov    eax,DWORD PTR [ebp+0x8]
0x08048540 <_ZN5point4moveEii+6>:       mov    eax,DWORD PTR [eax]
0x08048542 <_ZN5point4moveEii+8>:       mov    edx,eax
0x08048544 <_ZN5point4moveEii+10>:      add    edx,DWORD PTR [ebp+0xc]
0x08048547 <_ZN5point4moveEii+13>:      mov    eax,DWORD PTR [ebp+0x8]
0x0804854a <_ZN5point4moveEii+16>:      mov    DWORD PTR [eax],edx
25                      this->y += addy;
0x0804854c <_ZN5point4moveEii+18>:      mov    eax,DWORD PTR [ebp+0x8]
0x0804854f <_ZN5point4moveEii+21>:      mov    eax,DWORD PTR [eax+0x4]
0x08048552 <_ZN5point4moveEii+24>:      mov    edx,eax
0x08048554 <_ZN5point4moveEii+26>:      add    edx,DWORD PTR [ebp+0x10]
0x08048557 <_ZN5point4moveEii+29>:      mov    eax,DWORD PTR [ebp+0x8]
0x0804855a <_ZN5point4moveEii+32>:      mov    DWORD PTR [eax+0x4],edx
26
27                      return *this;
0x0804855d <_ZN5point4moveEii+35>:      mov    eax,DWORD PTR [ebp+0x8]
28              }
0x08048560 <_ZN5point4moveEii+38>:      pop    ebp
0x08048561 <_ZN5point4moveEii+39>:      ret
End of assembler dump.
32/3<123>
重磅发布,2022软件测试行业现状调查报告~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计

法律顾问:上海漕溪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2023
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号