LinuxカーネルHack: GDBとKVMによるカーネルデバッグ

これまでUML(User Mode Linux)でカーネルデバッグできる環境を使って、カーネルの解析等を行ってきた。UMLの環境は、VMWare Server上のUbuntu 8.0.4に構築していたが、最近、起動しなってしまった。これまでKVM(Kernel-based Virtual Machine)に興味があったものの、手元にはIntel VT対応CPUが無く、ずっと試せない状況だった。そこで、Intel VT対応のマシンを新たに購入し、そのマシンにLinuxカーネル開発環境を作ることにした。

余談: 購入したマシンについて

購入したマシンスペックは、以下の通り。o'zzioのXRシリーズ。http://www.ozzio.jp/html/ozzio_xr_specs.htm このマシンにUbuntu 10.10をインストールした。

OS無しモデルで8万円だった。メモリーを追加で2GB買ったので9万くらいにはなったけれども、この値段でこれだけのスペックが手に入る時代になったのだなと感動した。購入した時は全く気にしていなかったビデオカード。ビデオカードはUbuntu 10.10とも相性が良いようで、今のところ快適に使えている。NVIDIAのドライバをインストールすることで、デュアルディスプレイも簡単に設定できた。

KVMのインストール

さて、ここからKVMのインストールに入る。次のページを参考にしてインストールした。http://www.atmarkit.co.jp/flinux/rensai/kvm02/kvm02a.html
KVMとして走らせるOSは、Ubuntu 10.10にする。

以下の手順でインストールした。

% sudo apt-get install kvm bridge-utils
% mkdir ~/kvm
% cd ~/kvm

50GBの仮想ディスクを作成。

% kvm-img kvm-img create -f qcow2 ubuntu.img 50GB

ドライブにUbuntu 10.10のディスクを入れてから、以下を実行。Ubuntuのインストーラが起動する。特に何も考えずほぼデフォルトの設定でインストールした。変更した設定としては、パスワードを入力しなくても自動ログインできるようにしたこと。このVMは自分しか使わないし、起動の都度パスワードを入れるのは面倒なので。

% kvm -hda ubuntu.img -cdrom /dev/cdrom -boot d -m 384 -monitor stdio

インストール完了。ゲストOSを起動してみる。手元の環境では10秒ほどでUbuntuが起動した。こんなにも速く起動できるのかと、驚いた。

% kvm -hda ubuntu.img -boot c -m 384 -monitor stdio

デバッグシンボル(vmlinux)の準備

この記事によると、GDBからKVMをリモートデバッグできるらしい。
http://d.hatena.ne.jp/big-eyed-hamster/20091211/1260540819

デバッグするにはGDBにLinuxカーネルのシンボル情報を読み込ませる必要がある。シンボル情報はvmlinuxに入っている。

しかし、次のページの情報によると、Ubuntuにはvmlinuxは提供されておらず、自力でカーネルをビルドして用意する必要があるらしい。
http://www.crashcourse.ca/introduction-linux-kernel-programming/intermission-building-vmlinux-file-under-ubuntu-free-lesson

そこで、KVMにインストールしたUbuntuのカーネルと同一のカーネルをビルドすることでvmlinuxファイルを用意することにした。
Ubuntu 10.10カーネルのgitツリーはhttps://wiki.ubuntu.com/Kernel/SourceCodeを見ると、git://kernel.ubuntu.com/ubuntu/ubuntu-maverick.git の模様。

% sudo apt-get build-dep linux-image-$(uname -r)
% git clone git://kernel.ubuntu.com/ubuntu/ubuntu-maverick.git

カーネルの厳密なバージョンを調べる。

$ uname -a
Linux xr 2.6.35-22-generic #35-Ubuntu SMP Sat Oct 16 20:36:48 UTC 2010 i686 GNU/Linux

調べたバージョンをgit checkoutする。

% cd ubuntu-maverick
% git checkout Ubuntu-2.6.35-22.35

説明に従い、cleanしてからカーネルをビルドした。どうやら、これだけでカーネルを並列ビルドしてくれるようだ。ビルド中にtopコマンドで確認するとccが同時に8つくらい実行されていることを確認した。

% fakeroot debian/rules clean
% fakeroot debian/rules binary-generic

ビルド完了。debian/build/build-generic/vmlinuxが作成された。

GDBからのリモートデバッグ

GDBからKVMをリモートデバッグするには、KVMの起動時に-sオプションを指定する必要がある。man kvmを見ると、他にも様々な起動オプションがあるようだ。

% kvm -hda ubuntu.img -boot c -s -m 384 -monitor stdio

GDBからリモートデバッグしてみる。fileコマンドでvmlinuxファイルを指定し、カーネルのシンボル情報をロードする。ブレークポイントの指定を考える。今回は特にデバッグしたい所は無いので、ブレークしたことがわかる関数ならなんでも良い。そう言えば、KVMのUbuntuのファイルシステムは、デフォルトの設定でインストールしたので、Ext4になっている。関数名からしてこれは呼ばれそうということで、ext4_writepageを指定してみることにした。-sオプションで起動したKVMは、デフォルト1234ポートで受け付けるので、そこに対して接続。接続できた瞬間、native_safe_haltで停止した。こういうものなのか。

% gdb
(gdb) file ~/ubuntu-maverick/debian/build/build-generic/vmlinux
Reading symbols from /home/fixme/ubuntu-maverick/debian/build/build-generic/vmlinux...done.
(gdb) b ext4_writepage

Breakpoint 1 at 0xc029b91e: file /home/fixme/ubuntu-maverick/fs/ext4/inode.c, line 2681.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
native_safe_halt ()
    at /home/fixme/ubuntu-maverick/arch/x86/include/asm/irqflags.h:50
50	}

処理を続行する。しばらくすると、設定したブレークポイントで停止した。バックトレースもちゃんと取れる。

(gdb) c
Continuing.

Breakpoint 1, ext4_writepage (page=0xc110d7c0, wbc=0xd6521e98)
    at /home/fixme/ubuntu-maverick/fs/ext4/inode.c:2681
2681		trace_ext4_writepage(inode, page);
(gdb) bt
#0  ext4_writepage (page=0xc110d7c0, wbc=0xd6521e98)
    at /home/fixme/ubuntu-maverick/fs/ext4/inode.c:2681
#1  0xc01e0760 in __writepage (page=0xc110d7c0, wbc=0xd6521e98, 
    data=0xd55cc3b0) at /home/fixme/ubuntu-maverick/mm/page-writeback.c:999
#2  0xc01e16d6 in write_cache_pages (mapping=0xd55cc3b0, 
    wbc=<value optimized out>, writepage=<value optimized out>, 
    data=0xd55cc3b0) at /home/fixme/ubuntu-maverick/mm/page-writeback.c:932
#3  0xc01e18b4 in generic_writepages (mapping=0xc110d7c0, wbc=0xd6521e98)
    at /home/fixme/ubuntu-maverick/mm/page-writeback.c:1019
#4  0xc02d28e9 in journal_submit_inode_data_buffers (
    journal=<value optimized out>, commit_transaction=<value optimized out>)
    at /home/fixme/ubuntu-maverick/fs/jbd2/commit.c:226
#5  journal_submit_data_buffers (journal=<value optimized out>, 
    commit_transaction=<value optimized out>)
    at /home/fixme/ubuntu-maverick/fs/jbd2/commit.c:257
#6  0xc02d2ebf in jbd2_journal_commit_transaction (
    journal=<value optimized out>)
    at /home/fixme/ubuntu-maverick/fs/jbd2/commit.c:508
#7  0xc02d8104 in kjournald2 (arg=<value optimized out>)
    at /home/fixme/ubuntu-maverick/fs/jbd2/journal.c:159
#8  0xc01659e4 in kthread (_create=0xd52b7d1c)
    at /home/fixme/ubuntu-maverick/kernel/kthread.c:78
#9  0xc010363e in ?? ()

おわりに

KVMを用いることで、UMLと同様にGDBでカーネルをデバッグできることが確認できた。ハードが良くなったのもあるが、UMLよりも断然ゲストOSの起動時間が速くなったので、カーネル開発環境としても実用的なレベルに達している。

ここまでできれば、メインラインのカーネルに対しても同様のデバッグは難しくないはずなので、今後はそれをできるようにしてみたい。