この文書は、 Bochs エミュレータ 上で動く、 Minix オペレーティングシステム の関数コールグラフを書く方法を説明します.
こちらは英語版です.
コールグラフの例
fs:do_read
fs:do_read+0x5
fs:read_wr
fs:read_wr+0xa0
fs:get_fil
fs:get_fil+0x2d ret
fs:read_wr+0xa5
fs:read_wr+0x119
fs:sys_uma
fs:sys_uma+0x2a
fs:_taskca
fs:_taskca+0x12
fs:_sendre
実行時の関数コールグラフは、ソースコードを勉強するのに役に立ちます。
Bochs は、もしデバッガを有効にして configure されたなら、実行するそれぞれの 命令をログファイルに記録することが出来ます。 それぞれの命令アドレスは、 nm で作られるマップファイルを使って、 シンボルに変換することができます。 ターゲットのオペレーティングシステム Minix への変更は不要です。
環境
Minix 3.1.0
Bochs-2.3.6 built with Microsoft VC++ 2005 and Platform SDK for Windows Server 2003 SP1
Step 1 Minix をコンパイルする。
# cd /usr/src/tools
# make hdboot
Step 2 Minix で、マップファイルを作る。
# cd /usr/src/
# nm -n kernel/kernel > kernel.map
# nm -n servers/fs/fs > fs.map
etc.
# more kernel.map
kernel/kernel:
00000000 d
...
00002fe8 T _do_fork
Step 3 マップファイルをホストコンピュータにコピーする。
# mtools copy *.map a:
and
C:\>copy a:*.map your-bochs-dir
Step 4 カーネルモジュールのロードアドレスを得る。
ブート時に、Minix boot loader は以下のメッセージを出します。
Bochs gui の "snapshot" ボタンで、これをファイルに記録します。
d0p0s0>boot
Loading Boot image 3.1.0 revision 0.
cs ds text data bss stack
0000800 0005800 19568 3144 29808 0 kernel
0100000 0105000 19504 2360 48616 1024 pm
0111c00 011c400 42624 5556 5316124 2048 fs
0630000 0631400 4352 616 4696 131072 rs
0652c00 0659400 26144 4996 44192 1024 tty
0665c00 0667000 4784 764 3012 4096 memory
0669000 066a800 5904 504 63276 4096 log
067b400 0681400 23680 10776 10932 8192 AT:at_wini
0688c00 068a800 7088 2284 1356 768 init
Step 5 ホストコンピュータで、 Bochs を internal debugger support つきで
コンパイルします。
--enable-debugger を configure コマンドの引数に指定するか、
.conf.win32-vcpp. をなおしてください。
詳しくは、
Bochs User Manual, "Compiling on Win32 with Microsoft VC++", and
"Using Bochs internal debugger, Instruction tracing"
を参照。
Step 6 .bochsrc で、デバッガログファイルを有効にする。
debugger_log: debugger.out
Step 7 私の書いたスクリプト minixtrace.pl を手で編集して、
カーネルモジュールのロードアドレスを、ステップ5で得たファイルに
したがって、調整してください。ここが、きたないですね。
Step 8 新しい Minix を新しい Bochs でブートする。
Step 9 あなたがトレースをとりたい関数の物理アドレスをさがして、
そこにブレークポイントをかけます。
今の場合、 do_read in servers/fs/read.c です。
C:\>findstr do_read fs.map
000013d8 T _do_read
一方, Minix boot loader に、こうありますから、
0111c00 011c400 42624 5556 5316124 2048 fs
これが求めるアドレスです。
0x0111c00 + 0x000013d8 == 0x000112FD8
Bochs で、そのアドレスを見てみましょう。
<bochs:2> disassemble 0x000112FD8 0x000112FF8
00112fd8: ( ): push ebp ; 55
00112fd9: ( ): mov ebp, esp ; 89e5
00112fdb: ( ): push 0x00000000 ; 6a00
00112fdd: ( ): call .+0x00000003 ; e803000000
00112fe2: ( ): pop ecx ; 59
00112fe3: ( ): leave ; c9
00112fe4: ( ): ret ; c3
よさそうですね。さて、ブレークポイントを、 do_read の入り口と出口に
かけます。
<bochs:2> break 0x000112FD8
<bochs:3> break 0x000112fe2
<bochs:4> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x00112fd8
2 pbreakpoint keep y 0x00112fe2
Step 10 何かをします。 Minix でファイルを読んでみます。
# cat /home/kanda/hello > /dev/null
Step 11 ブレークポイントに当たったら、トレースを有効にします。
(0) Breakpoint 1, 0x0000000000112fd8 in ?? ()
Next at t=564623263
(0) [0x00112fd8] 0007:00000000000013d8 (unk. ctxt): push ebp ; 55
<bochs:6> trace on
Tracing enabled for CPU 0
Step 12 続行して、終了のブレークポイントに来たら、トレースを無効にします。
<bochs:7>c
... たくさんのトレースレコードが出ます。... 何分もかかることがあります。...
(0) Breakpoint 2, 0x0000000000112fe2 in ?? ()
Next at t=564626075
(0) [0x00112fe2] 0007:00000000000013e2 (unk. ctxt): pop ecx ; 59
<bochs:8> trace off
Tracing disabled for CPU 0
Step 13 だいたい終わりです。ブレークポイントを無効にして、
Minix を続行させます。
<bochs:9> bpd 1
<bochs:10> bpd 2
<bochs:11> c
Step 14 ホストコンピュータで、スクリプト minixtrace.pl を、
ログファイル debugger.out に対して実行します。
kernel.map などを、カレントディレクトリに置いておいてください。
C:\>perl minixtrace.pl debugger.out | more
(0) Breakpoint 1, 0x0000000000112fd8 in ?? ()
fs:do_read
fs:do_read+0x5
fs:read_wr
...
Enjoy.
do_read のコールグラフ
スクリプト minixtrace.pl
ステップ9で、カーネルの最初の命令にブレークポイントをかけます。 restart にもかけます。そこで、main in kernel/main.c が終わるためです。
00000431 T _restart
<bochs:1> b 0x0800
<bochs:4> b 0x0c31
MINIX and main のコールグラフ
do_open in servers/fs/open.c の最初と最後にブレークポイントをかけます。 そして、ルートディレクトリの、既存の通常ファイルを開きます。
# cat /hello.txt
00000a76 T _do_open
00000ae8 t _common_
<bochs:23> b 0x112676
<bochs:24> b 0x1126e6
Call graph of open.
do_open に同じブレークポイントをかけます。新しいファイルを作ります。
# date > /world.txt
Call graph of create.
do_exec in servers/pm/exec.c and at pm_exit にブレークポイントを かけます。
00000e34 T _do_exec
00000913 T _pm_exit
<bochs:1> b 0x100E34
<bochs:2> b 0x100913
exec /usr/bin/date のコールグラフ 大きすぎて、何が起きているのか、わかりません。
Amsterdam Compiler Kit (ACK) cc は、8バイトより長い関数名を、きりつめます。
Minix 3.1.2a を gcc でビルドして、名前のきりつめのないコールグラフを 書きました。こんなかんじです。
pm:do_fork
pm:do_fork+0x7c
pm:alloc_mem
pm:alloc_mem+0xba ret
pm:do_fork+0x81
pm:do_fork+0xce
pm:sys_physcopy
pm:sys_physcopy+0x49
pm:_taskcall
以下、注意点です。
1) Gcc は 80 MB のメモリが必要。
# PATH=$PATH:/usr/gnu/bin
# gcc helloworld.c
gcc: installation problem, cannot exec `/usr/gnu/libexec/gcc/i386-pc-minix/3.4.3/cc1': Not enough core
# size /usr/gnu/libexec/gcc/i386-pc-minix/3.4.3/cc1
text data bss stack memory
2535864 606696 542792 73400320 77085672 cc1
2) Gcc は Minix の .s アセンブラファイルを扱えない.
# gcc -c kernel/mpx386.s
mpx386.s:2: Error: junk at end of line, first unrecognized character is `!'
mpx386.s:46: Error: unknown pseudo-op: `.sect'
3) ack cc, anm, aal というツールチェーンと gcc, gnm を混ぜて使うことはできない.
4) Minix 3.1.0 book version を Minix 3.1.2a の上でビルドしようとしたら できませんでした
nb_send という関数がありませんでした. ユーザ空間のライブラリは、カーネルと 同期してないといけません。gcc の話ではありませんが.
ack cc を /usr/src/kernel に使って、gcc でいくつかのサーバをビルドしました.
# cd /usr/src/servers/fs
# make CC=gcc
... Same for pm and other servers.
# cd /usr/src/tools
# make hdboot
installboot と boot loader は無事に動きました.
Minix 3 book 「オペレーティングシステム 設計と実装 第3版」 タネンバウム他 著、吉澤康文、木村信二他 著、をきちんと読む。
do_read のトレースを読む。実際、私は、 do_read の延長で何が動くか知らないので、 生成されたコールグラフがバグっていてもわかんないのです。(笑)。
strncmp など、小さい関数は表示しないほうがよい。
クロックチックを長く出来たら、関係ない割り込みとコンテキストスイッチを除けるのに。
もっとコールグラフをアップロードする。
Bochs の "load-symbols" and "show call" デバッガコマンドを使ってみる。
Q. カーネルパニックします。
Debug exception
k_reenter = 1 process -4 (-IDLE), pc = 5:0xE74
Kernel panic: exception in a kernel task
sys_call: trap 1 not allowed, caller -4, sr
A. Minix/Bochs のどちらが悪いのかわかりませんが、panic 文をコメントアウト するのが、とりあえずの回避策です。
kernel/exception.c
98 /* XXX panic("exception in a kernel task", NO_NUM); */
それと、 /etc/rc の、 fsck はコメントアウトしたほうがいいかもしれません。 エミュレータでの fsck は遅いですから。
if shutdown -C
# fflag=t XXX
Q. keymap を japanese にすると、コロン : パイプ | などのキーが入力できません。私は、 Fedora Core 2 の X Window 環境を使っています。
A. Minix の jp106 keymap は、生のPC,Windows 上の Bochs, Fedora Core 2 CLI コンソールの Bochs で、正常に動いています。 "display_library: term" を、 .bochsrc に指定したらどうでしょうか。
いや、そうすると、改行が乱れるのだな。これ以上は、わかりません。
参考
Minix on Bochs by Prof. Woodhull.
Linux kernel function trace の結果をいくつか
Minix 3 正誤表
Google App Engine で動く、Linux kernel function trace のブラウザ (NEW)
著者
Kanda.Motohiro@gmail.com
Last update July 4 2008