標籤:linux 程式崩潰 調試技術 c++ 彙編
下面看一個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 0x08048662Dump of assembler code for function _ZN28xuzhina_dump_c06_s5_ex_child11inheritFromEPci: 0x08048640 <+0>: push %ebp 0x08048641 <+1>: mov %esp,%ebp 0x08048643 <+3>: sub $0x18,%esp 0x08048646 <+6>: mov 0x8(%ebp),%eax 0x08048649 <+9>: mov (%eax),%eax 0x0804864b <+11>: mov (%eax),%eax 0x0804864d <+13>: mov 0x8(%ebp),%edx 0x08048650 <+16>: mov 0xc(%ebp),%ecx 0x08048653 <+19>: mov %ecx,0x4(%esp) 0x08048657 <+23>: mov %edx,(%esp) 0x0804865a <+26>: call *%eax 0x0804865c <+28>: mov 0x8(%ebp),%eax 0x0804865f <+31>: mov 0xc(%eax),%eax=> 0x08048662 <+34>: mov (%eax),%eax 0x08048664 <+36>: mov 0x8(%ebp),%edx 0x08048667 <+39>: lea 0xc(%edx),%ecx 0x0804866a <+42>: mov 0x10(%ebp),%edx 0x0804866d <+45>: mov %edx,0x4(%esp) 0x08048671 <+49>: mov %ecx,(%esp) 0x08048674 <+52>: call *%eax 0x08048676 <+54>: mov 0x8(%ebp),%eax 0x08048679 <+57>: movl $0x1,0x14(%eax) 0x08048680 <+64>: leave 0x08048681 <+65>: ret End of assembler dump.
由
0x0804865c <+28>: mov 0x8(%ebp),%eax 0x0804865f <+31>: mov 0xc(%eax),%eax=> 0x08048662 <+34>: mov (%eax),%eax 0x08048664 <+36>: mov 0x8(%ebp),%edx 0x08048667 <+39>: lea 0xc(%edx),%ecx 0x0804866a <+42>: mov 0x10(%ebp),%edx 0x0804866d <+45>: mov %edx,0x4(%esp) 0x08048671 <+49>: mov %ecx,(%esp) 0x08048674 <+52>: call *%eax
可以知道來看,eax是一個虛函數表指標
由崩潰指令來看,eax所指向地址非法,而eax是由this加上0xc位移值得到,this放在ebp+0x8
看一下this所指向的內容
(gdb) x /x $ebp+8 0xbff59da0: 0x08c03008(gdb) x /8x 0x08c030080x8c03008: 0x08048798 0x6c6c6548 0x726f576f 0x6854646c0x8c03018: 0x73497369 0x69766544 0x0000006c 0x00020fe1
由於這個地址0x08c03008下一個單元0x08c0300c及後續幾個單元的每個位元組都少於0x80,有可能是ascii碼.
而且還可以由
(gdb) i r eaxeax 0x6854646c 1750361196
看到eax的值放在0x8c03014這個單元。
看一下0x08c0300c開始的是不是字串:
(gdb) x /s 0x08c0300c0x8c0300c: "HelloWorldThisIsDevil"(gdb) x /s 0x8c030140x8c03014: "ldThisIsDevil"
可見,確實是有一個字串在裡面,且this+0xc這個虛函數表指標剛好是字串” ldThisIsDevil”.說明剛好是被前面的成員變數覆蓋了.為什麼被覆蓋了?
在xuzhina_dump_c06_s5_ex!xuzhina_dump_c06_s5_ex_child::inheritFrom這個函數裡,有兩處調用.先看一下前一個調用是什麼,有沒有可能把這個虛函數表指標給覆蓋掉,如果沒有,就看一下main函數有沒有調用這個類的其它成員函數了.
由這一段指令
0x08048646 <+6>: mov 0x8(%ebp),%eax 0x08048649 <+9>: mov (%eax),%eax 0x0804864b <+11>: mov (%eax),%eax 0x0804864d <+13>: mov 0x8(%ebp),%edx 0x08048650 <+16>: mov 0xc(%ebp),%ecx 0x08048653 <+19>: mov %ecx,0x4(%esp) 0x08048657 <+23>: mov %edx,(%esp) 0x0804865a <+26>: call *%eax
可以知道,這個函數是從第一個虛函數表取出來的第一個函數.
(gdb) x /x $ebp+8 0xbff59da0: 0x08c03008(gdb) x /4x 0x08c030080x8c03008: 0x08048798 0x6c6c6548 0x726f576f 0x6854646c(gdb) x /4x 0x080487980x8048798 <_ZTV28xuzhina_dump_c06_s5_ex_child+8>: 0x08048614 0x08048640 0xfffffff4 0x08048800
從上面可以看到,是調用了setName這個函數,有一個參數.這個參數的值在xuzhina_dump_c06_s5_ex_child::inheritFrom由ebp+c傳入.
看一下ebp+c的內容:
(gdb) x /x $ebp+0xc0xbff59da4: 0xbff5a672(gdb) x /s 0xbff5a6720xbff5a672: "HelloWorldThisIsDevil"
由這可以推斷,是由xuzhina_dump_c06_s5_ex_father::setName這個函數導致第二個虛函數表指標被改寫的.
看一下xuzhina_dump_c06_s5_ex_father::setName做了什麼事情:
(gdb) disassemble _ZN29xuzhina_dump_c06_s5_ex_father7setNameEPcDump of assembler code for function _ZN29xuzhina_dump_c06_s5_ex_father7setNameEPc: 0x08048614 <+0>: push %ebp 0x08048615 <+1>: mov %esp,%ebp 0x08048617 <+3>: sub $0x18,%esp 0x0804861a <+6>: mov 0x8(%ebp),%eax 0x0804861d <+9>: lea 0x4(%eax),%edx 0x08048620 <+12>: mov 0xc(%ebp),%eax 0x08048623 <+15>: mov %eax,0x4(%esp) 0x08048627 <+19>: mov %edx,(%esp) 0x0804862a <+22>: call 0x8048490 <[email protected]> 0x0804862f <+27>: leave 0x08048630 <+28>: ret End of assembler dump.
通過逆向上面的彙編,可以得到這一個函數是參數1的值一個字元一個字元地複製到這個對象的第一個成員變數(this+4)裡.在這個coredump裡,參數1的值是”HelloWorldThisIsDevil”,長度為21,由第一個參數開始,即(+4).而這個對象的第二個虛函數表指標位於+c的位置,剛好被” ldThisIsDevil”來覆蓋.
原始碼如下:
1 #include <string.h> 2 class xuzhina_dump_c06_s5_ex_father 3 { 4 private: 5 char m_name[8]; 6 public: 7 virtual void setName( char* name ) 8 { 9 strcpy( m_name, name ); 10 } 11 }; 12 13 class xuzhina_dump_c06_s5_ex_mother 14 { 15 private: 16 int m_nature; 17 public: 18 virtual void setNature( int nature ) 19 { 20 m_nature = nature; 21 } 22 }; 23 24 class xuzhina_dump_c06_s5_ex_child: public xuzhina_dump_c06_s5_ex_father, 25 public xuzhina_dump_c06_s5_ex_mother 26 { 27 private: 28 int m_sweet; 29 public: 30 virtual void inheritFrom( char* lastName, int nature ) 31 { 32 setName( lastName ); 33 setNature( nature ); 34 m_sweet = 1; 35 } 36 }; 3738 int main( int argc, char* argv[] ) 39 { 40 if ( argc < 2 ) 41 { 42 return -1; 43 } 44 45 xuzhina_dump_c06_s5_ex_child* child = new xuzhina_dump_c06_s5_ex_child; 46 child->inheritFrom( argv[1], 1 ); 47 48 return 0; 49 }
《coredump問題原理探究》Linux x86版6.8節多繼承coredump例子