# 使用 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 **** ```