ddb(3)
『4次元時計は狂わない』(文藝春秋)なんて本を読んだ。立花隆さんが雑誌に連載されて いたコラムをまとめたもの。1コラムが数ページなので気楽に読める。長編小説もいいんだ けど、探偵物とか警察物は面白くて、ついつい熱中しちゃって、翌日は目が痛いという 情けない状態になってしまう。どこから読んで終わってもいいと言うのは、オイラーみたい なのには、うってつけと思う次第。
で、件の本には、色々な分野、LNGガスが凄いとか、ベトナム戦争の話だとか沖縄訪問だとか 分野にこだわらず肩がこなないエッセイが盛りだくさん。
時計の話が紛れ込んでいた。超精密な時計。宇宙が出来てからその時計を動かしたとしても、 1秒も狂わないとか。
原子時計 の一種。正式名称は、光格子時計と言うらしいけど、開発者の名前を取って、香取時計とも 言うそうな。
アインシュタインの理論が実感出来る世界か。ああ、GPSで既にお世話になってたな。
埼玉に住んでいる時、小金井にある日本時間製造所を訪ねた事が有った。そこで地味に 展示されてた原子時計を見た。
時計を置く位置が変わると時計のずれが変わるんですよと言われて、そんな馬鹿なと思った。 そんな高精度の時計ったって、そのずれはどうやって測定してんの? なにかペテンにかけられているみたいだな。
今もって、精度確認の方法が理解出来ないぞ。2台なり3台なりの時計を相互比較して 精度を決定するって言われてもなあ。ひょっとして、確率の世界の事だろうか。
理論によって、精度は10e-18です。その理論によって時計を10台作りました。同時に 動かしたら、中には遅れる時計や進んでしまう時計がありました。この進とか遅れるってのは、 2つの時計のdiffだな。
工業製品なら、ばらつきは当たり前。それらを平均したら、何処かに真値が有るでしょう。 多分、そういう事なんだろうね。
それから、もう一つ疑問。何十THzと言う光の周波数ってどうやって測定するんだろう? 単純なカウンターでは実現出来ないし。そんなの、基準波と混合して、差分を測れば いいじゃん。無線屋が考えると、クリスタルコンバーターだな。その安定な基準波が 無いんだって。さあ、困ったら、ぐぐれ。
違った世界が見えてきた感じ。でも、この測定器も基準レーザーなんて言葉が出て来る んで、やはり無理筋だな。未踏の技術って面白そう。
光測定を音測定に変えてぐぐったら、
こんなのが出てきた。こういうのも楽しそう。
panic
前回panicに触って、最後にコードも見た。panicって、OSがやばいと思った時、事故保護の ために自己保護するためある、ヒューズとかブレーカーみたいなもの。当然、OSの あちこちに埋め込まれているはず。一体幾つぐらいあるの? オイラーのあてずっぽうな 予想では、200ぐらい。競馬の予想屋さんは、どう思う?
[ob: sys]$ find . -name '*.c' | xargs grep panic | wc 1914 10711 124914
archに下には、amd64しか入れていない。devはOpenBSD6.0がサポートしてるもの全てを 含めて数えたわけだけど、予想が大幅に外れて、しかも桁が違う間違いをしてて恥ずかしいぞ。
お詫びに、全てのpanicを探訪する、panic巡礼の旅でも企画しますかね。まてまて、それじゃ オイラーの寿命が尽きちゃいそうなので、ボツですかね。ちなみに、kern/の中だけでも、 332箇所も地雷が埋め込まれていたぞ。
所で、panicのコードで大事な所を見落としていた。panicした後、勝手にdumpしてから rebootした。これどうなってるのって事。見直したら、しっかりコードされてた。
void panic(const char *fmt, ...) { : bootopt = RB_AUTOBOOT | RB_DUMP; : reboot(bootopt); /* NOTREACHED */ }
ダンプと再起動のフラグを設定して、rebootを呼んでる(と言うかジャンプ)。 だから、ここには戻ってこないと注意書きが表示されてた。
で、rebootの先を追って行くと、最後はbootに集約されてる。bootは何処に在るか? 一発必中させる方法が有るんだ。
厳しい親分のコード書き作法により、関数定義は、型を書き、次の行の冒頭から 関数名、そして引数リストを書く習わし。(関数名とかっこの間は詰める)ならば、
[ob: sys]$ find . -name '*.c' | xargs egrep '^boot\(' ./arch/amd64/amd64/machdep.c:boot(int howto) ./stand/boot/boot.c:boot(dev_t bootdev)
久しぶりに正規表現を使ってみた。amd64の方が今回の本命。standの方は、boot(8)で 説明されてるやつだ。
本命を追いかけてみる。
if ((howto & RB_DUMP) != 0) dumpsys(); : if ((howto & RB_HALT) != 0) { #if NACPI > 0 && !defined(SMALL_KERNEL) extern int acpi_enabled; if (acpi_enabled) { delay(500000); if ((howto & RB_POWERDOWN) != 0) acpi_powerdown(); } #endif printf("\n"); printf("The operating system has halted.\n"); printf("Please press any key to reboot.\n\n"); cnpollc(1); /* for proper keyboard command handling */ cngetc(); cnpollc(0); } printf("rebooting...\n"); if (cpureset_delay > 0) delay(cpureset_delay * 1000); cpu_reset(); for (;;) ; /* NOTREACHED */
shutdown -h now した時に見るメッセージはここに有ったのね。関連でユーザーランドの shutdownを見たら、面白い事やってた。dumpsys() の中で、grr.ってコメントが有った けど、これって、奴らの吠え声の事。頭を掻きむしっているのかな。ダンプの途中で、 ユーザーがそれを中断って、何か恐い感じがするな。
ddbのアドレス解決
ddbを使って、BPを張る時、
ddb> break sys_settimeofday
とかやる。ddbは、そんな人間様が決めた読みやすい名前なんて知ったこっちゃない。 ddb内部で、名前からRAMのアドレスを見つけ出しているはず。これ、どうやっているん だろう? gdb使って、動きを追ってみた。
(gdb) bt 10 #0 db_symbol_values (sym=0xffffffff81c88c20 "O#\002", namep=0xffff8000055ac498\ , valuep=0xffff8000055ac538) at ../../../../ddb/db_elf.c:372 #1 0xffffffff811f1dc3 in db_value_of_name (name=0xffffffff81d32e9f "sys_settim\ eofday", valuep=0xffff8000055ac538) at ../../../../ddb/db_elf.c:456 #2 0xffffffff811f278e in db_term (valuep=0xffff8000055ac538) at ../../../../dd\ b/db_expr.c:57 #3 0xffffffff811f297a in db_unary (valuep=0xffff8000055ac538) at ../../../../d\ db/db_expr.c:128 #4 0xffffffff811f29a4 in db_mult_expr (valuep=0xffff8000055ac578) at ../../../\ ../ddb/db_expr.c:137 #5 0xffffffff811f2b09 in db_add_expr (valuep=0xffff8000055ac5b8) at ../../../.\ ./ddb/db_expr.c:173 #6 0xffffffff811f2bcd in db_shift_expr (valuep=0xffff8000055ac640) at ../../..\ /../ddb/db_expr.c:199 #7 0xffffffff811f2ca8 in db_expression (valuep=0xffff8000055ac640) at ../../..\ /../ddb/db_expr.c:228 #8 0xffffffff811ef833 in db_command (last_cmdp=0xffffffff81b72da0 <db_last_com\ mand>, cmd_table=0x0) at ../../../../ddb/db_command.c:268 #9 0xffffffff811eff35 in db_command_loop () at ../../../../ddb/db_command.c:690
ddbも一応アドレス演算(例: sys_settimeofday+0x12)が出来るようなので、式を評価する ルーチンを呼んでいる。そして、式の中の項(term)に分解する。そいつが名前なら、 db_value_of_name関数を呼んで、アドレス値を得るって事だな。
この関数は、db_elf.cというファイルに収納されてる。カーネルも一応elf形式なファイル なんで、elfの内部に蓄えられている テーブルを見ているんだろうな。 readelfすると出て来る
Symbol table '.symtab' contains 33371 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: ffffffff81b660a0 0 NOTYPE LOCAL DEFAULT 3 lapic_ppr 2: ffffffff81b66100 0 NOTYPE LOCAL DEFAULT 3 lapic_isr : 10168: ffffffff81b78ed0 8 OBJECT GLOBAL DEFAULT 3 pppx_devs 10169: ffffffff8123af60 293 FUNC GLOBAL DEFAULT 1 sys_settimeofday 10170: ffffffff81118037 163 FUNC GLOBAL DEFAULT 1 rtw_disable :
から、探し出しているんだな。ユーザーランドにある、nm なんてコマンドを使う訳には いかないからね。
BPの正体
こうして、得られたアドレスを引数にして、db_breakpoint_cmd経由でdb_set_breakpointが 呼ばれる。ここで、BPの管理に必要な構造体を作っている。
(gdb) p/x *bkpt $31 = { address = 0xffffffff8123af60, init_count = 0x1, count = 0x1, flags = 0x0, bkpt_inst = 0x0, link = 0xffffffff81c02aa0 }
BPは当然、複数設定出来るので、リンクリストで管理してるんだな。
続けて、contすると、
db_continue_cmd(db_expr_t addr, int have_addr, db_expr_t count, char *modif) { if (modif[0] == 'c') db_run_mode = STEP_COUNT; else db_run_mode = STEP_CONTINUE; db_inst_count = 0; => db_cmd_loop_done = 1; }
STEP_CONTINUEをセットして、REPLを抜けるフラグを立てて抜けるんだな。そして、db_trap.c の最後の部分に到達。
db_command_loop(); } => db_restart_at_pc(&ddb_regs, watchpt); db_is_active = 0; }
ここで、ddbに飛び込んだ時のregを回復、BPをメモリーに置く操作が行われる。 regsってどんなのが保存されてる?
(gdb) p/x *regs $38 = { tf_rdi = 0xffff80000009c000, tf_rsi = 0xffff8000055aca2c, tf_rdx = 0xf420, tf_rcx = 0x1b, tf_r8 = 0x0, tf_r9 = 0x0, tf_r10 = 0xffff8000055accb0, tf_r11 = 0x8, tf_r12 = 0x9, tf_r13 = 0x4, tf_r14 = 0xffff80000005d300, tf_r15 = 0x0, tf_rbp = 0xffff8000055ac968, tf_rbx = 0xffff80000005d380, tf_rax = 0x1, tf_gs = 0x286, tf_fs = 0xffffffff81b89e40, tf_es = 0x900000000, tf_ds = 0x282, tf_trapno = 0x1, tf_err = 0x0, tf_rip = 0xffffffff81498fed, tf_cs = 0x8, tf_rflags = 0x282, tf_rsp = 0xffff8000055ac958, tf_ss = 0x10 }
そこはかとなく、i386の影を引きずっている気がするぞ。まあ、そんな事はどうでもいい。 流れを追うと、
if (db_run_mode == STEP_CONTINUE) { if (watchpt || db_find_breakpoint(pc)) { /* * Step over breakpoint/watchpoint. */ db_run_mode = STEP_INVISIBLE; db_set_single_step(regs); } else { => db_set_breakpoints(); db_set_watchpoints(); }
watchptがセットされてると激遅になりそう。今の場合は、そうではなくて、本当に BPとかWPをハードウェアに設定するんだな。
db_set_breakpoints(void) { db_breakpoint_t bkpt; => if (!db_breakpoints_inserted) { for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { bkpt->bkpt_inst = db_get_value(bkpt->address, BKPT_SIZE, 0); db_put_value(bkpt->address, BKPT_SIZE, BKPT_SET(bkpt->bkpt_inst)); } db_breakpoints_inserted = 1; } }
BPの構造体を辿りながら、今有る命令を退避し、空き地に例のCCを置くんだな。 db_put_valueの最後の部分で、BPを書き込んでいる。
=> db_write_bytes(addr, size, data); }
アーキテクチャによっては、BP命令が複数バイトだったりエンディアンを考えなければ いけないので、ルーチンの前半部分は、それに充てられている。amd64の場合は、
(gdb) p/x addr $39 = 0xffffffff8123af60 (gdb) p/x data $40 = {0xcc, 0xc6, 0x5a, 0x5, 0x0, 0x80, 0xff, 0xff} (gdb) p size $41 = 1
1バイトで済んでいるね。