ddb(2)
今月はスーパームーンだったらしい。
そのスーパームーンって何? 例の『月に変わって、お仕置きよ』なのか?
11月14日に「68年ぶり」にやってくるウルトラスーパームーンの影響。
やっぱりなあ。
満月の夜は、殺人が増えるとか、色々言われているけど、メガ級の満月だと、色々想像を 逞しくして、民衆を煽る訳ね。
そういう、生臭い事はもういいよ。風流に行きましょう。
月 々 に 月 見 る 月 は 多 け れ ど 月 見 る 月 は こ の 月 の 月
有名な句である。これを知らなきゃ、日本人じゃないぞ。
この句になんとなく、再帰を感じるのは、オイラーだけ?
ふと、これを発句として、DebggerをDebuggerでDebugしたら、どうなる? なんて連想した。
こういうのをはっく、hackと言うのでしょうか?
我がアースと言うか地上と言うか電脳空間は、相変わらず生臭いですなあ。
goとCの勉強にうってつけ、です。
バランスも大事っつう事で。俗な世界も知っておく。使うかどうかは、別問題だけど。
ddbをgdbでdebugする
この場合のdebugは、例によって観察って意味です。
OpenBSD上に入れた、qemuでOpenBSDをゲストとして動かしている。そのゲストのddbを ホスト側からgdbで観察してみる。
環境は、前々回あたりに作ってある。そして、前回、ddbを呼び出すのはDebgger()を 起動する事を調査すみ。
qemuのdebug支援モード -s を付けて、ゲストを起動。そして、普通にroginしとく。 ホスト側で、emacs上からegdb bsd.gdb で、ソースを見られる状態にする。 そして、
(gdb) target remote :1234 Remote debugging using :1234 acpicpu_idle () at ../../../../dev/acpi/acpicpu.c:1170 1170 break; (gdb) b Debugger Breakpoint 1 at 0xffffffff81498fdd: file ../../../../arch/amd64/amd64/db_interf\ ace.c, line 399. (gdb) c Continuing.
ゲスト側から、ddbを起動
# sysctl ddb.trigger=1
ここから、追跡開始です。Debugger()関数ってのは、
void Debugger(void) { B => breakpoint(); }
ここに来るまでの、道筋。
(gdb) bt #0 Debugger () at ../../../../arch/amd64/amd64/db_interface.c:399 #1 0xffffffff811f5b3c in ddb_sysctl (name=0xffff800007273d64, namelen=1, oldp=\ 0x7f7ffffcbb20, oldlenp=0xffff800007273d98, newp=0x7f7ffffcbb14, newlen=4, p=0x\ ffff800007262698) at ../../../../ddb/db_usrreq.c:98 #2 0xffffffff812316f3 in sys_sysctl (p=0xffff800007262698, v=0xffff800007273e6\ 0, retval=0xffff800007273eb0) at ../../../../kern/kern_sysctl.c:236 #3 0xffffffff81479ef2 in mi_syscall (p=0xffff800007262698, code=202, callp=0xf\ fffffff81b74940 <sysent+3232>, argp=0xffff800007273e60, retval=0xffff800007273e\ b0) at ../../../../sys/syscall_mi.h:77 #4 0xffffffff81479d0b in syscall (frame=0xffff800007273f20) at ../../../../arc\ h/amd64/amd64/trap.c:568 #5 0xffffffff810017fb in Xsyscall ()
reakpoint()の正体は、cpufunc.hに定義されてた。
/* Break into DDB/KGDB. */ static __inline void breakpoint(void) { => __asm volatile("int $3"); }
そして、すこしづつ動きを追って行くと、dev/wsconsの中をさ迷った挙句に、
#0 db_ktrap (type=1, code=0, regs=0xffff800007273bb0) at ../../../../arch/amd6\ 4/amd64/db_interface.c:151 #1 0xffffffff81479252 in trap (frame=0xffff800007273bb0) at ../../../../arch/a\ md64/amd64/trap.c:198 #2 0xffffffff8147c032 in calltrap () :
ddbのrepl
以上が、ddbへの突入だった。これは、ddbをカーネル内のひとつのアプリとしてみた場合の 起動順と言う事になる。
では、今度はアプリ側の視点に立ってみる。ddbのそれぞれのコマンドを受け付けて、 それを実行。終了したら、次のddbコマンドを受け付ける。そう、Lispとかで言う、 REPLの構造を成している。
じゃ、そのコマンドループは何処にある? 上記を更に辿っていくと、
(gdb) bt 5 #0 db_trap (type=1, code=0) at ../../../../ddb/db_trap.c:93 #1 0xffffffff81498f7a in db_ktrap (type=1, code=0, regs=0xffff800007273bb0) at\ ../../../../arch/amd64/amd64/db_interface.c:150 #2 0xffffffff81479252 in trap (frame=0xffff800007273bb0) at ../../../../arch/a\ md64/amd64/trap.c:198 #3 0xffffffff8147c032 in calltrap () #4 0xffffffff81498fed in breakpoint () at ./machine/cpufunc.h:326 (More stack frames follow...)
if (! ddb_msg_shown) { db_printf( "http://www.openbsd.org/ddb.html describes the minimum info required in bug\n" "reports. Insufficient info makes it difficult to find and fix bugs.\n"); ddb_msg_shown = 1; } } => db_command_loop(); } db_restart_at_pc(&ddb_regs, watchpt); db_is_active = 0; }
パニクッた時の報告の仕方が広告されてた。コピペ出来ないVGAな端末なら、しょうがない カメラで写真を撮って送ってくれ。本当は、シリアル・コンソールでログを送ってくれると 嬉しいんだけど。親分、優しいな。Bug潰しの役に立つなら、多少の事は目をつぶるとな。
最後の方に書いてある、アセンブルした時のアドレスをC語の行番号に変換する例は、 非常に為になるな。覚えておこう。
で、db_command_loop()なんて言う、もろな名前の関数が有るじゃないですか。ちょっと 突入してみる。
=> while (!db_cmd_loop_done) { if (db_print_position() != 0) db_printf("\n"); db_output_line = 0; #ifdef MULTIPROCESSOR db_printf("ddb{%d}> ", CPU_INFO_UNIT(curcpu())); #else db_printf("ddb> "); #endif (void) db_read_line(); db_command(&db_last_command, db_command_table); } db_recover = savejmp; }
何か問題が発生しても困らないように、保護した上で、コマンドを入力して貰い、 直前に実行したコマンド名と共に、コマンドテーブルを指定して、実行の解析を 行うんだな。
直前のコマンドが必要になるのは、breakみたいに、何回もそれを実行した挙句に breakするなんてのが有るからか。 db_commandの第一引数の例。
(gdb) p **last_cmdp $3 = { name = 0xffffffff819bbf18 "trace", fcn = 0xffffffff811f0396 <db_stack_trace_cmd>, flag = 0, more = 0x0 }
PRの為の非常訓練
上のクラッシュダンプを取ってPR(problem reports)する手順が非常に有益と思われる ので、まさかの為に非常訓練をやっておく。
近頃は、総理大臣も泊原発の非常訓練に参加して、意義を訴えるぐらいですから、民間人も やっておくべき。
手順書には、
# DEBUG=-g make pf.o # objdump --line --disassemble --reloc pf.o >pf.dis # grep "<_pf_route>:" pf.dis # (calc first hex address + offset, that use gdb calc) # more pf.dis # cat -n pf.c | head -n 3872 | tail -n 1
想定は、_pf_route+0x263と表示された時に、該当するC語ファイルの対象行を見つける もの。
自分なりに理解すると、_pf_routeから、pf.oを割り出す(これが一番、難儀だろうな)。 続いて、debugフラグ付きにコンパイル。そして出来たオブジェクトファイルをobjdumpを 使って、逆アセンブル。これ長いオプションを使ってるけど、そんな悠長な事を やっていられないだろう。
# obudump -l -d -r pf.o >pf.dis
で、十分。そして、逆アセンブルリストから、対象関数の先頭を探す。それに、オフセットを 加える。16進数同士の足し算だから、dcを使うなり、gdbを使うなりして求める。
[ob: ~]$ egdb -q (gdb) p/x 0x1234+0x2 $1 = 0x1236
gdbの16進数計算器は、上記のように使うんだった。
求まったアドレスを元に対象行を探す。そこから前に遡っていくと、Cファイルの 対象行が表示されている。後は、viで開くなりemacsで開くなり、上記の様にcat/head/tail の合わせ技でOK。
以前ddbに飛び込んだ時に取ったログから、どこで発生したか突き止めてみる。
Stopped at Debugger+0x9: leave ddb> trace Debugger() at Debugger+0x9 init_x86_64() at init_x86_64+0x740
[ob: sys]$ find . -name '*.[ch]' | xargs fgrep Debugger : ./arch/amd64/amd64/cpu.c: Debugger(); ./arch/amd64/amd64/cpu.c: Debugger(); ./arch/amd64/amd64/db_interface.c: Debugger(); ./arch/amd64/amd64/db_interface.c:Debugger(void)
色々な所から呼び出されていて雑音が入ってしまっているけど、冒頭からDebuggerってのが 出てきている、voidが入っているって言うのが、決め手になって、定義されている ファイルは、./arch/amd64/amd64/db_interface.c と決定。
# cd /sys/arch/amd64/compile/EXPL/ # rm db_interface.o # DEBUG=-g make db_interface.o # objdump -l -d -r db_interface.o > zz.dis # grep 'Debugger>:' zz.dis 0000000000000000 <Debugger>: # more zz.dis 0000000000000000 <Debugger>: Debugger(): ../../../../arch/amd64/amd64/db_interface.c:398 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp breakpoint(): ./machine/cpufunc.h:326 8: cc int3 Debugger(): ../../../../arch/amd64/amd64/db_interface.c:400 9: c9 leaveq a: c3 retq
定義の始まりアドレスがZEROだったので、計算器の必要は無かった。確かに、 Debugger+0x9 に、leaveqが居るな。Cファイルの該当行番号も表示されている。
[ob: amd64]$ cat -n db_interface.c : 396 void 397 Debugger(void) 398 { 399 breakpoint(); 400 }
以上、非常訓練終了。
debuggerへ飛び込むには、マシン語で1バイトで済む。これなら、どんな狭い所にも 潜り込ませられるな。実態は、ソフトウェア割り込みを起こす命令か。
インテルな石用のアプリを壊すのは簡単。適当にccをばら撒けばいいんだ。 美味しい、CCレモンをどうぞってね。(オイオイ)
なお、上の訓練で、objdumpに -lが付いていて、説明を読むと
-l --line-numbers Label the display (using debugging information) with the filename and source line numbers corresponding to the object code or relocs shown. Only useful with -d, -D, or -r.
これが、今回の訓練の肝でした。