# 使用 gdb 進行 Linux 核心除錯
以下兩個部份由於書寫時間關係,我用的 linux 版本是不同的,但可以用相同的方式做到。
## 使用 buildroot 編譯 Linux 核心
首先要編譯 linux 核心,我之前嘗試在 ubuntu 24.04 去編譯,但是失敗了,還沒找到原因,看到別人的[筆記](https://sammyne.github.io/buildroot-quickstart/#%E5%B0%86%E8%87%AA%E5%B7%B1%E7%9A%84%E5%BA%94%E7%94%A8%E5%8A%A0%E5%85%A5-buildroot-%E6%89%93%E5%8C%85%E7%9A%84%E7%B3%BB%E7%BB%9F)使用 ubuntu 20.04 的環境,所以去嘗試了一下就成功了。
### 參考資料
[linux-kernel-debugging](https://github.com/Rhydon1337/linux-kernel-debugging)
[buildroot 快速入門](https://sammyne.github.io/buildroot-quickstart/#%E5%B0%86%E8%87%AA%E5%B7%B1%E7%9A%84%E5%BA%94%E7%94%A8%E5%8A%A0%E5%85%A5-buildroot-%E6%89%93%E5%8C%85%E7%9A%84%E7%B3%BB%E7%BB%9F)
### 操作步驟
首先安裝一些依賴:
```bash
sudo apt install file libncurses5-dev g++ wget cpio unzip rsync bc git tree make
```
然後去獲得 buildroot 的 repository:
```bash
git clone https://github.com/buildroot/buildroot.git
```
接下來進到 `buildroot` 目錄底下去進行操作。首先依照對應的架構去看要使用什麼要的預設 config。可以用以下命令得到有哪些選項是可以用的:
```bash
make list-defconfigs
```
我現在要用 qemu 去跑 x86-64 的 Linux 核心,所以這樣去設定:
```bash
make qemu_x86_64_defconfig
```
然後這樣開啟選單去做細部調整:
```bash
make menuconfig
```
我這裡只有調核心版本,我想要編譯 v4.20 的核心,於是有幾的地方要調:
- Toolchain -> Custom kernel headers series 調成 4.20.x
- Kernel -> Kernel version 調成 4.20
接下來正常來說可以直接編譯了,但是會發現他的 hash 值並沒有在白名單中,於是要先去找到他的 hash 值是什麼:
```bash
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.20.tar.xz
sha256sum linux-4.20.tar.xz
ad0823183522e743972382df0aa08fb5ae3077f662b125f1e599b0b2aaa12438 linux-4.20.tar.xz
```
然後去修改 `buildroot/linux/linux.hash`,加上這行:
```diff
+ sha256 ad0823183522e743972382df0aa08fb5ae3077f662b125f1e599b0b2aaa12438 linux-4.20.tar.xz
```
接下來進行核心的設定:
```bash
make linux-menuconfig
```
這裡我調的是以下幾個:
- Kernel hacking -> Kernel debugging 選起來
- Kernel hacking -> Compile-time checks ... -> Compile the kernel with debug info 選起來
然後退出儲存設定,接下來就可以進行編譯了,`-j` 可以指定要用多少個處理器核去編譯,這裡我用上這台機器所有的核:
```bash
make -j `nproc`
```
編譯過程中會發生錯誤,要依照下面兩個 patch 去做修正:
https://www.spinics.net/lists/kernel/msg3797871.html
https://lore.kernel.org/lkml/20200124181811.4780-1-hjl.tools@gmail.com/
### 開機
編譯完後 `buildroot/output/build/linux-4.20` 這個目錄是編譯選項都設定好的原始碼,編譯結果也在其中,等下會在這個目錄下做操作,另外可以在 `buildroot/output/images` 拿到 `rootfs.ext2` 和 `bzImage`,把他們移到 `linux-4.20` 這個目錄。
建立一個這樣的 `run.sh`:
```bash
#!/bin/sh
qemu-system-x86_64 \
-kernel bzImage \
-drive file=rootfs.ext2,if=virtio,format=raw \
-append "console=ttyS0 root=/dev/vda rw" \
-net nic,model=virtio \
-net user \
-nographic
```
然後就可以用它開機了。
如果要修改 `rootfs.ext2` 裡面的內容的話可以使用 `mount` 去做掛載:
```
sudo mount -o loop rootfs.ext2 /mnt
```
這樣就把 `/mnt` 掛載到 `rootfs.ext2` 上了,當修改完後再 `umount`:
```
sudo umount /mnt
```
## 進行除錯
接下來我使用的是 6.11.0 版本的 linux。建立一個這樣的啟動腳本 `debug.sh`:
```bash
#!/bin/sh
qemu-system-x86_64 \
-kernel bzImage \
-s -S \
-drive file=rootfs.ext2,if=virtio,format=raw \
-append "console=ttyS0 root=/dev/vda rw nokaslr" \
-net nic,model=virtio \
-net user \
-nographic
```
這樣就會在 `localhost:1234` 開啟 gdb server,並且在起始點停下來。另外使用 `nokaslr` 選項關閉核心的 ASLR,讓虛擬記憶體的 base address 是固定的。用一個終端機執行這樣的命令來啟動核心:
```bash
./debug.sh
```
另一個終端機用這樣的命令來啟動 gdb:
```bash
gdb vmlinux
```
然後在 gdb 裡面去連上 gdb server:
```bash
(gdb) target remote :1234
```
我這裡有用 gdb 插件 [pwndbg](https://github.com/pwndbg/pwndbg),所以以下稍微顯示不同。可以在 `common_startup_64` 下個斷點,然後繼續執行:
```bash
pwndbg> b common_startup_64
pwndbg> c
```
然後就會撞到了:
```
► 0xffffffff8102b5c8 <secondary_startup_64+24> mov edx, cpu_debug_store+32 EDX => 0x1020 (cpu_debug_store+32)
0xffffffff8102b5cd <secondary_startup_64+29> or edx, 0x40
0xffffffff8102b5d0 <secondary_startup_64+32> mov rcx, cr4
0xffffffff8102b5d3 <secondary_startup_64+35> and ecx, edx
0xffffffff8102b5d5 <secondary_startup_64+37> bts ecx, 4
0xffffffff8102b5d9 <secondary_startup_64+41> mov cr4, rcx
0xffffffff8102b5dc <secondary_startup_64+44> bts ecx, 7
0xffffffff8102b5e0 <secondary_startup_64+48> mov cr4, rcx
0xffffffff8102b5e3 <secondary_startup_64+51> mov ecx, dword ptr [rip + 0xe08a17] ECX, [smpboot_control]
0xffffffff8102b5e9 <secondary_startup_64+57> test ecx, 0x80000000
0xffffffff8102b5ef <secondary_startup_64+63> jne 0xffffffff8102b5f9 <secondary_startup_64+73>
──────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /home/rota1001/linux2025/final-project/linux-6.11-build/arch/x86/kernel/head_64.S:208
203 * From the SDM:
204 * "If CR4.PGE is changing from 0 to 1, there were no global TLB
205 * entries before the execution; if CR4.PGE is changing from 1 to 0,
206 * there will be no global TLB entries after the execution."
207 */
► 208 movl $(X86_CR4_PAE | X86_CR4_LA57), %edx
209 #ifdef CONFIG_X86_MCE
210 /*
211 * Preserve CR4.MCE if the kernel will enable #MC support.
212 * Clearing MCE may fault in some environments (that also force #MC
213 * support). Any machine check that occurs before #MC support is fully
──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x1e03f58 ◂— 0
... ↓ 7 skipped
────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
► 0 0xffffffff8102b5c8 secondary_startup_64+24
1 0x0 fixed_percpu_data
****
```