2011年11月26日土曜日

KVM上のFedora 16 kernelをgdbでデバッグ

まえがき

こないだ衝動にかられてThinkpad X121e (Core i3モデル)を買ってしまった。IBM時代からのThinkpadファンだったのだが、iPhoneを買ったのを機に、にわかMacユーザーになっていたので、久しぶりにPCに復帰した形だ。キーボードがMacBook Proと同じタイプ(セパレート型というのだろうか)だが、MacBook Proよりもタイプ感が良い気がする。・・・というように、5万円(MacBook Proの1/3)の割にかなり満足度の高い買い物であった。Windows要らないので、そのぶん1000円でもいいから安くして欲しいとは思うが、まぁしょうがない。Windows 7さまは一度も起動されることなくFedora 16で上書きされた。さようなら。

さて新しいマシンで何をしようかと思っていたが、あれこれ記事を見ていると、KVMで動かしたLinux kernelをgdbでデバッグできるらしいので、それを試してみた。数年前にXenのインストールで挫折したので、仮想化関連で何か試してみたかったのだ。

ネットワークの知識不足もあってはまったところも多いので、まとめておきたい。なお、ホストはFedora 16 x86_64なので、環境が異なる人は多少の読み替えが必要だろう。


ネットワーク

KVM のドキュメントはたくさん見つかるが、多くはKVMを外からアクセス可能な、完全に独立したマシンとして作成することを前提としているようで、KVMに外からつなげるにはどうするか、ということを説明しているページが多いような気がする。

しかし、「KVMからインターネットにアクセスしたり、ホストマシンからKVMにアクセスしたりするが、その他のマシンからKVMにアクセスすることはない」という環境の場合、特別な設定は必要ない。「仮想ブリッジを作って外のネットワークと繋げて・・・」という説明もあるが、ブリッジをつくろうとすると、NetworkManagerが使えなくなって不便だし(NetworkManagerはブリッジをサポートしていない)、(ラップトップではデフォルトであろう)Wifiのカード・ドライバがブリッジに対応しているか調べなきゃいけないしで、大変なことこの上ないので、何もしないのがよかろう。

Fedora 16にはlibvirtがデフォルトで入っていて、ifconfigするとvirbr0という、見るからに仮想っぽいインターフェイスが見えるのだが、これを使ってホスト<->KVMの通信ができる。
$ /sbin/ifconfig virbr0              
virbr0    Link encap:Ethernet  HWaddr 52:54:00:F0:D1:F7  
inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:5713 errors:0 dropped:0 overruns:0 frame:0
TX packets:5048 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0 
RX bytes:1552502 (1.4 MiB)  TX bytes:344602 (336.5 KiB)
あと、NAT/IP masqueradeも設定されていて、iptables -t nat -Lで確認できる。
$ sudo /sbin/iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  tcp  --  192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
MASQUERADE  udp  --  192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
MASQUERADE  all  --  192.168.122.0/24    !192.168.122.0/24    

まとめると、KVM用に192.168.122.0/24のネットワークが用意されていて、ホスト<ー>KVMはこのネットワークでつながることになる。また、ホストのiptablesにNAT/IP masqueradeの設定があるので、KVMからインターネットにアクセスすることもできる。(が、インターネット側からKVMにはアクセスできない)


Fedora 16 インストール

ネットワークは問題ないので、いよいよ仮想マシンを作ってOSをインストールする。virt-installというコマンドで、仮想マシンの設定と、OSインストールをスタートできる。(virt-managerを使うとGUIでインストールできるが試してない。)

ちなみに、virt-installとかvirt-managerとか(あとででてくる)virshとかは、libvirtが提供するコマンドで、KVMやXenやVirtualBoxなどの複数の仮想化環境を統一的に扱うためのツール群である(が、ここではKVMしか使わないので、恩恵はない。)

$ sudo virt-install \
-n fedora.kvm \ -- 仮想マシンの名前
-r 1024 \ -- メモリ (MB)
-f /var/kvm/images/fedora.kvm.img \ -- ディスクイメージの保存場所
-s 20 \ -- ディスクのサイズ
--vcpus=1 \ -- 割り当てるCPUの数
--os-type=linux \ -- OSタイプ。何に使われるのか不明。
--os-variant=fedora16 \ -- OSの細かい種別。何に使われるのか不明。
--network network=default \ -- 後述
--nographics \ -- テキストモードでインストールする
--location='http://ftp.jaist.ac.jp/pub/Linux/Fedora/releases/16/Fedora/x86_64/os/' \ -- インストールソースの指定。"man virt-install"にサンプルがある。
--extra-args='console=tty0 console=ttyS0,115200n8 serial' -- 再起動後にvirshから接続するために必要っぽいが、よくわからない。

"man virt-install"のNetworking Configurationを読むとわかるが、network=XXXのXXXに指定する名前は、virshコマンドで確認できる。virshは、おそらくVIRtual SHellの略でlibvirtの機能にアクセスするためのシェル。KVM作成後のVMの起動や終了もvirshから行う。以下のようにして、ネットワーク名を確認できる。(ホスト側に普通にFedora 16をインストールしていれば)"default"というネットワークが見えるはず。
$ sudo virsh
Welcome to virsh, the virtualization interactive terminal.

Type:  'help' for help with commands
'quit' to quit

virsh # net-list
Name                 State      Autostart
-----------------------------------------
default              active     yes       

で、上記のvirt-installコマンドを実行すると、Fedoraのサイトからいろいろダウンロードしたあと、インストールプロセスが始まる。
KVMとは本質的には関係ないが、Fedora 16はテキストインストール時には、デフォルトパーティション(LVMとか使うやつ)しか選べない(カスタマイズできない)。カスタマイズしたい場合は、途中でVNCを使ってGUIインストールに切り替えれば良い。その場合は、yumを使ってホスト側にtigervncというVNCクライアントをインストールしておくこと。

インストールが終わると、KVMが再起動して、ゲスト側のログインが表示される。

なお、KVM起動したあと、virshで"console fedora.kvm"と打つと、KVMのコンソールにつなぐことができる。(何らかの事情でSSHが使えなくなった時などは、コンソール接続必須)ちなみに、virshにはconnectというサブコマンドもあり、こちらはhypervisorに接続するためのコマンドなのだが、間違えて "connect fedora.kvm"と入力すると、virshの内部状態が壊れるようで、それ以降のすべてのコマンドがエラーになる。(quitして再度virshを起動すると直る)virsh 0.9.6のバグだと思うが気をつけていただきたい。


ゲストのIPの固定

ここまでの作業でKVM上のゲストOSは普通に使うことができる。が、ゲスト側のIPがDHCPなので、IPがいつか変わってしまうのではないかと思うと不安で夜も眠れないので、IPを固定することにする。

ホスト-KVM間の仮想ネットワークではDHCPが動いていて、192.168.122.2-254のIP(つまり全て)をDHCPで使っているので、まずこれを変更する。(変更しなくても問題ないのかもしれないが、気持ち悪いので) virshを起動し、"net-edit default"と入力すると、設定を変更できる。

<network>
  <name>default</name>
  <uuid>6c2b5811-9733-490f-b14a-e7d3c73c5d87</uuid>
  <forward mode='nat'/>
  <bridge name='virbr0' stp='on' delay='0' />
  <mac address='52:54:00:F0:D1:F7'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254' />
    </dhcp>
  </ip>
</network>
と表示されるはずなので、254を適当に100などに変えておく。

あとは、ゲスト側の/etc/sysconfig/network-scripts/ifcfg-eth0を、こんな感じに書き換えれば良い。ここでは、192.168.122.240を割り当てることにする。

IPV6INIT="yes"
NM_CONTROLLED="no"
IPV6_AUTOCONF="yes"
HWADDR="52:54:00:7B:F4:D0"
BOOTPROTO="none"
DEVICE="eth0"
ONBOOT="yes"
IPADDR="192.168.122.240"
NETMASK="255.255.255.0"
GATEWAY="192.168.122.1"

gdbでデバッグするための設定

KVM(が使っているqemu)は、gdbserverの機能があり、起動時に-sオプションを指定することで、TCPポート 1234 でgdbからの接続を待つ。

ここに書いてあるとおりだが、virshで"edit fedora.kvm"と入力して、KVMを定義しているXMLを変更し、ルートエレメントのアトリビュートとして xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' を追加し、<domain>の最後の子要素に、
<qemu:commandline>
<qemu:arg value='-s'/>
</qemu:commandline>
を加える。

この変更をする前に、KVMを停止 (destroy) しておかないと反映されないようなので注意。


デバッグ
シンボル情報なしで、gdbからKVMに接続するとKVMが固まってしまう。バグっぽいが、シンボル情報があるに越したことはないので、vmlinux (vmlinuzではない)を(gdbが動くホスト側に)インストールする。Fedoraでは、kernel-debuginfoパッケージにvmlinuxが含まれているので、yumを使ってインストールする。

$ sudo yum \
--enablerepo=fedora-debuginfo \
--enablerepo=updates-debuginfo \
install kernel-debuginfo

ここでインストールしたkernel-debuginfoのバージョンと、ゲストのkernelのバージョンとが合ってないと、シンボルとアドレスがずれたりしてあとあと面倒になりそうなので注意すること。

これで準備完了。この記事と同じく、ext4_writepageで止めてみる。

まず、KVMを起動する。virshで"start fedora.kvm"。sshかvirshのconsoleコマンドでログインしておく。

ホスト側でgdbを起動し、vmlinuxからシンボル情報を読み込ませ、ブレイクポイントを指定し、デバッグターゲットを指定する。

$ gdb
GNU gdb (GDB) Fedora (7.3.50.20110722-10.fc16)
Copyright (C) 2011 Free Software Foundation, Inc.
(...)
(gdb) file /usr/lib/debug/lib/modules/3.1.2-1.fc16.x86_64/vmlinux 
Reading symbols from /usr/lib/debug/lib/modules/3.1.2-1.fc16.x86_64/vmlinux...done.
(gdb) b ext4_writepage
Breakpoint 1 at 0xffffffff8118c590: file fs/ext4/inode.c, line 1778.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
native_safe_halt () at /usr/src/debug/kernel-3.1.fc16/linux-3.1.x86_64/arch/x86/include/asm/irqflags.h:50
50      }
(gdb)

attachした瞬間に、ゲストはnative_safe_haltで止まるので、gdbのcontinueコマンドでゲストを走らせる。
あとは、ログインしておいたゲストOS側でなんかすれば、ext4_writepageで止まるはず。

(gdb) continue
Continuing.

Breakpoint 1, ext4_writepage (page=0xffffea0000c54880, wbc=0xffff880039499dd8) at fs/ext4/inode.c:1778
1778    {
(gdb) bt
#0  ext4_writepage (page=0xffffea0000c54880, wbc=0xffff880039499dd8) at fs/ext4/inode.c:1778
#1  0xffffffff810e61a2 in __writepage (page=, wbc=, data=0xffff880038d7e8f8) at mm/page-writeback.c:1222
#2  0xffffffff810e5fd4 in write_cache_pages (mapping=0xffff880038d7e8f8, wbc=0xffff880039499dd8, 
writepage=0xffffffff810e618d <__writepage>, data=0xffff880038d7e8f8) at mm/page-writeback.c:1160
#3  0xffffffff810e6144 in generic_writepages (mapping=0xffff880038d7e8f8, wbc=) at mm/page-writeback.c:1246
#4  0xffffffff811bace0 in journal_submit_inode_data_buffers (mapping=) at fs/jbd2/commit.c:186
#5  journal_submit_data_buffers (commit_transaction=0xffff880036c66400, journal=0xffff880036e3e000) at fs/jbd2/commit.c:217
#6  jbd2_journal_commit_transaction (journal=0xffff880036e3e000) at fs/jbd2/commit.c:456
#7  0xffffffff811bef97 in kjournald2 (arg=0xffff880036e3e000) at fs/jbd2/journal.c:162
#8  0xffffffff81072cdf in kthread (_create=0xffff8800385c3b00) at kernel/kthread.c:96
#9  0xffffffff814bfa74 in ?? () at arch/x86/kernel/entry_64.S:1152
#10 0x0000000000000000 in ?? ()

backtraceも見れた。


終わりに
Fedora 16上でのKVM+gdbによるkernelデバッグをやってみた。ウェブを見るとUbuntuでやってる人が多いようなので、Fedoraでやりたい人の参考になれば幸いだ。

0 件のコメント:

コメントを投稿