首页
学习
活动
专区
圈层
工具
发布
21 篇文章
1
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
2
《coredump问题原理探究》Linux x86版6.5节虚函数的coredump例子
3
《coredump问题原理探究》Linux x86版6.8节多继承coredump例子
4
《coredump问题原理探究》Linux x86版7.2节vector coredump例子
5
《coredump问题原理探究》Linux x86版7.4节List coredump例子
6
《coredump问题原理探究》Linux x86版7.6节 Map coredump例子
7
《coredump问题原理探究》Linux x86版5.9节C风格数据结构内存布局之联合体
8
《coredump问题原理探究》Linux x86版6.1节C++风格数据结构内存布局之无成员变量的类
9
《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类
10
《coredump问题原理探究》Linux x86版4.5节函数的逆向之coredump例子
11
《coredump问题原理探究》Linux x86版5.1节C风格数据结构内存布局之引言
12
《coredump问题原理探究》Linux x86版5.2节C风格数据结构内存布局之基本数据类型
13
《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组
14
《coredump问题原理探究》Linux x86版5.4节C风格数据结构内存布局之数组coredump例子
15
《coredump问题原理探究》Linux x86版5.5节C风格数据结构内存布局之基本数据类型构成的结构体
16
《coredump问题原理探究》Linux x86版5.6节C风格数据结构内存布局之复合类型构成的结构体
17
《coredump问题原理探究》Linux x86版5.7节C风格数据结构内存布局之结构体数组
18
《coredump问题原理探究》Linux x86版5.8节C风格数据结构内存布局之结构体数组结构体coredump
19
《coredump问题原理探究》Linux x86版3.5节栈布局之-fomit-frame-pointer编译选项
20
《coredump问题原理探究》Linux x86版3.6节栈布局之gcc内嵌关键字
21
《coredump问题原理探究》Linux x86版3.8节栈布局之栈溢出coredump例子

《coredump问题原理探究》Linux x86版6.5节虚函数的coredump例子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344556

在大型项目中,很容易出现版本不匹配的问题,其中导致的虚函数飘移的问题比较难解决。

在这里,用一个例子来说明如何解决这种问题。

建立三个源文件:testso.h,testso.cpp,xuzhina_dump_c6_s3_ex.cpp。

testso.h的代码如下:

代码语言:javascript
复制
  1	 #ifndef __TESTSO_H__
  2	 #define __TESTSO_H__
  3 
  4	 class xuzhina_dump_c6_s3_ex
  5	 {
  6	     public:
  7	         virtual char* encode( char* str );
  8	 };      
  9 
 10	 #endif

testso.cpp的代码如下:

代码语言:javascript
复制
  1	 #include <string.h>
  2	 #include "testso.h"
  3 
  4	 char* xuzhina_dump_c6_s3_ex::encode( char* str )
  5	 {
  6	     return str;
  7	 }
  8

xuzhina_dump_c6_s3_ex.cpp的代码如下:

代码语言:javascript
复制
  1	 #include <stdio.h>
  2	 #include "testso.h"
  3	 
  4	 int main()
  5	 {
  6	     char* hello = (char*)"hello";
  7	     xuzhina_dump_c6_s3_ex* test = new xuzhina_dump_c6_s3_ex;
  8	 
  9	     char* p = test->encode( hello );
 10 
 11	     printf( "the second char:%c\n", p[1] );
 12 
 13	     return 0;
 14	 }

编译代码:

代码语言:javascript
复制
[buckxu@xuzhina 3_ex]$ g++ -o libtestso.so -shared -fpic testso.cpp
[buckxu@xuzhina 3_ex]$ g++ -o xuzhina_dump_c6_s3_ex xuzhina_dump_c6_s3_ex.cpp -L. -ltestso

运行xuzhina_dump_c6_s3_ex可以得到这样的结果:

代码语言:javascript
复制
[buckxu@xuzhina 3_ex]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.;./xuzhina_dump_c6_s3_ex 
the second char:e

现在在testso.h的类新增一个虚函数(注意虚函数声明的顺序):

代码语言:javascript
复制
  1	 #ifndef __TESTSO_H__
  2	 #define __TESTSO_H__
  3 
  4	 class xuzhina_dump_c6_s3_ex
  5	 {
  6	     public:
  7	         virtual char* parseVal( char* str );
  8	         virtual char* encode( char* str );
  9	 };
 10 
 11	 #endif

在testso.cpp新增它的实现代码:

  1	 #include <string.h>
  2	 #include "testso.h"
  3 
  4	 char* xuzhina_dump_c6_s3_ex::encode( char* str )
  5	 {
  6	     return str;
  7	 }
  8 
  9	 char* xuzhina_dump_c6_s3_ex::parseVal( char* str )
 10	 {
 11	     char* p = strstr( str, "=" );
 12	     if ( p != NULL )
 13	     {
 14	         return p+1;
 15	     }
 16	     return NULL;
 17	 }

重新生成so文件,但不重新生成xuzhina_dump_c6_s3_ex(为什么?这是模拟几个部门协作的产品开发过程,往往有些版本制作人为了省事,没有清除重新编译)

代码语言:javascript
复制
[buckxu@xuzhina 3_ex]$ g++ -o libtestso.so -shared -fpic testso.cpp

运行xuzhina_dump_c6_s3_ex,它coredump了。

代码语言:javascript
复制
[buckxu@xuzhina 3_ex]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.;./xuzhina_dump_c6_s3_ex 
./xuzhina_dump_c6_s3_ex: Symbol `_ZTV21xuzhina_dump_c6_s3_ex' has different size in shared object, consider re-linking
Segmentation fault (core dumped)

打开coredump文件,可以见到如下堆栈:

代码语言:javascript
复制
(gdb) bt
#0  0x08048630 in main ()

看一下main函数的汇编:

代码语言:javascript
复制
(gdb) disassemble main
Dump of assembler code for function main:
   0x080485e0 <+0>:     push   %ebp
   0x080485e1 <+1>:     mov    %esp,%ebp
   0x080485e3 <+3>:     push   %ebx
   0x080485e4 <+4>:     and    $0xfffffff0,%esp
   0x080485e7 <+7>:     sub    $0x20,%esp
   0x080485ea <+10>:    movl   $0x80486f4,0x1c(%esp)
   0x080485f2 <+18>:    movl   $0x4,(%esp)
   0x080485f9 <+25>:    call   0x80484d0 <_Znwj@plt>
   0x080485fe <+30>:    mov    %eax,%ebx
   0x08048600 <+32>:    mov    %ebx,(%esp)
   0x08048603 <+35>:    call   0x8048650 <_ZN21xuzhina_dump_c6_s3_exC2Ev>
   0x08048608 <+40>:    mov    %ebx,0x18(%esp)
   0x0804860c <+44>:    mov    0x18(%esp),%eax
   0x08048610 <+48>:    mov    (%eax),%eax
   0x08048612 <+50>:    mov    (%eax),%eax
   0x08048614 <+52>:    mov    0x1c(%esp),%edx
   0x08048618 <+56>:    mov    %edx,0x4(%esp)
   0x0804861c <+60>:    mov    0x18(%esp),%edx
   0x08048620 <+64>:    mov    %edx,(%esp)
   0x08048623 <+67>:    call   *%eax
   0x08048625 <+69>:    mov    %eax,0x14(%esp)
   0x08048629 <+73>:    mov    0x14(%esp),%eax
   0x0804862d <+77>:    add    $0x1,%eax
=> 0x08048630 <+80>:    movzbl (%eax),%eax
   0x08048633 <+83>:    movsbl %al,%eax
   0x08048636 <+86>:    mov    %eax,0x4(%esp)
   0x0804863a <+90>:    movl   $0x80486fa,(%esp)
   0x08048641 <+97>:    call   0x80484c0 <printf@plt>
   0x08048646 <+102>:   mov    $0x0,%eax
   0x0804864b <+107>:   mov    -0x4(%ebp),%ebx
   0x0804864e <+110>:   leave  
   0x0804864f <+111>:   ret    
End of assembler dump.

看一下寄存器eax的值

代码语言:javascript
复制
(gdb) i r eax
eax            0x1      1

可见,是由于eax的值为1而导致的。而eax由

代码语言:javascript
复制
   0x08048623 <+67>:    call   *%eax
   0x08048625 <+69>:    mov    %eax,0x14(%esp)
   0x08048629 <+73>:    mov    0x14(%esp),%eax
   0x0804862d <+77>:    add    $0x1,%eax

可知是

代码语言:javascript
复制
   0x08048623 <+67>:    call   *%eax

的返回值。

那么,eax所存放的函数指针又是从哪里来的?

代码语言:javascript
复制
   0x0804860c <+44>:    mov    0x18(%esp),%eax
   0x08048610 <+48>:    mov    (%eax),%eax
   0x08048612 <+50>:    mov    (%eax),%eax
   0x08048614 <+52>:    mov    0x1c(%esp),%edx
   0x08048618 <+56>:    mov    %edx,0x4(%esp)
   0x0804861c <+60>:    mov    0x18(%esp),%edx
   0x08048620 <+64>:    mov    %edx,(%esp)
   0x08048623 <+67>:    call   *%eax

显然,最终是从esp+0x18所指向单元取来的,而由

代码语言:javascript
复制
   0x08048600 <+32>:    mov    %ebx,(%esp)
   0x08048603 <+35>:    call   0x8048650 <_ZN21xuzhina_dump_c6_s3_exC2Ev>
   0x08048608 <+40>:    mov    %ebx,0x18(%esp)

可知,esp+0x18存放着类xuzhina_dump_c6_s3_ex(shell c++filt _ZN21xuzhina_dump_c6_s3_exC2Ev)对象的this指针。

那么,

代码语言:javascript
复制
   0x0804860c <+44>:    mov    0x18(%esp),%eax
   0x08048610 <+48>:    mov    (%eax),%eax
   0x08048612 <+50>:    mov    (%eax),%eax

就是从this指针中取出虚函数的操作了。看一下类xuzhina_dump_c6_s3_ex的虚函数表:

代码语言:javascript
复制
(gdb) x $esp+0x18
0xbfc0fee8:     0x08c07008
(gdb) x /x 0x08c07008
0x8c07008:      0x08049958
(gdb) x /4x 0x08049958
0x8049958 <_ZTV21xuzhina_dump_c6_s3_ex+8>:      0xb77b16c8      0x00000000      0x00000000      0x00000000
(gdb) info symbol 0xb77b16c8
xuzhina_dump_c6_s3_ex::parseVal(char*) in section .text of libtestso.so

非常奇怪.按照代码逻辑,应该是xuzhina_dump_c6_s3_ex::encode(char *)才对的。

好了,定位到这个地步,就可以知道类xuzhina_dump_c6_s3_ex所在的头文件已经修改了,但没有进行重新编译,而且是在成员虚函数encode的前面增加了虚函数。为什么不是后面呢?有兴趣的读者可以试一下这种情况。

已经知道新版本的头文件被修改,剩下的工作用比较工具,如diff之类就可以找出修改处了。

当然还有另外一个方法来看增加了多少虚函数。

由上面的分析,可以知道0x08049958是类xuzhina_dump_c6_s3_ex的虚函数表地址。

代码语言:javascript
复制
(gdb) x /4x 0x08049958                          
0x8049958 <_ZTV21xuzhina_dump_c6_s3_ex+8>:      0xb77b16c8      0x00000000      0x00000000      0x00000000

可以看到,它是偏移_ZTV21xuzhina_dump_c6_s3_ex,而_ZTV21xuzhina_dump_c6_s3_ex是

代码语言:javascript
复制
(gdb) shell c++filt _ZTV21xuzhina_dump_c6_s3_ex
vtable for xuzhina_dump_c6_s3_ex

那么,_ZTV21xuzhina_dump_c6_s3_ex的地址是0x08049950。看一下它的内容

代码语言:javascript
复制
(gdb) x /4x 0x08049950                         
0x8049950 <_ZTV21xuzhina_dump_c6_s3_ex>:        0x00000000      0xb77b2808      0xb77b16c8      0x00000000
(gdb) info symbol 0xb77b2808
typeinfo for xuzhina_dump_c6_s3_ex in section .data.rel.ro of libtestso.so
(gdb) info symbol 0xb77b16c8
xuzhina_dump_c6_s3_ex::parseVal(char*) in section .text of libtestso.so

也就是说,类xuzhina_dump_c6_s3_ex后面紧接着虚函数表。有兴趣可以试一下其它的类,也是如此。

那么,看一下类xuzhina_dump_c6_s3_ex所在so文件libtestso.so,通过objdump来看。

代码语言:javascript
复制
[buckxu@xuzhina 3_ex]$ objdump -R libtestso.so 

libtestso.so:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
000017e4 R_386_RELATIVE    *ABS*
000017e8 R_386_RELATIVE    *ABS*
000017f0 R_386_RELATIVE    *ABS*
000017fc R_386_32          _ZTI21xuzhina_dump_c6_s3_ex
00001800 R_386_32          _ZN21xuzhina_dump_c6_s3_ex8parseValEPc
00001804 R_386_32          _ZN21xuzhina_dump_c6_s3_ex6encodeEPc
00001808 R_386_32          _ZTVN10__cxxabiv117__class_type_infoE
0000180c R_386_32          _ZTS21xuzhina_dump_c6_s3_ex
00001908 R_386_GLOB_DAT    __gmon_start__
0000190c R_386_GLOB_DAT    _Jv_RegisterClasses
00001910 R_386_GLOB_DAT    _ITM_deregisterTMCloneTable
00001914 R_386_GLOB_DAT    _ITM_registerTMCloneTable
00001918 R_386_GLOB_DAT    __cxa_finalize
00001928 R_386_JUMP_SLOT   __gmon_start__
0000192c R_386_JUMP_SLOT   strstr
00001930 R_386_JUMP_SLOT   __cxa_finalize

[buckxu@xuzhina 3_ex]$ c++filt _ZTI21xuzhina_dump_c6_s3_ex
typeinfo for xuzhina_dump_c6_s3_ex
[buckxu@xuzhina 3_ex]$ c++filt _ZN21xuzhina_dump_c6_s3_ex8parseValEPc
xuzhina_dump_c6_s3_ex::parseVal(char*)
[buckxu@xuzhina 3_ex]$ c++filt _ZN21xuzhina_dump_c6_s3_ex6encodeEPc
xuzhina_dump_c6_s3_ex::encode(char*)

可以看到类xuzhina_dump_c6_s3_ex有两个虚函数parseVal和encode,parseVal在encode前面。也就是说,在encode前面只是增加了一个虚函数。如果增加多个,可以从这里的顺序数出来的。

下一篇
举报
领券