QEMU上で動作しているUbuntu 16.04のカーネルとデバイスドライバのモジュールをGDBでデバッグします。この記事ではxfsモジュールをロードしてデバッグします。
Table of Contents
1 カーネルのデバッグシンボル取得
デバッグシンボル用のリポジトリを追加してから、カーネルのデバッグシンボルを取得します。
$ sudo apt install -y linux-image-$(uname -r)-dbgsym $ file /usr/lib/debug/boot/vmlinux-4.4.0-31-generic /usr/lib/debug/boot/vmlinux-4.4.0-31-generic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=c66bb1efd9220f7386509bee83bcc80a1aa21744, not stripped
2 カーネルのソースコード取得
ソースコード用のリポジトリを追加してから、カーネルのソースコードを取得します。
$ mkdir ~/linux $ cd ~/linux $ apt source linux $ ls linux-4.4.0 linux_4.4.0-31.50.diff.gz linux_4.4.0-31.50.dsc linux_4.4.0.orig.tar.gz $ cd ..
カーネルのデバッグシンボルが参照するパスにソースコードを格納します。
$ sudo mkdir -p /build/linux-dcxD3m/ $ sudo mv linux/linux-4.4.0 /build/linux-dcxD3m/
3 ルートファイルシステムの作成
ここではlivecd-rootfsを利用します。以下のスクリプトを実行すると2時間ほどでxubuntu-desktopをインストールしたルートファイルシステムのrootfs.extが作成されます。
#!/bin/sh -ex PASSWORD="password" PACKAGE="xubuntu-desktop linux-modules-$(uname -r)" SIZE=4096 # rootfs size: 1MByte x 4096 = 4GByte hostname=`hostname` TIMEZONE="Asia/Tokyo" # Please change your timezone. # Intall packages for creating rootfs. sudo apt install -y livecd-rootfs systemd-container xorriso # Create work directory mkdir rootfs cd rootfs # Prepare live-build cp -a /usr/share/livecd-rootfs/live-build/auto . cp -a /usr/share/livecd-rootfs/live-build/ubuntu-core . PROJECT=ubuntu-core lb config sed -i 's/precise/xenial/g' config/bootstrap # Create filesystem dd if=/dev/zero of=rootfs.ext4 bs=1M count=${SIZE} mkfs.ext4 rootfs.ext4 mkdir chroot sudo mount -t ext4 -o loop,rw rootfs.ext4 chroot # Run live-build and create minimul rootfs sudo PROJECT=ubuntu-core lb build # Additional rootfs configuration sudo cp /etc/apt/sources.list chroot/etc/apt/ sudo cp /etc/default/keyboard chroot/etc/default/ sudo su -c "echo ${hostname} > chroot/etc/hostname" yes "${PASSWORD}" | sudo systemd-nspawn -D chroot passwd # Install expect. sudo apt install -y expect # Run command on systemd_nspawn via expect. systemd_nspawn_via_expect() { expect -c " set timeout -1 spawn sudo systemd-nspawn -b -D chroot expect \"${hostname} login:\" send -- \"root\n\" expect \"Password:\" send -- \"${PASSWORD}\n\" expect \"root@${hostname}:~#\" send -- \"${1} || (apt install -f && ${1})\n\" expect \"root@${hostname}:~#\" send -- \"poweroff\n\" expect \"${PS1}\" " # BUG: The systemd-nspawn by root user cannot be poweroffed. sleep 10 while machinectl status --no-pager chroot > /dev/null 2>&1; do echo 3 | sudo tee /proc/sys/vm/drop_caches sync sudo machinectl terminate chroot sleep 10 done } # Update apt database. systemd_nspawn_via_expect "apt update -y" systemd_nspawn_via_expect "apt install -y apt-utils" # Install console-setup which needs interactive setting. expect -c " set timeout -1 spawn sudo systemd-nspawn -D chroot apt install -y console-setup expect \"Encoding to use on the console:\" send -- \"27\n\" expect \"Character set to support:\" send -- \"20\n\" expect \"${PS1}\" " # Install tzdata which needs interactive setting. systemd_nspawn_via_expect "ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime" systemd_nspawn_via_expect "echo ${TIMEZONE} > /etc/timezone" systemd_nspawn_via_expect "DEBIAN_FRONTEND=noninteractive apt install -y tzdata" systemd_nspawn_via_expect "dpkg-reconfigure --frontend noninteractive tzdata" # Install packages. [ -n "${PACKAGE}" ] && systemd_nspawn_via_expect "apt install -y ${PACKAGE}" # Unmount filesystem sudo umount chroot
4 QEMUのGDB-stubでUbuntu 16.04のカーネルをデバッグする
QEMUとGDBをインストールします
$ sudo apt install -y qemu gdb64
Ubuntu 16.04のカーネルはroot権限でしかアクセスできません。 QEMUでアクセスできるように、カーネルを作業ディレクトリにコピーします。
$ sudo cp /boot/vmlinuz-4.4.0-31-generic . $ sudo chown hiroom2:hiroom2 vmlinuz-4.4.0-31-generic
xfsでフォーマットしたファイルを作成します。
$ dd if=/dev/zero of=xfs.img bs=1M count=4K $ sudo apt install -y xfsprogs $ mkfs.xfs xfs.img
GDB用のスクリプトファイルを作成します。
$ cat <<EOF > gdb.x set architecture i386:x86-64 target remote localhost:10000 symbol-file /usr/lib/debug/boot/vmlinux-4.4.0-31-generic c EOF
QEMUを起動します。 64bitのバイナリを用いる場合、start_kernelが呼ばれる前のカーネル初期化処理のデバッグが上手く動作しません(命令セットが切り替わるのに GDB-stubが対応できてないようです)。よって、-Sオプションを用いてGDBとの同期を取ることもできません。
$ qemu-system-x86_64 \ --kernel vmlinuz-4.4.0-31-generic \ --initrd /boot/initrd.img-4.4.0-31-generic \ --hda rootfs/rootfs.ext4 \ --hdb xfs.img \ --append "console=tty1,115200 console=ttyS0,115200 root=/dev/sda rw" \ --nographic \ -m 512M \ --gdb tcp::10000
start_kernelが実行されたら(コンソールにprintkの文字列が表示されたら)、GDBを起動します。
$ gdb64 -x gdb.x
QEMU上のUbuntu 16.04でxfsモジュールをロードします。ここでは簡単にロードされたアドレスを/proc/modulesで確認します。
# modprobe xfs # grep "^xfs" /proc/modules xfs 970752 0 - Live 0xffffffffc05c4000
GDBでxfsモジュールのシンボルファイルを追加します。 mountコマンドの延長で呼ばれるxfs_fs_mountでbreakします。
(gdb) add-symbol-file /usr/lib/debug/lib/modules/4.4.0-31-generic/kernel/fs/xfs/xfs.ko 0xffffffffc05c4000 (gdb) b xfs_fs_mount (gdb) c
QEMU上のUbuntu 16.04でmoutコマンドを実行します。
# mount -t xfs /dev/sdb /mnt
xfs_fs_mountでbreakしました。
Breakpoint 1, xfs_fs_mount (fs_type=0xffffffffc0673980, flags=0, dev_name=0xffff88001a207010 "/dev/sdb", data=0x0 <irq_stack_union>) at /build/linux-dcxD3m/linux-4.4.0/fs/xfs/xfs_super.c:1611 warning: Source file is more recent than executable. 1611 { (gdb) l 1606 xfs_fs_mount( 1607 struct file_system_type *fs_type, 1608 int flags, 1609 const char *dev_name, 1610 void *data) 1611 { 1612 return mount_bdev(fs_type, flags, dev_name, data, xfs_fs_fill_super); 1613 } 1614 1615 static long (gdb)