QEMUで動くLinuxをGDBで観察する
「必要なコンパイルオプションを設定し挑戦したけどうまく行かなかった」そんな失敗を供養をまとめていたら渡してるパラメータのポカミスに気づきました。 やり直したらうまくいきました。というわけで成功した方法のメモです。
QEMU+GDBで起動やシステムコールの動きを実際に確認したかった。
やったこと
作業としては5ステップ。
- カーネルビルド環境を作る
- カーネルをビルドする
- デバッグ環境を準備する
- QEMUとGDBを繋げる
- GDBでアクセスして動きを確認する
環境
|
|
カーネルビルド環境の準備
必要なパッケージをインストールします。
|
|
Linuxのソースコードを取得します。
|
|
カーネルをビルドする
デバッグに必要なオプションは DEBUG_INFO=y, DEBUG_KERNEL=y, RELOCATABLE=n らしいので設定しました。 また CONFIG_DEBUG_INFO_DWARF4=y を設定してみました。これによるとデバッグ情報が大きくなるけど最適化コードでも変数の解決成功率が高まるらしいです。
色々と必要な設定をするのが大変なので make defconfig で .config を作ってからmake menuconfig で設定しました。
|
|
メニューで設定できることが多くて迷子になるけど、 make menuconfig のダイアログで/
を打つとオプションを検索できます。
|
|
デバッグ環境を準備する
はじめにツールを準備します。
|
|
QEMUでは初期RAMディスクを -initrd フラグで指定します。初期RAMディスクを指定しないと下のようにpanicが起こります。
[ 3.284738] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[ 3.286360] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.12 #7
[ 3.286608] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
[ 3.290602] Call Trace:
[ 3.292184] dump_stack+0x57/0x6a
[ 3.292386] panic+0xf3/0x2b4
[ 3.295570] mount_block_root+0x17d/0x21d
[ 3.297225] ? rdinit_setup+0x26/0x26
[ 3.297515] mount_root+0xec/0x10a
[ 3.297737] ? initrd_load+0x37/0x3a
[ 3.298702] prepare_namespace+0x130/0x15f
[ 3.299168] kernel_init_freeable+0x1dc/0x1eb
[ 3.300168] ? rest_init+0xb0/0xb0
[ 3.300391] kernel_init+0x5/0x110
[ 3.300480] ret_from_fork+0x22/0x30
[ 3.301873] Kernel Offset: disabled
[ 3.302444] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
下のように作っておきましょう。 mknod(1) で2つのデバイスファイルを定義していてこれは必要なようです。 パラメータの意味は $KERNEL_SOURCE/Documentation/admin-guide/devices.txt に書かれています。
|
|
QEMUとGDBを繋げる
2つのターミナルを使います。1つはQEMUでLinuxを起動します。
|
|
もう一方でGDBを起動してQEMUで開けているポートに接続します。
|
|
こんな感じで進めていると下のようになります。 画面はVMの中で ls コマンドを打って sh が呼んだ clone(2) の中でのブレークポイント。
QEMUは Ctrl-A,x でQEMU自体を止めないと何もできなくなるので注意が必要だった。
おわりに
ここまでするのに凡ミスで意図した結果にならなくて苦労もしましたが、安心して実験できるようになれて嬉しいです。 反省文を書くの大事です。仕事でも質問をまとめてると解決したりしますね。 またカーネル内部であっても start_kernel() や sys_clone() 含めて知りたい関数の動きを実際にGDBで追えるようになったのは勉強しやすくなると思っています。 一方で、モジュールやシステム全体をQEMUで準備するのはまだ難しいと感じています。今後は時間をみてkprobeなど観測する技術を身につけて行きたいです。
Linuxの話にもどりますが、clone(2) のカーネル側のハンドラを sys_clone() だと思っていたのですが、今回の環境では __x64_sys_clone でした。 マクロやリンカなどがアーキテクチャ毎に実装を変えているというのは自然に思います。 ですが、 kprobe で見えていた sys_clone() とはなんだったのだろうかという疑問が湧きました。 これもまた宿題のようです。