首页 > 代码库 > 《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子

《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_ex5printEv:
   0x08048610 <+0>:     push   %ebp
   0x08048611 <+1>:     mov    %esp,%ebp
   0x08048613 <+3>:     sub    $0x28,%esp
   0x08048616 <+6>:     movl   $0x0,-0xc(%ebp)
   0x0804861d <+13>:    jmp 0x804869b <_ZN22xuzhina_dump_c06_s2_ex5printEv+139>
   0x0804861f <+15>:    mov    0x8(%ebp),%eax
   0x08048622 <+18>:    movzwl (%eax),%eax
   0x08048625 <+21>:    cwtl   
   0x08048626 <+22>:    test   %eax,%eax
   0x08048628 <+24>:    je     0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33>
   0x0804862a <+26>:    cmp    $0x1,%eax
   0x0804862d <+29>:    je     0x8048654 <_ZN22xuzhina_dump_c06_s2_ex5printEv+68>
   0x0804862f <+31>:    jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102>
   0x08048631 <+33>:    mov    0x8(%ebp),%eax
   0x08048634 <+36>:    mov    0x14(%eax),%edx
   0x08048637 <+39>:    mov    -0xc(%ebp),%eax
   0x0804863a <+42>:    add    %edx,%eax
=> 0x0804863c <+44>:    movzbl (%eax),%eax
   0x0804863f <+47>:    movsbl %al,%eax
   0x08048642 <+50>:    mov    %eax,0x4(%esp)
   0x08048646 <+54>:    movl   $0x80487c4,(%esp)
   0x0804864d <+61>:    call   0x80484a0 <printf@plt>
   0x08048652 <+66>:    jmp 0x8048697 <_ZN22xuzhina_dump_c06_s2_ex5printEv+135>
   0x08048654 <+68>:    mov    0x8(%ebp),%eax
   0x08048657 <+71>:    mov    0x14(%eax),%eax
   0x0804865a <+74>:    mov    -0xc(%ebp),%edx
   0x0804865d <+77>:    shl    $0x2,%edx
   0x08048660 <+80>:    add    %edx,%eax
   0x08048662 <+82>:    flds   (%eax)
   0x08048664 <+84>:    fstpl  0x4(%esp)
   0x08048668 <+88>:    movl   $0x80487c8,(%esp)
   0x0804866f <+95>:    call   0x80484a0 <printf@plt>
   0x08048674 <+100>:   jmp 0x8048697 <_ZN22xuzhina_dump_c06_s2_ex5printEv+135>
   0x08048676 <+102>:   mov    0x8(%ebp),%eax
   0x08048679 <+105>:   mov    0x14(%eax),%eax
   0x0804867c <+108>:   mov    -0xc(%ebp),%edx
   0x0804867f <+111>:   shl    $0x2,%edx
   0x08048682 <+114>:   add    %edx,%eax
   0x08048684 <+116>:   mov    (%eax),%eax
   0x08048686 <+118>:   mov    %eax,0x4(%esp)
   0x0804868a <+122>:   movl   $0x80487cc,(%esp)
   0x08048691 <+129>:   call   0x80484a0 <printf@plt>
   0x08048696 <+134>:   nop
   0x08048697 <+135>:   addl   $0x1,-0xc(%ebp)
   0x0804869b <+139>:   mov    0x8(%ebp),%eax
   0x0804869e <+142>:   mov    0x18(%eax),%eax
   0x080486a1 <+145>:   cmp    -0xc(%ebp),%eax
   0x080486a4 <+148>:   seta   %al
   0x080486a7 <+151>:   test   %al,%al
   0x080486a9 <+153>:   jne    0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15>
   0x080486af <+159>:   leave  
   0x080486b0 <+160>:   ret    
End of assembler dump.

由于从符号表可以看到xuzhina_dump_c06_s2_ex::print是类xuzhina_dump_c06_s2_ex的成员函数,那么由调用类成员函数时,this指针作为第一个参数传递的规则,知道ebp+8存放着this指针。

再由崩溃指令地址是0x0804863c,可以把这个指令地址附近的汇编分析如下:

   0x0804862f <+31>:    jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102>
   0x08048631 <+33>:    mov    0x8(%ebp),%eax			//this指针
   0x08048634 <+36>:    mov    0x14(%eax),%edx			//偏移this指针20个字节的成员变量
   0x08048637 <+39>:    mov    -0xc(%ebp),%eax			//某个局部变量
   0x0804863a <+42>:    add    %edx,%eax				//该成员变量为数组
=> 0x0804863c <+44>:    movzbl (%eax),%eax				//该成员变量是char数组

而0x0804862f是无条件跳转,也就是说,0x08048631这条指令应该由其它地方跳转过来的。

   0x08048628 <+24>:    je     0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33>

查看一下0x00401073附近的汇编,可分析如下

   0x0804861f <+15>:    mov    0x8(%ebp),%eax		//this指针
   0x08048622 <+18>:    movzwl (%eax),%eax			//第一个成员变量,类型是short
   0x08048625 <+21>:    cwtl   
   0x08048626 <+22>:    test   %eax,%eax				//第一个成员变量是否为0
   0x08048628 <+24>:    je     0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33>
   0x0804862a <+26>:    cmp    $0x1,%eax				//第一个成员变量是否为1
   0x0804862d <+29>:    je     0x8048654 <_ZN22xuzhina_dump_c06_s2_ex5printEv+68>
   0x0804862f <+31>:    jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102>

由此可见,当第一个成员变量(命名为flag)为0时,才会执行崩溃指令所在的那个分支。也就是说,这一段汇编的结构如下

if ( flag == 0 )
{
…
}
else if ( flag == 1 )
{
….
}
else
{
…
}

现在尝试着找出ebp-0xc这个局部变量是处于什么作用。

   0x08048616 <+6>:     movl   $0x0,-0xc(%ebp)
   0x0804861d <+13>:    jmp  x804869b <_ZN22xuzhina_dump_c06_s2_ex5printEv+139>
   0x0804861f <+15>:    mov    0x8(%ebp),%eax

   0x08048697 <+135>:   addl   $0x1,-0xc(%ebp)
   0x0804869b <+139>:   mov    0x8(%ebp),%eax
   0x0804869e <+142>:   mov    0x18(%eax),%eax
   0x080486a1 <+145>:   cmp    -0xc(%ebp),%eax
   0x080486a4 <+148>:   seta   %al
   0x080486a7 <+151>:   test   %al,%al
   0x080486a9 <+153>:   jne    0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15>

由上面一段,可以看出,ebp-0xc存放着是一个计数变量,也就是一个索引,且处于一个指令地址由0x804861f-0x080486a9范围组成的循环。那么

   0x08048631 <+33>:    mov    0x8(%ebp),%eax			//this指针
   0x08048634 <+36>:    mov    0x14(%eax),%edx			//偏移this指针20个字节的成员变量
   0x08048637 <+39>:    mov    -0xc(%ebp),%eax			//某个局部变量
   0x0804863a <+42>:    add    %edx,%eax				//该成员变量为数组
=> 0x0804863c <+44>:    movzbl (%eax),%eax				//该成员变量是char数组

可以看出eax+14h即this指针偏移20个字节的成员变量是char指针,命名为ptr。由

   0x0804869b <+139>:   mov    0x8(%ebp),%eax
   0x0804869e <+142>:   mov    0x18(%eax),%eax
   0x080486a1 <+145>:   cmp    -0xc(%ebp),%eax
   0x080486a4 <+148>:   seta   %al
   0x080486a7 <+151>:   test   %al,%al
   0x080486a9 <+153>:   jne    0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15>

可推知this指针偏移0x18即24个字节的成员变量是一个最大整数,命名为num。

从上面可以知道,类xuzhina_dump_c06_s2_ex大概如下:

class xuzhina_dump_c06_s2_ex
{
private:
   short flag;   //第一个成员变量
   char unknown[n];  //未知数目成员变量,只以字节来算。n = 16或n=18
   char* ptr;        
   int num;         //指定上面ptr的元素个数
public:
   void print();
 ………….
};

(gdb) x /x $ebp+8
0xbfe09690:     0xbfe096b4
(gdb) x /x 0xbfe096b4+0x14
0xbfe096c8:     0x69766544
(gdb) x /x 0x69766544
0x69766544:     Cannot access memory at address 0x69766544

可知ptr这个类成员变量被修改为0x69766544,所以才会coredump。现在看一下这个对象的数值:

(gdb) x /8x 0xbfe096b4
0xbfe096b4:     0x68540000      0x73497369      0x726f5741      0x7546646c
0xbfe096c4:     0x664f6c6c      0x69766544      0x4f6f4e6c      0x6143656e

从这些数值来看,它们大多处于ASCII码的范围,就是0-127(0x7f)。试一下看是不是。

由于第一个成员变量flag占两个字节,那么查看的起始地址应该是0xbfe096b6.

(gdb) x /s 0xbfe096b6
0xbfe096b6:      "ThisIsAWorldFullOfDevilNoOneCanSurvie"


它的长度是37。也就是说,它把ptr,num的值都覆盖了。

那么它是从哪里来的,是在构造时就已经设置还是在别的地方。为了弄清楚这个,先看一下main函数的汇编:

(gdb) disassemble main
Dump of assembler code for function main:
   0x080486b1 <+0>:     push   %ebp
   0x080486b2 <+1>:     mov    %esp,%ebp
   0x080486b4 <+3>:     and    $0xfffffff0,%esp
   0x080486b7 <+6>:     sub    $0x40,%esp
   0x080486ba <+9>:     cmpl   $0x2,0x8(%ebp)
   0x080486be <+13>:    jg     0x80486c7 <main+22>
   0x080486c0 <+15>:    mov    $0xffffffff,%eax
   0x080486c5 <+20>:    jmp    0x8048718 <main+103>
   0x080486c7 <+22>:    mov    0xc(%ebp),%eax
   0x080486ca <+25>:    add    $0x8,%eax
   0x080486cd <+28>:    mov    (%eax),%eax
   0x080486cf <+30>:    mov    %eax,(%esp)
   0x080486d2 <+33>:    call   0x8048480 <strlen@plt>
   0x080486d7 <+38>:    mov    0xc(%ebp),%edx
   0x080486da <+41>:    add    $0x8,%edx
   0x080486dd <+44>:    mov    (%edx),%ecx
   0x080486df <+46>:    mov    0xc(%ebp),%edx
   0x080486e2 <+49>:    add    $0x4,%edx
   0x080486e5 <+52>:    mov    (%edx),%edx
   0x080486e7 <+54>:    mov    %eax,0x10(%esp)
   0x080486eb <+58>:    mov    %ecx,0xc(%esp)
   0x080486ef <+62>:    movl   $0x0,0x8(%esp)
   0x080486f7 <+70>:    mov    %edx,0x4(%esp)
   0x080486fb <+74>:    lea    0x24(%esp),%eax
   0x080486ff <+78>:    mov    %eax,(%esp)
   0x08048702 <+81>:    call   0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj>
   0x08048707 <+86>:    lea    0x24(%esp),%eax
   0x0804870b <+90>:    mov    %eax,(%esp)
   0x0804870e <+93>:    call   0x8048610 <_ZN22xuzhina_dump_c06_s2_ex5printEv>
   0x08048713 <+98>:    mov    $0x0,%eax
   0x08048718 <+103>:   jmp    0x8048722 <main+113>
   0x0804871a <+105>:   mov    %eax,(%esp)
   0x0804871d <+108>:   call   0x80484c0 <_Unwind_Resume@plt>
   0x08048722 <+113>:   leave  
   0x08048723 <+114>:   ret    
End of assembler dump.

通过结合上面汇编和

(gdb) shell c++filt _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj
xuzhina_dump_c06_s2_ex::xuzhina_dump_c06_s2_ex(char*, short, void*, unsigned int)
(gdb) shell c++filt _ZN22xuzhina_dump_c06_s2_ex5printEv 
xuzhina_dump_c06_s2_ex::print()

可知,main函数只是调用了四个函数,strlen,类xuzhina_dump_c06_s2_ex的构造函数和成员函数print,及_Unwind_Resume(说明可见http://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/baselib--unwind-resume.html)

可见,main函数只是调用了类xuzhina_dump_c06_s2_ex两个对外接口,一个构造函数,一个print成员函数。那么,类xuzhina_dump_c06_s2_ex的成员变量ptr,num应该是在构造函数里初始化的。

由下面

   0x080486e7 <+54>:    mov    %eax,0x10(%esp)
   0x080486eb <+58>:    mov    %ecx,0xc(%esp)
   0x080486ef <+62>:    movl   $0x0,0x8(%esp)
   0x080486f7 <+70>:    mov    %edx,0x4(%esp)
   0x080486fb <+74>:    lea    0x24(%esp),%eax
   0x080486ff <+78>:    mov    %eax,(%esp)
   0x08048702 <+81>:    call   0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj>

从四个push指令,可以猜测,类xuzhina_dump_c06_s2_ex构造函数有四个参数(五个参数减去默认的this指针这个参数)。

看一下构造函数:

(gdb) disassemble _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj
Dump of assembler code for function _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj:
   0x080485d0 <+0>:     push   %ebp
   0x080485d1 <+1>:     mov    %esp,%ebp
   0x080485d3 <+3>:     sub    $0x28,%esp
   0x080485d6 <+6>:     mov    0x10(%ebp),%eax
   0x080485d9 <+9>:     mov    %ax,-0xc(%ebp)
   0x080485dd <+13>:    mov    0x8(%ebp),%eax
   0x080485e0 <+16>:    mov    0x14(%ebp),%edx
   0x080485e3 <+19>:    mov    %edx,0x14(%eax)
   0x080485e6 <+22>:    mov    0x8(%ebp),%eax
   0x080485e9 <+25>:    mov    0x18(%ebp),%edx
   0x080485ec <+28>:    mov    %edx,0x18(%eax)
   0x080485ef <+31>:    mov    0x8(%ebp),%eax
   0x080485f2 <+34>:    lea    0x2(%eax),%edx
   0x080485f5 <+37>:    mov    0xc(%ebp),%eax
   0x080485f8 <+40>:    mov    %eax,0x4(%esp)
   0x080485fc <+44>:    mov    %edx,(%esp)
   0x080485ff <+47>:    call   0x8048490 <strcpy@plt>
   0x08048604 <+52>:    mov    0x8(%ebp),%eax
   0x08048607 <+55>:    movzwl -0xc(%ebp),%edx
   0x0804860b <+59>:    mov    %dx,(%eax)
   0x0804860e <+62>:    leave  
   0x0804860f <+63>:    ret    
End of assembler dump.

   0x080485d6 <+6>:     mov    0x10(%ebp),%eax

   0x080485dd <+13>:    mov    0x8(%ebp),%eax

   0x080485e0 <+16>:    mov    0x14(%ebp),%edx

   0x080485e6 <+22>:    mov    0x8(%ebp),%eax

   0x080485f5 <+37>:    mov    0xc(%ebp),%eax



这五条指令来看,类xuzhina_dump_c06_s2_ex构造函数的参数确实是四个。

再由ebp+8放着this指针和下面四段代码

   0x080485dd <+13>:    mov    0x8(%ebp),%eax
   0x080485e0 <+16>:    mov    0x14(%ebp),%edx
   0x080485e3 <+19>:    mov    %edx,0x14(%eax)

   0x080485e6 <+22>:    mov    0x8(%ebp),%eax
   0x080485e9 <+25>:    mov    0x18(%ebp),%edx
   0x080485ec <+28>:    mov    %edx,0x18(%eax)

   0x080485ef <+31>:    mov    0x8(%ebp),%eax
   0x080485f2 <+34>:    lea    0x2(%eax),%edx
   0x080485f5 <+37>:    mov    0xc(%ebp),%eax

   0x08048604 <+52>:    mov    0x8(%ebp),%eax
   0x08048607 <+55>:    movzwl -0xc(%ebp),%edx
   0x0804860b <+59>:    mov    %dx,(%eax)


可知,类xuzhina_dump_c06_s2_ex只有四个成员变量。且由

   0x080485ef <+31>:    mov    0x8(%ebp),%eax
   0x080485f2 <+34>:    lea    0x2(%eax),%edx		//只是偏移2个字节
   0x080485f5 <+37>:    mov    0xc(%ebp),%eax
   0x080485f8 <+40>:    mov    %eax,0x4(%esp)
   0x080485fc <+44>:    mov    %edx,(%esp)
   0x080485ff <+47>:    call   0x8048490 <strcpy@plt>


可知,第二个成员变量是char数组,大小是18的字节。也就是说,这个类的定义大概如下:

class xuzhina_dump_c06_s2_ex
{
private:
   short flag;   //第一个成员变量
   char str[18];  //暂命名为str
   char* ptr;     
   int num;         //指定ptr的元素个数
public:
   void print();
 ………….
};

由于ptr(偏移值在0x14h)这个成员变量是在str后面,且

   0x080485ef <+31>:    mov    0x8(%ebp),%eax
   0x080485f2 <+34>:    lea    0x2(%eax),%edx
   0x080485f5 <+37>:    mov    0xc(%ebp),%eax
   0x080485f8 <+40>:    mov    %eax,0x4(%esp)
   0x080485fc <+44>:    mov    %edx,(%esp)
   0x080485ff <+47>:    call   0x8048490 <strcpy@plt>

所以,ptr的值有可能是在strcpy时被覆盖的。由strcpy的原型

char *strcpy(char *dest, const char *src);

  0x080485f5 <+37>:   mov    0xc(%ebp),%eax

  0x080485f8 <+40>:    mov    %eax,0x4(%esp)

可知,src是类xuzhina_dump_c06_s2_ex构造函数的第一个参数(不是指默认this指针)。那么跳到main函数桢看一下,传递给构造函数的第一个参数是从哪里来的。

由main函数这一段汇编

   0x080486df <+46>:    mov    0xc(%ebp),%edx	//第二个参数argv
   0x080486e2 <+49>:    add    $0x4,%edx			// argv[1]的地址
   0x080486e5 <+52>:    mov    (%edx),%edx
   0x080486e7 <+54>:    mov    %eax,0x10(%esp)
   0x080486eb <+58>:    mov    %ecx,0xc(%esp)
   0x080486ef <+62>:    movl   $0x0,0x8(%esp)
   0x080486f7 <+70>:    mov    %edx,0x4(%esp)
   0x080486fb <+74>:    lea    0x24(%esp),%eax
   0x080486ff <+78>:    mov    %eax,(%esp)
   0x08048702 <+81>:    call   0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj>

可知,类xuzhina_dump_c06_s2_ex构造函数的第一个参数是由main函数的第二个参数argv[1]传入的。看一下argv[1]是多少

(gdb) x $ebp+0xc
0xbfe096e4:     0xbfe09774
(gdb) x 0xbfe09774+4
0xbfe09778:     0xbfe0a6a3
(gdb) x /s 0xbfe0a6a3
0xbfe0a6a3:      "ThisIsAWorldFullOfDevilNoOneCanSurvie"

和上面分析出来的字符串正好是一样的。也就是说,由于不正确使用了strcpy,导致改写了ptr,导致了coredump。

 

例子的源代码:

  1	 #include <string.h>
  2	 #include <stdio.h>
  3	 class xuzhina_dump_c06_s2_ex
  4	 {
  5	     private:
  6	         short m_type;
  7	         char m_name[16];
  8	         void* m_ptr;
  9	         unsigned int m_len;
 10	     public:
 11	         xuzhina_dump_c06_s2_ex( char* name, short type,
 12	                 void* data, unsigned int len );
 13	         void print();
 14	 };
 15 
 16	 xuzhina_dump_c06_s2_ex::xuzhina_dump_c06_s2_ex( char* name, short type,
 17	         void* data, unsigned int len )
 18	 {
 19	     m_ptr = data;
 20	     m_len = len;
 21	     strcpy( m_name, name );
 22	     m_type = type;
 23	 
 24	 }
 25 
 26	 void xuzhina_dump_c06_s2_ex::print()
 27	 {
 28	     for ( unsigned int i = 0; i < m_len; i++ )
 29	     {
 30	         switch( m_type )
 31	         {
 32	             case 0:
 33	                 printf( "%c ", *((char*)m_ptr + i ) );
 34	                 break;
 35	             case 1:
 36	                 printf( "%f ", *((float*)m_ptr + i ) );
 37	                 break;
 38	             default:
 39	                 printf( "%d ", *((int*)m_ptr + i ) );
 40	                 break;
 41	         }
 42	     }
43	 }
 44	 
 45	 int main(int argc, char* argv[] )
 46	 {
 47	     if ( argc < 3 )
 48	     {
 49	         return -1;
 50	     }
 51	     xuzhina_dump_c06_s2_ex test( argv[1], 0,
 52	             argv[2], strlen( argv[2] ) );
 53	     test.print();
 54 
 55	     return 0;
 56	 }



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