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. kgdb+gdbを使う
  2. qemu+gdbを使う
  3. kdbを使う

私は初めに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となるbusyboxlinuxディレクトリ内に存在する状態となりました。

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氏からのたくさんのアドバイス