ddb(2)

今月はスーパームーンだったらしい。

そのスーパームーンって何? 例の『月に変わって、お仕置きよ』なのか?

質問2-7)「スーパームーン」ってなに?

11月14日に「68年ぶり」にやってくるウルトラスーパームーンの影響。

やっぱりなあ。

満月の夜は、殺人が増えるとか、色々言われているけど、メガ級の満月だと、色々想像を 逞しくして、民衆を煽る訳ね。

そういう、生臭い事はもういいよ。風流に行きましょう。

   月  々  に  月  見  る  月  は  多  け  れ  ど
       月  見  る  月  は  こ  の  月  の  月

有名な句である。これを知らなきゃ、日本人じゃないぞ。

この句になんとなく、再帰を感じるのは、オイラーだけ?

ふと、これを発句として、DebggerをDebuggerでDebugしたら、どうなる? なんて連想した。

こういうのをはっく、hackと言うのでしょうか?

我がアースと言うか地上と言うか電脳空間は、相変わらず生臭いですなあ。

「Mirai」ソースコード徹底解剖-その仕組みと対策を探る

goとCの勉強にうってつけ、です。

Pythonのクラス、最速理解

バランスも大事っつう事で。俗な世界も知っておく。使うかどうかは、別問題だけど。

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.

これが、今回の訓練の肝でした。