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をインストールした。
- CPU: Intel Core i7 870 (8MB L3キャッシュ/2.93GHz)
- メモリ: DDR-3 SDRAM(PC3-10800) 2GB
- HDD: 500GB Serial ATA
- ビデオカード: NVIDIA GeForce GT220 / 1GB
- ドライブ: DVDスーパーマルチドライブ
- LAN: ギガビット(10/100/1000) LAN
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 ?? ()