差分表示


ログを取っている環境が 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