Debug on FreeBSD

女房が図書館に行くと言うのでお供をした。5冊/2週間とかの貸し出しなのだけどそんなに 読めないので1冊だけ(女房のカードで)借りる事にした。

娯楽だからジャンルは問わず、老眼なので文庫本は敬遠。老人用の超大字の本も、プチ・プライドが 許さないので普通のハードカバーの本。題名に引かれて『eの悲劇』なんてのを借りてきた。

この本の主人公は元相場師で今は訳あってガードマンをやっていると言う設定。舞台はY2Kでコンピュータ業界が 大騒ぎしてた時。コンピュータが無くてはやっていけない銀行業界。もしもに備えてデータを紙に 出して銀行の大金庫にしまう事になった。で、主人公はその立会い。 でも、ふとした事から大金庫に閉じ込められてしまう。完全密閉の空間。開けられなければ 窒息死間違い無し。

金庫は外からパスワードで開けられるようになっているんだけど、パスワードは主人公と共に 金庫の中。どうやってパスワードを外に伝える? そこで登場したのは、扉を叩いて通信する 方法。モールス信号ですな。で、主人公は若かりし頃無線で電信をやってた。思い出しながら SOS SOS。自衛隊あがりの同僚が運よくそのコードを理解出来。。。

でも、パスワードの最後のチェック数字だけを想像するしかなくなって。その月の数字を 元に作成されてることまでは分かったんだが、10~2としても、2~10 としても間違い。 後1回しか試せなくて、ミレニアム紀の最後の月という事で、9~9に賭ける。この人、ひょっとして COBOLERさん?

兎も角、モールス信号が登場する小説なんて初めて読んだよ。面白かった。世の中には モールス関連のシーンが登場する作品 とか 韓国のCMかっこいい。ワイン好きには ワインのラベル が、モールスになってるのでも飲んで酔いましょう。

VMWAREでシリアル接続して debug

FreeBSDでカーネルデバッグをする時って、パソコン同士をシリアルケーブルで結んで、gdbを 使って行うのが普通みたいだ。その為のI/Fがカーネルに組み込まれているようだ。(特殊な 人の趣味ですから、I/Fをenableにして再コンパイルが必要との事)

ここで問題は、VMWAREにどうやってシリアルケーブルを接続するねん? である。ぐぐってみると カーネルデバッグ のがヒットしてきた。

まずはVMWARE Playerでも同様な事が出来るか実験だな。

VMWARE の追加からシリアルポートを選び、名前付きパイプ出力を指定。この端末はサーバー (もう片方はクライアント)、接続先は仮想マシン、起動時に接続を指定。を行ってから VMWAREを起動。

dmesgを見ると

uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0
uart0: [FILTER]
uart1: <16550 or compatible> port 0x2f8-0x2ff irq 3 on acpi0
uart1: [FILTER]

それっぽいのが出てるけど。。まずは、/devの下だな。

[sakae@fb8 ~]$ ls -l /dev/cu*
crw-rw----  1 uucp  dialer    0,  50 Jun 29 14:10 /dev/cuau0
crw-rw----  1 uucp  dialer    0,  51 Jun 29 13:36 /dev/cuau0.init
crw-rw----  1 uucp  dialer    0,  52 Jun 29 13:36 /dev/cuau0.lock
crw-rw----  1 uucp  dialer    0,  56 Jun 29 13:36 /dev/cuau1
crw-rw----  1 uucp  dialer    0,  57 Jun 29 13:36 /dev/cuau1.init
crw-rw----  1 uucp  dialer    0,  58 Jun 29 13:36 /dev/cuau1.lock

ひょっとして、cuau0とcuau1が新しく生えたデバイスかなあ。先ほどVVWAREに登録した時、 com1なんて言ってたから、きっとFreeBSD上では、cuau0だろう。取り合えず、パーミションを 変更して使うけど、恒久的には、おいらがdialerのグループに加入しとけばいいんだな。 忘れずに、target側とremote側も変更しておこう。

そうそう、VMWAREの設定のとき、この端末はサーバー/クライアントの設定があって、remote側を サーバーに割り当てたけど、これはどうするのが正解なのだろう? シリアルなんて、どっちも どっちだと思うんだけど。。

いきなりkernelじゃハードルが高そうなので、前回やったマシン語実験のアプリを リモートでバックしてみよう。未知のものは段階を踏まないとね。しくじった時、わけわかめ になるのが落ちだもの。

こちらのtarget側では、取り合えずの実験つう事でgdbserver経由でアプリを起動しました。 gdb信号は、/dev/cuau0に流れてねって設定です。

----- target ------------------------------------
[sakae@cdr ~/hand]$ gdbserver /dev/cuau0 foo
Prodess foo created; pic = 4995
Remote debugging using /dev/cuau0
Hellow world!

Child exited with retcode = 0

Child exited with status 0
GDBserver exiting
--------------------------------------------------

remote側は、gdbを操ります。runじゃなくて、contで動かし始めるのがお約束。

----- remote -------------------------------------
[sakae@fb8 ~]$ gdb -q
(gdb) target remote /dev/cuau0
Remote debugging using /dev/cuau0
0x08050054 in ?? ()
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x08050055 in ?? ()
(gdb) x/10i $pc
0x8050055:      push   $0xd
0x805005a:      lea    0x805007a,%eax
0x8050060:      push   %eax
0x8050061:      push   $0x1
0x8050063:      push   $0x0
0x8050065:      mov    $0x4,%eax
0x805006a:      int    $0x80
0x805006c:      add    $0x10,%esp
0x805006f:      push   $0x0
0x8050071:      push   $0x0
(gdb) c
Continuing.

Program exited normally.
---------------------------------------------------------

どうやら、普通に動いていますねぇ。よかったよかった、VMWARE偉いぞ。

make kernel for debug

そなら早速、debug用のkernelを作ってみる。GENERLCをSAKAEにcpして、以下のオプションを 追加。後は、config SAKAEして、make depend してから make。

options         KDB
options         GDB

待つ事数十分。出来たっぽい。make installすると、kernelって言う小さい方が、インストール された。

[sakae@cdr /sys/i386/compile]$ du -sh SAKAE/
646M    SAKAE/
[sakae@cdr /sys/i386/compile/SAKAE]$ ls -lh kernel*
-rwxr-xr-x  1 root  wheel    12M  6 29 15:33 kernel*
-rwxr-xr-x  1 root  wheel    46M  6 29 15:33 kernel.debug*
-rwxr-xr-x  1 root  wheel    36M  6 29 15:33 kernel.symbols*

後は、SAKAEをtarで固めて、もう一方のFreeBSD機に転送して、/sys/i386/compileの所に展開してあげた。 次は、一応新しいkernelで立ち上がるか確認。立ち上がる時に gdbのポートがうんたらかんたらと、 ちょっとメッセージがコンソールに出てたけど、普通に上がってきた。そして普通に使えたよ。

さあ、いよいよだな。心を落ち着けて、 リモート GDB を使ったオンラインカーネルデバッグ に目を通しておく。それにしてもこういうのがHandBookになってるって、いいよな。

いざ始めよう

target機の方から先に起動する。起動メニューが出たら、6番の Escape to boot promptを 選び、そこで boot -d を行う。 もう一方のremote機の方は、/sys/i386/compile/SAKAEへ移動しておいて

[sakae@fb8 /sys/i386/compile/SAKAE]$ kgdb -r /dev/cuau0 kernel.debug

でいいはずなんだけど、target機は止まらずに起動してしまう。何で? これはもうソース嫁って 事なんですかね。

/sys/boot/i386/libi386/bootinfo.c の中のbi_getboothowto(char *kargs)に bootの オプションがあるな。ここを基点にgdbを探してみたら、/sys/gdb/gdb-main.cに行き着いた。 そして、gdb_init を読んでいる所でそれらしいのは、

./dev/sio/sio.c:static gdb_init_f siogdbinit;
./dev/uart/uart_dbg.c:static gdb_init_f uart_dbg_init;

迷宮だなあ。

ここにもお悩みの人が 、そして、 Linuxはどうか とかで、 ここらあたりを参考にして VMWARE Playerを設定して、

serial0.present = "TRUE"
serial0.fileType = "pipe"
serial0.fileName = "\\.\pipe\com_1"
serial0.pipe.endPoint = "server"
serial0.yieldOnMsrRead = "TRUE"
serial0.tryNoRxLoss = "TRUE"

Kernelにもオプションを追加。 (option DDB) 面倒だけど、もう一度kernelを作り直し、 再び試してみると、

[sakae@fb8 /sys/i386/compile/SAKAE]$ kgdb -r /dev/cuau0 kernel.debug
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-marcel-freebsd"...Switching to remote protocol
Ignoring packet error, continuing...
Ignoring packet error, continuing...
Ignoring packet error, continuing...
Couldn't establish connection to remote target
Malformed response to offset query, timeout

(kgdb) q
OK boot -d
GDB: no debug ports present
KDB: debugger backends: ddb
KDB: current backend: ddb
KDB: enter: Boot flags requested debugger
[thread pid 0 tid 0 ]
Stopped at      kdb_enter+0x3a: movl    $0,kdb_why
db>c

やっぱり通信が出来ていないっぽい。つうか、target側でシリアルポートを認識出来て いないんだな。だから、どんどんと低級な機能しかないdebuggerに縮退しちゃうんだ。 これはもう、ddbを覚えて、追ってみるしかないのか。

kbdをキーに場所を探してみたら、/sys/i386/i386/machdep.cの中のinit386か、いかにもって 名前の中だな。ここから先はユニマガの連載の記事が役に立ちそうだけど、もうとっくの昔に 処分しちまったなあ。

ddb

そんな訳で、ちまちまとddbの事を調べている。

ddbへ入る(起動する)方法は3つあると言う。一つは、カーネル起動時に -d オプションを 渡す方法(はからずも上で実験しちゃったもの) 2番目は、OS稼動中にキーコンビネーション により、対話型デバッガーを呼び出す方法。3番目は、シリアル接続した端末から、Break信号を 送り込む方法らしい。

シリアル接続端末方式をちょいと調べてみた。ちょっと面倒そう。 シリアルコンソールとか ハンドブックのシリアル設定方法 が参考になるかな。 でも、もう一度カーネルを作り直すってのが難とも。。。

結局キーコンビネーションでddbを起動させる方法を調べる。古い資料では、CTL+ALT+ESC らしいけど、これって、もろにVMWAREの画面切り替えと衝突してしまって使えないのだ。 だから、マニュアルに当たってみる。

[sakae@cdr ~]$ man syscons
      :
     SC_DISABLE_KDBKEY
            This option disables the ``debug'' key combination (by default, it
            is Alt-Esc, or Ctl-PrintScreen).  It will prevent users from
            entering the kernel debugger (KDB) by pressing the key combina-
            tion.  KDB will still be invoked when the kernel panics or hits a
            break point if it is included in the kernel.  If this option is
            not defined, this behavior may be controlled at runtime by the
            sysctl(8) variable hw.syscons.kbd_debug.

ALT+ESCも画面切り替えに使われてるし、望みは、Ctl+PrintScreen だな。その前に イネーブル/ディセーブルSWを確認しておかねば。

[sakae@cdr ~]$ sysctl -a | grep hw.syscons
hw.syscons.kbd_debug: 1
hw.syscons.kbd_reboot: 1
hw.syscons.bell: 1
hw.syscons.saver.keybonly: 1
hw.syscons.sc_no_suspend_vtswitch: 0

うん大丈夫そうだ。 私のパソコンでのコンビネーションは Ctl+Fn+Insert だな。 VMWAREの小さなシステムコンソールでしかオペレーション出来ないけど、一応 rootで loginしておいて、

cdr# KBD: enter: manual escape to debugger
[thread pic 12 tid 100014 ]
Stopped at      kdb_enter+0x3a: movl     $0,kdb_why
> 

おお、入りましたね。思わずMac時代を思い出しちゃったぞ。 MAC/SE SE30 PowerBook パフォーマーと言う遍歴なんだけど、macsbugと言うdebuggerへ入るSWを必ず取り付けて いたっけ。そして意味もなくdebuggerに落ちて、ごちょごちょやってたな。 これからは、Ctl+Fn+Insert が、病み付きになりそうな予感。

暫くddbで止めておいてから継続すると、けなげにもcalcruって(VMtoolsの一族?)のが 時刻あわせをやってくれる。/var/log/messagesにログが出ていた。

Jun 30 16:23:54 cdr kernel: KDB: enter: manual escape to debugger
Jun 30 16:54:22 cdr kernel: calcru: runtime went backwards from 5593750 usec to 1856745 usec for pid 900 (vmtoolsd)
Jun 30 16:54:22 cdr kernel: calcru: runtime went backwards from 10053 usec to 3322 usec for pid 900 (vmtoolsd)

go ddb

そんじゃ、前回調べてあたりを付けておいた、ELFファイルがどうロードされるか追って みる。名前は、確か load_file っていうはずだから、何処に位置してるか調べてみる。

[sakae@cdr /sys/i386/compile/SAKAE]$ readelf -s kernel.debug | grep load_file
  4120: c0bd36e0    10 FUNC    GLOBAL DEFAULT    5 elf_cpu_unload_file
  4892: c0d8e850     8 OBJECT  GLOBAL DEFAULT   28 linker_load_file_method_d
  6128: c0bd36d0    10 FUNC    GLOBAL DEFAULT    5 elf_cpu_load_file
 10082: c0d8e858     8 OBJECT  GLOBAL DEFAULT   28 linker_load_file_desc
 16169: c085a9c0   842 FUNC    LOCAL  DEFAULT    5 elf32_load_file
 18161: c08c8c50   368 FUNC    LOCAL  DEFAULT    5 link_elf_unload_file
 18165: c08c9010  2509 FUNC    LOCAL  DEFAULT    5 link_elf_load_file
 39183: c0bd36e0    10 FUNC    GLOBAL DEFAULT    5 elf_cpu_unload_file
 39955: c0d8e850     8 OBJECT  GLOBAL DEFAULT   28 linker_load_file_method_d
 41191: c0bd36d0    10 FUNC    GLOBAL DEFAULT    5 elf_cpu_load_file
 45145: c0d8e858     8 OBJECT  GLOBAL DEFAULT   28 linker_load_file_desc

elf32_load_file だろうな、アドレスは、c085a9c0 だから、break 0xc085a9c0 して おいてから、contか。何か適当にコマンドを実行すると止まるな。

残念ながら、VMWAWEのコンソールに結果が出てくるんで、おいそれとはここに貼れないけど traceをすると、syscall --> execve --> kern_execve --> elf32_load_file って経路で 到達してるようだ。それにしても、コンソールと行ったりきたり、めんどくさいな。 何とかならないかなあ。

シリアルコンソール

シリアルコンソール を用意して、そこからBreak信号を送出すれば、ひょっとしてddbに落ちるんでないかい。

例で出てたのはウブンツだけど、FreeBSDでは、/etc/ttysを編集して kill -HUP 1 するだけ。 端末は、teratermを使った。えっと、break信号を発生させるには、ALT+B らしいんで やってみたけど、落ちなかった。不自由する脳。それならばと思って、screenを登場させた。 manによると、C-a C-bでbreakを発生してくれるようだけど、やはり駄目。

screen /dev/cuau0 9600  ;;before do chmod 666 /dev/cuau0

それならば、古式ユカシク

[sakae@fb8 ~]$ cu -l /dev/cuau0 -s 9600
Connected


FreeBSD/i386 (cdr.kuma.net) (ttyu0)

login: root
Password:
  :
cdr# ~?
 ~!      shell
 ~<      receive file from remote host
 ~>      send file to remote host
 ~t      take file from remote UNIX
 ~p      put file to remote UNIX
 ~|      pipe remote file
 ~$      pipe local command to remote host
 ~C      connect program to remote host
 ~c      change directory
 ~.      exit from tip
 ~^D     exit from tip
 ~^Y     suspend tip (local+remote)
 ~^Z     suspend tip (local only)
 ~s      set variable
 ~v      list variables
 ~?      get this summary
 ~#      send break

cdr# ~    ;; <--- ~# but not happen !!

cdr# exit
logout

FreeBSD/i386 (cdr.kuma.net) (ttyu0)

login: ~.
[EOT]
[sakae@fb8 ~]$

やはりというか難と言うか、駄目駄目、ddbに落ちなーーーい! cuならソースを読むのも簡単だろうと思って探してみたら、/usr/src/usr.bin/tip/tip/cmds.cに 該当部分が有ったよ。

void
genbrk(int c)
{
        ioctl(FD, TIOCSBRK, NULL);
        sleep(1);
        ioctl(FD, TIOCCBRK, NULL);
}

こういうのが出てくるって事は、ハードの直接叩きですね。今更ながらbreak信号について 調べてみたら、これって文字の規格(ASCII)に無いんですね。一種のハードウェア割り込み でした。VMWAREでこんなニッチな信号をpipeを使って伝送してるんかいな? はなはだ疑問 が残ります。VMWAREのマニュアルを取り寄せて調べてみたけど言及されてなかった。

残念至極!