『coredump問題原理探究』Linux 86版6.5節虚関数のcoredump例
9493 ワード
大規模なプロジェクトでは、バージョンが一致しないという問題が発生しやすく、虚関数のフローティングの問題は解決しにくい.
ここでは,この問題をどのように解決するかを一例で説明する.
3つのソースファイルを作成します:testso.h,testso.cpp,xuzhina_dump_c6_s3_ex.cpp.
testso.hのコードは以下の通りである.
testso.cppのコードは以下の通りです.
xuzhina_dump_c6_s3_ex.cppのコードは以下の通りです.
コンパイルコード:
xuzhina_の実行dump_c6_s3_exは、次のような結果を得ることができます.
今はtestsoです.hのクラスに虚関数が追加されました(虚関数宣言の順序に注意してください):
soファイルを再生成しますが、xuzhina_は再生成されません.dump_c6_s3_ex(なぜですか?これはいくつかの部門の協力をシミュレートした製品開発プロセスであり、多くの場合、一部のバージョンのプロデューサーは手間を省くために再コンパイルをクリアしていない)
xuzhina_の実行dump_c6_s3_ex、coredumpです.
coredumpファイルを開くと、次のスタックが表示されます.
main関数のアセンブリを見てみましょう.
レジスタeaxの値を見てみましょう
eaxの値が1のためであることがわかる.eaxは
わかりました
で行ないます.
では、eaxに格納されている関数ポインタはどこから来たのでしょうか.
明らかに、最終的にはesp+0 x 18が指すユニットから取り出され、
分かるように、esp+0 x 18はクラスxuzhina_を格納しているdump_c6_s3_ex(shell c++filt_ZN 21 xuzhina_dump_c 6_s 3_exC 2 Ev)オブジェクトのthisポインタ.
では、
このポインタから虚関数を取り出す操作です.クラスxuzhinaを見てください.dump_c6_s3_exの虚関数テーブル:
とてもおかしいです.コードロジックではxuzhina_dump_c6_s3_ex::encode(char*)が正しいです.
ここまで位置づけるとクラスxuzhina_がわかりますdump_c6_s3_exが存在するヘッダファイルは変更されましたが、再コンパイルは行われず、メンバーの虚関数encodeの前に虚関数が追加されました.どうして後ろじゃないの?興味のある読者はこのような状況を試してみることができます.
新しいバージョンのヘッダファイルが修正されていることがわかり、残りの作業はdiffなどの比較ツールで修正箇所を見つけることができます.
もちろん、どれだけの虚関数が追加されたかを見る方法もあります.
以上の解析から、0 x 08049958はクラスxuzhina_であることがわかるdump_c6_s3_exの虚函数表アドレス.
オフセット_であることがわかります.ZTV21xuzhina_dump_c6_s3_ex,そして_ZTV21xuzhina_dump_c6_s3_exは
では、_ZTV21xuzhina_dump_c6_s3_exのアドレスは0 x 0804950です.その内容を見てみましょう
つまり、クラスxuzhina_dump_c6_s3_exの直後に虚関数テーブルが表示されます.興味があれば他のクラスを試してみてもいいですが、そうです.
では、クラスxuzhinaを見てみましょう.dump_c6_s3_exが存在するsoファイルlibtestso.so,objdumpで見る.
クラスxuzhina_が表示されますdump_c6_s3_exには2つの虚関数parseValとencodeがあり、parseValはencodeの前にあります.すなわち,encodeの前に虚関数が1つ追加されただけである.複数追加すれば、ここからの順番で数えられます.
ここでは,この問題をどのように解決するかを一例で説明する.
3つのソースファイルを作成します: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
", p[1] );
12
13 return 0;
14 }
コンパイルコード:
[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は、次のような結果を得ることができます.
[buckxu@xuzhina 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(なぜですか?これはいくつかの部門の協力をシミュレートした製品開発プロセスであり、多くの場合、一部のバージョンのプロデューサーは手間を省くために再コンパイルをクリアしていない)
[buckxu@xuzhina 3_ex]$ g++ -o libtestso.so -shared -fpic testso.cpp
xuzhina_の実行dump_c6_s3_ex、coredumpです.
[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ファイルを開くと、次のスタックが表示されます.
(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 <_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の値を見てみましょう
(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+0 x 18が指すユニットから取り出され、
0x08048600 <+32>: mov %ebx,(%esp)
0x08048603 <+35>: call 0x8048650 <_ZN21xuzhina_dump_c6_s3_exC2Ev>
0x08048608 <+40>: mov %ebx,0x18(%esp)
分かるように、esp+0 x 18はクラスxuzhina_を格納しているdump_c6_s3_ex(shell c++filt_ZN 21 xuzhina_dump_c 6_s 3_exC 2 Ev)オブジェクトのthisポインタ.
では、
0x0804860c <+44>: mov 0x18(%esp),%eax
0x08048610 <+48>: mov (%eax),%eax
0x08048612 <+50>: mov (%eax),%eax
このポインタから虚関数を取り出す操作です.クラス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などの比較ツールで修正箇所を見つけることができます.
もちろん、どれだけの虚関数が追加されたかを見る方法もあります.
以上の解析から、0 x 08049958はクラス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のアドレスは0 x 0804950です.その内容を見てみましょう
(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で見る.
[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には2つの虚関数parseValとencodeがあり、parseValはencodeの前にあります.すなわち,encodeの前に虚関数が1つ追加されただけである.複数追加すれば、ここからの順番で数えられます.