QEMUでLinuxカーネルをGDBでデバッグする

QEMUの起動

以下の2つのオプションを使う
オプション説明
-sTCP 1234番でGDBのリモートコクションをオープンします
-SGDBにアタッチされるまでゲストの実行を待機します。カーネルの起動後にアタッチしても問題ない場合、不要です。

Linuxの起動オプション

QEMUで起動されるLinuxカーネルのコマンドラインオプションにnokaslrを付与する。通常、GRUBなどのブートローダーに指定する。このオプションは、カーネルの関数の配置アドレスのランダム化を抑止する。このオプションを指定しない場合、カーネルのデバッグ情報に記載されているアドレスと、実際のアドレスがことなり、GDBの操作が期待どおりに行われない。

デバッグ情報の入手

ターゲットのカーネルに対応するデバッグ情報ファイルを取得する。

Debianの場合の例

sudo apt install linux-image-5.10.0-22-amd64-dbg
あるいは、APT repositoryから直接取得する。
wget http://ftp.debian.org/debian/pool/main/l/linux/linux-image-5.10.0-22-amd64-dbg_5.10.178-3_amd64.deb
sudo dpkg -i linux-image-5.10.0-22-amd64-dbg_5.10.178-3_amd64.deb

自分でビルドしたカーネルの場合

カーネルのビルドディレクトリにvmlinuxがあるのでそれを使う。ただし、ビルド時、以下の点に留意
  • 以下のCONFIGを有効化
    • CONFIG_DEBUG_INFO_DWARF5、または、CONFIG_DEBUG_INFO_DWARF4
    • CONFIG_GDB_SCRIPTS
    • CONFIG_FRAME_POINTER
  • 以下のCONFIGを無効化
    • CONFIG_DEBUG_INFO_REDUCED
参考: https://docs.kernel.org/dev-tools/gdb-kernel-debugging.html

GDBの起動

デバッグ情報付きのvmlinuxを引数にしてGDBを起動する
gdb /usr/lib/debug/vmlinux-5.10.0-22-amd64
GDB起動後、以下のコマンドを実行する
(gdb) target remote [QEMU起動マシンのアドレス]:1234

グローバル変数の表示例

(gdb) p jiffies
$1 = 4295462764

ブレークポイントの例

do_sys_openにブレークポイントを仕掛けて、ゲストOSでファイルを開く操作をすると以下のようにブレークする
(gdb) b do_sys_open
Breakpoint 2 at 0xffffffff812d6ff0: do_sys_open. (11 locations)
(gdb) c
Continuing.

Breakpoint 2.4, do_sys_open (mode=0, flags=557056, filename=0x7f153b8cabe7 "/etc/ld.so.cache", dfd=-100) at fs/open.c:1201
1201    fs/open.c: No such file or directory.
(gdb) bt
#0  do_sys_open (mode=0, flags=557056, filename=0x7f153b8cabe7 "/etc/ld.so.cache", dfd=-100) at fs/open.c:1201
#1  __do_sys_openat (mode=0, flags=557056, filename=0x7f153b8cabe7 "/etc/ld.so.cache", dfd=-100) at fs/open.c:1218
#2  __se_sys_openat (mode=0, flags=524288, filename=139729170115559, dfd=4294967196) at fs/open.c:1213
#3  __x64_sys_openat (regs=<optimized out>) at fs/open.c:1213
#4  0xffffffff818f7e00 in do_syscall_64 (nr=<optimized out>, regs=0xffffc9000027bf58) at arch/x86/entry/common.c:46
#5  0xffffffff81a000a9 in entry_SYSCALL_64 () at /build/linux-ts3hOX/linux-5.10.178/arch/x86/entry/entry_64.S:125

実行中のプロセスのtask_structの表示例

上記のブレーク例は、lsを実行したときのものである。このときのプロセスが確かにlsであることと、そのプロセスIDを確認する。
(gdb) set $curr = (struct task_struct **)($gs_base + (void *)&current_task)
(gdb) p $curr
$2 = (struct task_struct **) 0xffff88803d01fbc0
(gdb) p $curr->comm
$3 = "ls\000h\000\000\000)\000\000\000\000\000\000\000"
(gdb) p $curr->pid
$4 = 552
上記について補足する。実行中のプロセスのtask_struct構造体へのポインタは、CPUごとの変数領域の中にあり、グローバル変数current_taskの値は、その領域におけるオフセットである。また、CPUごとの変数領域の開始位置はGSセグメントレジスタに格納されている。そのため、両者を加算したアドレス位置に実行中のプロセスに関するtask_struct領域へのポインタが格納されている。

参考

0 件のコメント: