2011年02月08日
Androidのdebuggerd
Androidのポーティング作業や、NDKでネイティブライブラリの開発をしている方は、ネイティブのプログラムの部分で不正なメモリアクセスなどで例外が発生したときに、logcatでのログに、その例外を起こしたプロセスのレジスタやスタックがダンプされるのを見ることがあると思います。
これは一体どのような仕組みでこのダンプがログに出ているのでしょうか?
ダンプの例
以前書いたページでSIGBUS(バスエラー)の例外が発生した時のログです。
I/DEBUG ( 543): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 543): Build fingerprint: 'generic/generic/generic/:Donut/Donut/eng.koba.20090827.140821:eng/test-keys' I/DEBUG ( 543): pid: 590, tid: 590 >>> zygote <<< I/DEBUG ( 543): signal 7 (SIGBUS), fault addr 00000000 I/DEBUG ( 543): r0 00001412 r1 4104bbe4 r2 4104bbdc r3 4104bbe4 I/DEBUG ( 543): r4 41748086 r5 4104bb94 r6 bef614e8 r7 ad00e640 I/DEBUG ( 543): r8 000012ad r9 4104bbdc 10 4104bb84 fp 00000000 I/DEBUG ( 543): ip 000000ad sp bef614a8 lr ad01071c pc ad01119c cpsr 20000010 I/DEBUG ( 543): #00 pc 0001119c /system/lib/libdvm.so I/DEBUG ( 543): #01 pc 00017d90 /system/lib/libdvm.so I/DEBUG ( 543): #02 pc 000177d4 /system/lib/libdvm.so I/DEBUG ( 543): #03 pc 00052974 /system/lib/libdvm.so I/DEBUG ( 543): #04 pc 00052992 /system/lib/libdvm.so
debuggerd
このログを実際に出しているのはdebuggerdというプロセスです。
system/core/debuggerd
このプロセスは起動時にinitから起動され、"android:debuggerd"というUNIX domain socketを生成して接続されるのを待っています。
接続されると、ソケットにtid(gettidシステムコールで得られるスレッドID)が書き込まれることになっているので、そのtidに対して、ptraceシステムコールを使ってアタッチします。
つまり、例外を起こして瀕死の状態にあるプロセスを停止状態にします。さらに、そのプロセスのレジスタやメモリをptraceシステムコールを駆使して読み出してログにダンプします。
system/core/debuggerd/debuggerd.c
このソースを見るとptraceシステムコールの使い方が勉強できそうです。
ダイナミックリンカ
さて、debuggerdの開いた"android:debuggerd"というsocketに接続するのはどこでどうやっているのでしょうか。
その部分のソースはここにあります。
bionic/linker/debugger.c
void debugger_init() { signal(SIGILL, debugger_signal_handler); signal(SIGABRT, debugger_signal_handler); signal(SIGBUS, debugger_signal_handler); signal(SIGFPE, debugger_signal_handler); signal(SIGSEGV, debugger_signal_handler); signal(SIGSTKFLT, debugger_signal_handler); signal(SIGPIPE, debugger_signal_handler); }
これはbionic/linker/debugger.c の最後の部分です。これらのsignalのハンドラが登録されています。debugger_signal_handlerでdebuggerdの開いた"android:debuggerd"というsocketに接続しています。
このソースは実はbionicのダイナミックリンカの一部です。
ダイナミックリンクしているプログラムはmain関数が実行される前にまずダイナミックリンカが動作してメモリ上にダイナミックリンクライブラリをマッピングするわけですが、その時にこの関数debugger_initを呼んでいます。
これはなかなかうまいやり方だと思います。
つまり、ダイナミックリンクしているプログラムは全て自動的にdebuggerdと接続してデバッグダンプする機能を備えることになります。