開発環境でのバイナリやデバッグバイナリではないもの、すなわち、

障害解析対象のプログラムは-gが付いていない(プロダクション用なら当然)

というようなバイナリからとれたstacktraceを手がかりにsignal 11の位置を特定する方法について書きます。

(以前よりも少しやりかたを改善できたのでメモしておきます。もっと簡単にできる方法があればコメント下さい)

16進数のstacktraceを確認 まずエラーログを見る。mysqldの場合はエラーログにstacktraceを吐いてくれるので別にcoreファイルがなくても大丈夫。吐いてくれないプログラムの場合は"ulimit -c unlimited"でcoreを出すようにしておいてbtする。 こんな感じの16進数のstacktraceが取れる。 0x5c4ceb 0x6267de 0x626e63 ... hoge.logとでもしておく。

バイナリからシンボルを入手 strip済みのバイナリだと取れないので注意。stripしない、あるいは事前に入手しておくことが必要。 GNU binutilsのnmコマンドで採取する。 nm --numeric-sort --demangle=auto mysqld > fuga.sym オプションの"--numeric-sort"はアドレス順にソートして出力してくれるようにするためのもの。

シンボルとstacktraceを突き合わせる MySQLには、resolve_stack_dumpという便利なコマンドが付いています。これはシンボル情報とstacktraceをつき合わせて、stacktraceに書かれているアドレスがどの関数のもので、その関数の先頭アドレスから何バイト目のインストラクションのものなのかを判別してくれるものです。 ./resolve_stack_dump fuga.sym hoge.log このコマンドのやっていることは単純な突き合わせなので、使えない場合には人間の目による手動突き合わせでも代用可能です。(面倒くさいけど) するとこんな感じになります。 0x5c4ceb handle_segfault + 593 0x6267de join_read_next(st_read_record*) + 60 0x626e63 sub_select(JOIN*, st_join_table*, bool) + 499 ... ここではjoin_read_next(st_read_record*)の開始アドレスから60bytesのところでsignal 11が起きたのではということが分かります。

対象の関数をdisassembleする join_read_next関数が書かれたソースファイルを見ても、まだどの行が何バイト目なのかが分からないため、ソースの行の特定ができません。 そこでdisassembleしてまずはどのインストラクションを実行する際に問題が起きたのかを調べます。 disassembleはgdbなどで行えます。この時に使うバイナリは障害が起きたものと同じ-g無しのバイナリです。 (gdb) disass join_read_next Dump of assembler code for function _Z14join_read_nextP14st_read_record: 0x00000000006267a2 <_Z14join_read_nextP14st_read_record+0>: push %rbp 0x00000000006267a3 <_Z14join_read_nextP14st_read_record+1>: mov %rsp,%rbp 0x00000000006267a6 <_Z14join_read_nextP14st_read_record+4>: sub $0x20,%rsp 0x00000000006267aa <_Z14join_read_nextP14st_read_record+8>: mov %rdi,0xffffffffffffffe8(%rbp) 0x00000000006267ae <_Z14join_read_nextP14st_read_record+12>: callq 0x88dbe4 <_my_thread_var> 0x00000000006267b3 <_Z14join_read_nextP14st_read_record+17>: mov 0xa8(%rax),%eax 0x00000000006267b9 <_Z14join_read_nextP14st_read_record+23>: and $0x1,%eax 0x00000000006267bc <_Z14join_read_nextP14st_read_record+26>: xor $0x1,%eax 0x00000000006267bf <_Z14join_read_nextP14st_read_record+29>: test %al,%al 0x00000000006267c1 <_Z14join_read_nextP14st_read_record+31>: jne 0x6267f9 <_Z14join_read_nextP14st_read_record+87> 0x00000000006267c3 <_Z14join_read_nextP14st_read_record+33>: callq 0x88dbe4 <_my_thread_var> 0x00000000006267c8 <_Z14join_read_nextP14st_read_record+38>: mov 0xa8(%rax),%eax 0x00000000006267ce <_Z14join_read_nextP14st_read_record+44>: and $0x1a,%eax 0x00000000006267d1 <_Z14join_read_nextP14st_read_record+47>: cmp $0xa,%eax 0x00000000006267d4 <_Z14join_read_nextP14st_read_record+50>: jne 0x6267f9 <_Z14join_read_nextP14st_read_record+87> 0x00000000006267d6 <_Z14join_read_nextP14st_read_record+52>: mov 0xffffffffffffffe8(%rbp),%rax 0x00000000006267da <_Z14join_read_nextP14st_read_record+56>: mov 0x20(%rax),%rdx 0x00000000006267de <_Z14join_read_nextP14st_read_record+60>: mov 0xbe0(%rdx),%rax 0x00000000006267e5 <_Z14join_read_nextP14st_read_record+67>: add $0x1,%rax 0x00000000006267e9 <_Z14join_read_nextP14st_read_record+71>: mov %rax,0xbe0(%rdx) 0x00000000006267f0 <_Z14join_read_nextP14st_read_record+78>: movl $0x0,0xffffffffffffffe4(%rbp) 0x00000000006267f7 <_Z14join_read_nextP14st_read_record+85>: jmp 0x626843 <_Z14join_read_nextP14st_read_record+161> 0x00000000006267f9 <_Z14join_read_nextP14st_read_record+87>: mov 0xffffffffffffffe8(%rbp),%rax 0x00000000006267fd <_Z14join_read_nextP14st_read_record+91>: mov 0x8(%rax),%rax 0x0000000000626801 <_Z14join_read_nextP14st_read_record+95>: mov (%rax),%rax 0x0000000000626804 <_Z14join_read_nextP14st_read_record+98>: add $0xc0,%rax 0x000000000062680a <_Z14join_read_nextP14st_read_record+104>: mov (%rax),%rdx ... この行で落ちたということになります。 0x00000000006267de <_Z14join_read_nextP14st_read_record+60>: mov 0xbe0(%rdx),%rax

ソースコードをデバッグ情報付きでアセンブラにする これだけだとまだソースコードの行とアセンブラのインストラクションの対応の裏付けができません。 そこでgccでデバッグ情報付きのアセンブラコードを作成します。 make実行時のログなどから、対象のソースファイルのコンパイル実行コマンドを抜き出してきます。join_read_next関数はsql/sql_select.ccというファイルに書かれていまして、以下のようにコンパイルされていました。 gcc -DMYSQL_SERVER -DDEFAULT_MYSQL_HOME="\"/usr/local/mysql\"" -DDATADIR="\"/usr/local/mysql/data\"" -DSHAREDIR="\"/usr/local/mysql/share/mysql\"" -DHAVE_CONFIG_H -I. -I. -I.. -I../zlib -I../innobase/include -I../innobase/include -I../include -I../include -I../regex -I. -I/usr/include/senna -DDBUG_OFF -g -fno-implicit-templates -fno-exceptions -fno-rtti -c -o sql_select.o sql_select.cc ここに、--save-tempsオプションを付与することでgccが作る中間ファイル(プリプロセッサ実行後のファイル、アセンブラソースファイル)が残るようになり、さらに-dAオプションを付与することでアセンブラソースファイルにデバッグ情報が付与されるようになります。 gcc -DMYSQL_SERVER -DDEFAULT_MYSQL_HOME="\"/usr/local/mysql\"" -DDATADIR="\"/usr/local/mysql/data\"" -DSHAREDIR="\"/usr/local/mysql/share/mysql\"" -DHAVE_CONFIG_H -I. -I. -I.. -I../zlib -I../innobase/include -I../innobase/include -I../include -I../include -I../regex -I. -I/usr/include/senna -DDBUG_OFF -g -fno-implicit-templates -fno-exceptions -fno-rtti -c -o sql_select.o sql_select.cc --save-temps -dA これで生成されるsql_select.sというファイルがアセンブラソースファイルです。