差分表示
- 最後の更新で追加された行はこのように表示します。
- 最後の更新で削除された行は
このように表示します。
ログを取っている環境が i386だったり x86_64だったりするので、stackframeの addressの出方もまちまちだったりするが、
その辺は爽やかにスルーする方向でお願いしますw
* 動機
Solaris10上のコマンド[[DTrace]]にある機能 ustack() みたいなことを Linux上でもやってみたいということで、なんとかならんか調べてみた。
具体的にはこんな感じ。
---(
lynx@root[119] dtrace -n 'syscall::read:entry { ustack(); exit(0)}' -c 'ls /'
dtrace: description 'syscall::read:entry ' matched 1 probe
TT_DB devices lib opt tmp
archives etc lost+found platform usr
bin export mnt proc var
boot home mnt2 sbin vol
dev kernel net system zfs
dtrace: pid 18116 has exited
CPU ID FUNCTION:NAME
3 45642 read:entry
libc.so.1`_read+0xa
libproc.so.1`Preadauxvec+0xd7
libproc.so.1`Pupdate_maps+0x47
libproc.so.1`Pupdate_syms+0x1f
libdtrace.so.1`dt_proc_rdevent+0xe4
libdtrace.so.1`dt_proc_bpmatch+0xc6
libdtrace.so.1`dt_proc_control+0x4a9
libc.so.1`_thr_setup+0x5b
libc.so.1`_lwp_start
---)
* First Step
まずは呼ばれたら、stack frameを順に追って、自分の手前までの分をdumpする Library関数 int mydump() を作るところから始めてみる。glibc 2.1以降で実装されている[[backtrace>http://www.linux.or.jp/JM/html/LDP_man-pages/man3/backtrace.3.html]]がそのまま使えるか試してみた。
まず素で使ってみる
---(
$ cat test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <execinfo.h>
void foo() {
void *trace[BUFSIZ];
size_t size = backtrace(trace, sizeof(trace) / sizeof(trace[0]));
char **strings = backtrace_symbols(trace, size);
for ( int i = 0; i < size; i++ )
printf("%s\n", strings[i]);
free(strings);
}
int main() {
foo();
return 0;
}
$ ./test
./test(foo+0x1c) [0x400834]
./test(main+0xe) [0x4008c5]
/lib64/tls/libc.so.6(__libc_start_main+0xdb) [0x34be11c4bb]
./test [0x40078a]
---)
backtrace()は表示用のバッファサイズ(trace)とnestの深さ(n)の分だけ表示できる
* 2nd Step
毎回コードに直書きするのはどうかと思うので、foo()の部分だけ libraryに入れて、
コードからはその関数を呼び出す形に変えてみた。ついでにもう1枚、libを挟んでみた。
まずはsoに入れる部分
---(
$ cat tracehere.c
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
int tracehere() {
void *trace[BUFSIZ];
size_t size;
char **strings;
size_t i;
size = backtrace(trace, sizeof(trace));
strings = backtrace_symbols(trace, size);
printf("Obtained %zd stack frames.\n", size);
for ( i = 0; i < size; i++ ) {
printf("%s\n", strings[i]);
}
free(strings);// the array 'strings' is malloced by backtrace_symbols().
return 0;
}
---)
次に上記のsoに入れた関数を呼び出す方と実行結果。
---(
$ cat mock.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int tracehere();
void foo() {
tracehere();
}
$ cat backtrace.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int foo();
int main(int argc, char *argv[]) {
foo();
return (0);
}
$ make run
export LD_LIBRARY_PATH=.; ./backtrace
Obtained 5 stack frames.
./libmito.so(tracehere+0x1c) [0x2a956827d8]
./libmock.so(foo+0xe) [0x2a955576ba]
./backtrace(main+0x19) [0x400721]
/lib64/tls/libc.so.6(__libc_start_main+0xdb) [0x34be11c4bb]
./backtrace [0x40067a]
---)
%%backtrace()を呼び出したバイナリに含まれてる所(=libmito.so)はちゃんとsymbol名が表示されているが、本体(たぶん libmito.so以外?)はsymbol名が解決されていない。%% 単にstaticの関数にしてただけだった orz
[[上記のman page>http://www.linux.or.jp/JM/html/LDP_man-pages/man3/backtrace.3.html]]には
---(
シンボル名は特別なリンカ・オプションを使用しないと利用できない場合がある。
GNU リンカを使用するシステムでは、 -rdynamic リンカ・オプションを使う必要がある。
"static" な関数のシンボル名は公開されず、バックトレースでは利用できない点に注意すること。
---)
と書いてあるの%%で、-rdynamicを GCCに渡して buildしてみたが、有無に関わらず出力されなかった。%%通りでしたw
* 3rd Step
backtrace()は glibc(2.1以上)に依存するので、他OSへの portingも考えると [[http://sourceware.org/binutils/docs/bfd/index.html>libbfd]]とかを使って頑張ってみる。とりあえず objdump --syms optionでSTDOUTに出力してるのは、関数ポインタabfd->xvec->_bfd_print_symbolのさしてる先で、elf 32bitバイナリの場合、elf.cにある bfd_elf_print_symbol()でやってる。
TBD
* 4th Step
ここまでは tracehere()をチェック箇所に埋めこんで rebuildする必要があったが、live debugしたい対象を
rebuildするのはむずかしい。なので、以下のような感じで動くようにしてみる。
-1. 対象symbolの処理に入った所で ustack()を出力する
-- 対象の設定は固定位置($HOME/.xxx/xxx.confみたいな?)のファイルに書く。
-2. 対象binaryのrebuildは無し。とりあえず ELF限定でいいだろう。
-3. .debug section無しで動作する。stripされてても動く。
作り方はこんな感じで想定中。
- LD_PRELOADで対象 libを指定しておく
- 設定ファイルに書いてある対象のsymbolを lib内で動的に生成して、後からloadされるものを wrapする
- backtrace箇所は glibc版、libbfd版、readelf同等品(=自力構成)をつくってみる
TBD
* 参考文献
-[[bkブログ:binaryアーカイブ>http://0xcc.net/blog/archives/cat_21.html]]
--[[Binary Hack>http://0xcc.net/binhacks/]]著者のうちのお一人のblog。ここに書いてあることがそのまま本になってる。
-[[GNU Binutils>http://sources.redhat.com/binutils/]]
--libbfd
-[[SystemTap>http://sourceware.org/systemtap/]]
--爽やかに Linuxには Portingされてるっぽい。ま、Kernel差し替えとか導入面倒とか、ustack()とりたいけど前提条件を満たせない環境も多そう。
-[[フリーソフトウェア 徹底活用講座>http://www.cqpub.co.jp/interface/column/freesoft/default.htm]]
--[[Interface>http://www.cqpub.co.jp/interface/]]の連載に見合うだけの内容のGCC等の秀逸なOSS解説記事。
-[[Google Code Search/glibc>http://www.google.com/codesearch/p?hl=ja#5ge3gHPB4K4/gnu/glibc/glibc-2.5.tar.bz2|glibc-2.5/debug/backtrace.c&q=backtrace_symbol%20lang:c%20__builtin_frame_address]]
--[[本家>http://sourceware.org/cgi-bin/cvsweb.cgi/libc/debug/?cvsroot=glibc]]もいいが cvsviewなので、こっちのほうが使いやすい。
** 参考文献からのメモ
-1. 環境変数 LD_PRELOAD に /lib/libSegFault.so を指定するだけで異常終了時にバックトレースを表示できる。/usr/bin/catchsegvというwrapper scriptもある。
---(
$ catchsegv --help
Usage: catchsegv PROGRAM ARGS...
--help print this help, then exit
--version print version number, then exit
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.
---)
ということで、試してみた。テストコードと実行結果はこんな感じ。
---(
$ cat segvtest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int foo() {
char *a = NULL;
printf("%s\n", a);
return 0;
}
int main(int argc, char *argv[]) {
foo();
return (0);
}
$ ./segvtest
Segmentation fault
---)
/lib/libSegFault.soをLD_PRELOADに刺して実行した結果はこんな感じ。wrapper scriptの catchsegvから実行した結果も同じだった。(そりゃそうだ。)
---(
$ setenv LD_PRELOAD /lib/libSegFault.so
$ ./segvtest
*** Segmentation fault
Register dump:
EAX: 00000000 EBX: 00254ff4 ECX: 00000000 EDX: 00000001
ESI: 00377ca0 EDI: 00000000 EBP: bfb9fbe8 ESP: bfb9fbc8
EIP: 001700d3 EFLAGS: 00210246
CS: 0073 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000004 OldMask: 00000000
ESP/signal: bfb9fbc8 CR2: 00000000
Backtrace:
/lib/i686/nosegneg/libc.so.6(strlen+0x33)[0x1700d3]
/lib/i686/nosegneg/libc.so.6(_IO_puts+0x25)[0x15a405]
./segvtest(foo+0x18)[0x804855c]
./segvtest(main+0x16)[0x8048579]
/lib/i686/nosegneg/libc.so.6(__libc_start_main+0xe0)[0x116f70]
./segvtest[0x8048491]
Memory map:
00101000-00253000 r-xp 00000000 fd:00 4124746 /lib/i686/nosegneg/libc-2.6.so
00253000-00255000 r--p 00151000 fd:00 4124746 /lib/i686/nosegneg/libc-2.6.so
00255000-00256000 rw-p 00153000 fd:00 4124746 /lib/i686/nosegneg/libc-2.6.so
00256000-00259000 rw-p 00256000 00:00 0
00348000-00353000 r-xp 00000000 fd:00 4124787 /lib/libgcc_s-4.1.2-20070503.so.1
00353000-00354000 rw-p 0000a000 fd:00 4124787 /lib/libgcc_s-4.1.2-20070503.so.1
0035c000-00377000 r-xp 00000000 fd:00 4124744 /lib/ld-2.6.so
00377000-00378000 r--p 0001a000 fd:00 4124744 /lib/ld-2.6.so
00378000-00379000 rw-p 0001b000 fd:00 4124744 /lib/ld-2.6.so
0077c000-0077d000 r-xp 0077c000 00:00 0 [vdso]
00918000-0091b000 r-xp 00000000 fd:00 4124753 /lib/libSegFault.so
0091b000-0091c000 r--p 00002000 fd:00 4124753 /lib/libSegFault.so
0091c000-0091d000 rw-p 00003000 fd:00 4124753 /lib/libSegFault.so
08048000-08049000 r-xp 00000000 fd:00 9466779 /home/mage/sym-works/backtrace/tests/segvtest/segvtest
08049000-0804a000 rw-p 00000000 fd:00 9466779 /home/mage/sym-works/backtrace/tests/segvtest/segvtest
088df000-08900000 rw-p 088df000 00:00 0
b7f4f000-b7f50000 rw-p b7f4f000 00:00 0
b7f6b000-b7f6c000 rw-p b7f6b000 00:00 0
bfb8c000-bfba1000 rw-p bfb8c000 00:00 0 [stack]
Segmentation fault
---)
* 残課題
-1. binutilを --enable-sharedでbuildすると libbfd-2.1.x.soとsymlinkの libbfd.soができる。使う側のbuild時に -lbfdだと未解決シンボルが残るが、-lbfd-2.1.xだと残らない。なんでだろ。
Last-modified: 2009-01-27 13:39:20