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

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

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

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

testso.h的代码如下:

  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的代码如下:

  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的代码如下:

  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	 }

编译代码:

[[email protected] 3_ex]$ g++ -o libtestso.so -shared -fpic testso.cpp
[[email protected] 3_ex]$ g++ -o xuzhina_dump_c6_s3_ex xuzhina_dump_c6_s3_ex.cpp -L. -ltestso

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

[[email protected] 3_ex]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.;./xuzhina_dump_c6_s3_ex
the second char:e

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

  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(为什么?这是模拟几个部门协作的产品开发过程,往往有些版本制作人为了省事,没有清除重新编译)

[[email protected] 3_ex]$ g++ -o libtestso.so -shared -fpic testso.cpp

运行xuzhina_dump_c6_s3_ex,它coredump了。

[[email protected] 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文件,可以见到如下堆栈:

(gdb) bt
#0  0x08048630 in main ()

看一下main函数的汇编:

(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 <[email protected]>
   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 <[email protected]>
   0x08048646 <+102>:   mov    $0x0,%eax
   0x0804864b <+107>:   mov    -0x4(%ebp),%ebx
   0x0804864e <+110>:   leave
   0x0804864f <+111>:   ret
End of assembler dump.

看一下寄存器eax的值

(gdb) i r eax
eax            0x1      1

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

   0x08048623 <+67>:    call   *%eax
   0x08048625 <+69>:    mov    %eax,0x14(%esp)
   0x08048629 <+73>:    mov    0x14(%esp),%eax
   0x0804862d <+77>:    add    $0x1,%eax

可知是

   0x08048623 <+67>:    call   *%eax

的返回值。

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

   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所指向单元取来的,而由

   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指针。

那么,

   0x0804860c <+44>:    mov    0x18(%esp),%eax
   0x08048610 <+48>:    mov    (%eax),%eax
   0x08048612 <+50>:    mov    (%eax),%eax

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

(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的虚函数表地址。

(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是

(gdb) shell c++filt _ZTV21xuzhina_dump_c6_s3_ex
vtable for xuzhina_dump_c6_s3_ex

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

(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来看。

[[email protected] 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

[[email protected] 3_ex]$ c++filt _ZTI21xuzhina_dump_c6_s3_ex
typeinfo for xuzhina_dump_c6_s3_ex
[[email protected] 3_ex]$ c++filt _ZN21xuzhina_dump_c6_s3_ex8parseValEPc
xuzhina_dump_c6_s3_ex::parseVal(char*)
[[email protected] 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前面只是增加了一个虚函数。如果增加多个,可以从这里的顺序数出来的。

时间: 2024-11-03 21:57:39

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

《coredump问题原理探究》Linux x86版6.4节虚函数

在上一节已经探究了类的成员变量的排列,现在看一下虚函数表和成员变量的排列及虚函数之间的排列. 先看一个例子: 1 #include <stdio.h> 2 class xuzhina_dump_c06_s3 3 { 4 private: 5 int m_a; 6 public: 7 xuzhina_dump_c06_s3() { m_a = 0; } 8 virtual void inc() { m_a++; } 9 virtual void dec() { m_a--; } 10 virtu

《coredump问题原理探究》Windows版 笔记

<coredump问题原理探究>Windows版 笔记 Debug 一.环境搭建 1.Win7捕获程序dump 2.Windbg符号表设置(Symbols Search Path) 二.WinDbg命令 三.函数栈帧 1.栈内存布局 2.栈溢出 3.栈的规律 4.定位栈溢出问题的经验方法 四.函数逆向 五.C内存布局 1.基本类型 2.数组类型 3.结构体 六.C++内存布局 1.类的内存布局 2.this指针 3.虚函数表及虚表指针 4.单继承 5.多继承(无公共基类) 七.STL容器内存布

《coredump问题原理探究》Linux x86版6.1节C++风格数据结构内存布局之无成员变量的类

在探究完C风格数据结构内存布局之后,接着探究C++风格数据结构内存布局. 虽然最简单的类是没有任何成员变量和成员函数,但由于没什么意义,不值得探究.在这里,就先探究一下没有任何成员变量和虚函数,只有成员函数的类. 先看一下例子: 1 #include <stdio.h> 2 class xuzhina_dump_c06_s1 3 { 4 public: 5 void hello() 6 { 7 printf( "hello\n" ); 8 } 9 void print()

《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子

在探究完类成员变量分布后,来定位一个coredump例子来实践一把: (gdb) bt #0 0x0804863c in xuzhina_dump_c06_s2_ex::print() () #1 0x08048713 in main () 看一下xuzhina_dump_c06_s2_ex::print的汇编: (gdb) disassemble 0x0804863c Dump of assembler code for function _ZN22xuzhina_dump_c06_s2_ex

《coredump问题原理探究》Linux x86版7.2节vector coredump例子

看一个coredump的例子: [[email protected] s1_ex]$ gdb xuzhina_dump_c07_s1_ex core.27776 GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses

《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类

上面一节已经探究出this指针的辨别,由this指针就可以看到类的内容.在这里,就由this指针来看一下类的成员变量是如何排列. 先看一个例子 1 #include <stdio.h> 2 class xuzhina_dump_c06_s2 3 { 4 private: 5 short m_c; 6 char m_d; 7 int m_e; 8 9 public: 10 xuzhina_dump_c06_s2( int a, int b ) 11 { 12 m_c = (short)(a +

《coredump问题原理探究》Linux x86版7.5节 Map对象

先看一个例子: 1 #include <map> 2 3 int main() 4 { 5 std::map<int,int> iMap; 6 7 iMap[5] = 6; 8 iMap[8] = 20; 9 iMap[2] = 80; 10 11 return 0; 12 } 看一下汇编: (gdb) disassemble main Dump of assembler code for function main: 0x080486e4 <+0>: push %eb

《coredump问题原理探究》Linux x86版6.8节多继承coredump例子

下面看一个coredump的例子: (gdb) bt #0 0x08048662 in xuzhina_dump_c06_s5_ex_child::inheritFrom(char*, int) () #1 0x08048609 in main () 先看一下xuzhina_dump_c06_s5_ex_child::inheritFrom的汇编: (gdb) disassemble 0x08048662 Dump of assembler code for function _ZN28xuzh

《coredump问题原理探究》Linux x86版7.4节List coredump例子

看一个coredump例子: 看一个coredump例子: Core was generated by `./xuzhina_dump_c07_s2_ex'. Program terminated with signal 11, Segmentation fault. #0 0x0285b9b7 in std::_List_node_base::hook(std::_List_node_base*) () from /usr/lib/libstdc++.so.6 Missing separate