Linux kernelのQEMU+GDBデバッグ環境を整える(Ubuntu)
前書き
この記事はTSG Advent Calendar 2018の14日目の記事として書かれました。
昨日はfiord氏のTSG LIVE2!の感想・反省等を適当に書いたポエムでした。
TSGのみなさん、初めまして、kakinotaneです。(入部したばっかり)
現在TSGではLinux Source Code Reading 分科会をやっているのですが、その時Linux Kernelのデバッグ環境構築にみんながかなり苦戦しました(JP3BGY氏を除く)。
そこで今回はその環境構築についてまとめたいと思います。
環境
Host: macOS High Sierra 10.13.4
Guest: Ubuntu 16.04 (Virtualbox5.2.18)
kernel: Linux4.15
qemu version 2.5.0
gdb 7.11.1
gcc 5.4.0
make 4.1
HostがmacOSになっていますが、、gdbを動かすのはUbuntuなので実質的なHostはUbuntuと考えて問題ありません。
kernelのコンパイルには時間がかかるので、あらかじめUbuntuが複数のコアを使えるようにVirtualboxで設定しています。
デバッグ方法
Linux kernelのデバッグにはいくつか方法があります。
私は初めに1を試したのですがうまくいかず、2で再チャレンジしたところうまくいきました。
3については試していません。
kgdb + gdb
GuestOSのkernelのkgdbにHostOSのgdbを繋いでデバッグする方法です。
この方法は2つのOSが必要になります。
qemuを使わずにvirtualboxだけでデバッグ出来るのがありがたい点でしょうか。
やり方については以下のサイトに書かれているのでそちらを参考にしてください。
カーネルエクスプロイト入門 - Linuxカーネル解析の基礎 - - るくすの日記 ~ Out_Of_Range ~
これを自分でやろうとしたところ、Host上でコンパイルしたvmlinuxがelf形式だったために、Mach-O形式を動かすmac上ではgdbを繋ごうとするとエラーが出てしまいました。
うまく設定をいじくれば出来るということを聞いたのですがめんどくさそうだったので諦めました。
また暇だったら再チャレンジしようかなぁ
qemu + gdb
kernelをqemu(キューエミュ)上で動かし、そこにgdbを繋ぐ方法です。
今回はこの方法でやりました。
qemu上でkernelを動かすためにはinitrdが必要だったりでめんどくさかったのですが、慣れれば大したことないっぽいです。
以下ではこちらのサイトを参考に解説していきます。
おそらくmac上でqemuを動かしてもうまくいきますが、再現しやすいのもあるので今回はvirtualbox上のUbuntuでやっています。
Linux kernelの準備
まずはkernelをインストール・コンパイルしていきます。
インストール
git clone --depth=1 -b v4.15 https://github.com/torvalds/linux.git linux-4.15 sudo apt-get install ncurses-dev
ncursesはこの後のmenuconfigの際に必要なのであらかじめインストールしておきます。
ビルド
まずはkernelを最小限の設定にしておき、そこから必要なものだけ付け加えるようにします。
こうしておくとコンパイルもかなり短くなるので良いです。
cd linux-4.15 make allnoconfig make menuconfig
menuconfigではオプションを選択する画面になりますが、ここでは
64-bit kernel General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support General setup ---> Configure standard kernel features ---> Enable support for printk Executable file formats / Emulations ---> Kernel support for ELF binaries Executable file formats / Emulations ---> Kernel support for scripts starting with #! Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs Device Drivers ---> Character devices ---> Enable TTY Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port File systems ---> Pseudo filesystems ---> /proc file system support File systems ---> Pseudo filesystems ---> sysfs file system support Kernel hacking ---> Compile-time checks and compiler options ---> Compile the kernel with debug info Kernel hacking ---> Compile-time checks and compiler options ---> Compile the kernel with debug info ---> Provide GDB scripts for kernel debugging
を有効にします。
それではコンパイルします。
make -j4
makeのjオプションで設定する数字は使えるCPUのコア数の2倍にしておくと良いです。
今回は2コアだったので4を設定しています。
これによってディレクトリ内にvmlinuxが生成されていればOKです。
QEMUの準備
ここからQEMUの準備をしたいのですが、QEMU上でkernelを動かすためには何かinitrdが必要です。
ここではinitrdとしてBusyBoxを使います。
あらかじめこの準備をしておきましょう。
initrdとは
initrdはkernelのブート時にルートファイルシステムが使用出来る前に読み込まれるプログラムのことです。
これについては以下のサイトが詳しいので参考にしてください。
Linux 初期 RAM ディスク (initrd) の概要
BusyBox
BusyBoxとはcdやlsなどのUnixコマンドを単一の実行ファイルに詰め込んだものです。
出来るだけ小さいの実行ファイルになるように設計されていて組み込みシステムによく使われるらしく、公式サイトにも
The Swiss Army Knife of Embedded Linux
と書かれています。
インストール
# linuxのディレクトリがある場所と同じところにbusyboxを作ることにします cd .. git clone git://busybox.net/busybox.git
ビルド
cd busybox make defconfig make menuconfig
ここでは以下を有効にします。
Busybox Settings ---> Build Options ---> Build BusyBox as a static binary (no shared libs)
そしてコンパイルをします。
make -j4 make install
次に初期化の設定のため、こちらのサイトをもとにinitとetc/fstabを作成してください。
そして以下を実行してください。
find . | cpio --quiet -H newc -o | gzip > ../initrd.img mv initrd.img ../linux-4.15
これでinitrdとなるbusyboxがlinuxのディレクトリ内に存在する状態となりました。
QEMU
qemuのインストールはapt-getを使えば簡単にできます
sudo apt-get install qemu-system-x86 qemu-kvm
実行
cd ../linux-4.15 qemu-system-x86_64 -s -nographic -kernel arch/x86_64/boot/bzImage -append console=ttyS0 -initrd initrd.img
これで無事にQEMUが立ち上がったと思います。
なおsオプションによってgdb接続待ち状態になっています。
GDBを繋ぐ
最後にGDBをkernelに接続します。 qemuとは別にターミナルを開いて操作してください。
設定
# {...}はPATHが正しくなるように適宜埋めてください。 echo "add-auto-load-safe-path {...}/linux-4.15/vmlinux-gdb.py" > ~/.gdbinit
実行
cd linux-4.15 gdb vmlinux (gdb) target remote :1234
これによりgdbをkernelに繋ぐことができたと思います。
あとは調べたい関数にbreak pointを仕掛けたりして遊んでください。
同じように環境構築をまとめてくれているakourry氏のブログの後半で、狙った関数を準備する簡易的な方法を紹介しているので読んでみてください。
あとがき
今回の環境構築には今まで知らなかった知識が必要でかなり苦戦しました。
まだまだ僕の理解は甘いので、ここに載せたサイト等を参考にしていただけると嬉しいです。
参考
Using kgdb, kdb and the kernel debugger internals — The Linux Kernel documentation
カーネルエクスプロイト入門 - Linuxカーネル解析の基礎 - - るくすの日記 ~ Out_Of_Range ~
Linux 初期 RAM ディスク (initrd) の概要
Linuxカーネルのテスト実行とデバッグ (1) :QEMU編 - Fixstars Tech Blog /proc/cpuinfo
Linuxカーネルのテスト実行とデバッグ (2) :GDB編 - Fixstars Tech Blog /proc/cpuinfo
Build and run minimal Linux / Busybox systems in Qemu · GitHub
Debugging kernel and modules via gdb — The Linux Kernel documentation
QEMU on Vagrant on Mac の環境構築メモ - Lを探す日常
JP3BGY氏からのたくさんのアドバイス