在有些時候,可能電腦啟動後沒有成功的彈出 Shell,在 Kernel 啟動過程中出現了某些錯誤,或是我們想要除錯的裝置在 QEMU 中沒有支援,因此我們沒有辦法通過模擬的方式進行除錯,這時候我們可以借助於 RS232 以及 KGDB,讓我們能夠輸出啟動訊息到其他電腦上,以及在實際機器上進行除錯。
在古老的 RS232 協定中,我們會看到 DTE 與 DCE 等等,DTE 全稱為 Data Terminal Equipment,而 DCE 全稱為 Data Communication Equipment。我們現在的任務是要將兩台電腦進行連接,電腦屬於 DTE 裝置,連接兩台 DTE,兩端都是 DTE <-> DTE,所以我們需要將一端的 TX 連接到 RX,RX 連接到 TX 才能夠實現互相溝通。
我們使用 RS232 將 Target Machine 與 Host 進行連結,Target Machine 為要 Debug 的目標桌機,而 Host 為一台跑 Fedora 的筆電,在 Target Machine 上需要 RS232 COM SERIAL PORT,把擴充卡插到主機板上,在主機板上會有一個 Serial Com 的腳位,如下圖所示

對於 Host 端,我們需要一條 USB to RS232(公)
RS232 常見的接頭為 9-pin 的 D-sub 接頭,又被稱為 DB9,重要的 pin 為 pin2 和 pin3。pin2 為 RXD,pin3 為 TXD。而所謂 NULL Modem 的線或是稱為 2-3 反接線指的是 A port 的 pin3 接到 B port 的 pin2,A port 的 pin2 接到 B port 的 pin3,讓兩邊電腦可以互相溝通,也就是 DTE to DTE。
在 Host 與 Target Machine 之間,接上 Null modem 後 Target Mahcine 與 Host 之間就可以相互溝通了。以下為一個 NULL modem 接頭的外觀
連接完畢後如下圖所示,Host 為筆電,Target Machine 為桌機

Target Machine 連接 com to RS232 公,接著 RS232 公連接 NULL modem,RS232 母 <-> RS232 母,另一端使用 RS232 公 <-> USB 連接到筆電,實現兩台電腦互聯並且能互相通訊。
接著我們需要啟用主機板上的 com,以 ASUS TUF B650M-PLUS 為例子,需要在 BIOS 中進行以下設定
在 Advanced -> Onboard Devices Configuration -> Serial Port Configuration

啟用 Serial Port,並修改設定,可以看到設定中有以下 4 個選項
I/O base 表示這一 UART (屬於 8250/16450/16550) 佔用哪一段 I/O port 實體記憶體區間。對於 x86 電腦有記憶體地址空間以及 I/O port 記憶體地址空間。I/O port 記憶體地址空間通過 in/out 指令進行存取,在傳統 PC 上 COM1 使用 I/O base 0x3F8, 而 COM2 使用 I/O base 0x2F8。
意思為 UART 的暫存器會被映射到從該記憶體地址開始的 8 bytes 範圍,例如 COM1 就是 0x3F8 到 0x3FF。之後驅動程式就直接對 0x3F8 使用 in/out 指令去做讀寫字元,設置 baudrate 等等操作。
至於 IRQ4, IRQ3,IRQ 為硬體中斷線。在老電腦上,裝置通過拉高 IRQ 線的電位,接著被 8259 等等晶片偵測到,PIC 對 CPU 發起中斷請求。CPU 收到之後跳轉到對應的 ISR。在 COM1 通常使用 IRQ4, COM2 使用 IRQ3。如果 UART 發生有新的資料可以讀取,或是 TX buffer 清空了可以再送資料進來,就會通過 IRQ4 等等讓 CPU 跳到給定的 ISR。
在現代已經沒有 8259,而是使用 I/O APIC/LAPIC 處理,不過在 BIOS 中還是使用傳統的名稱。
使用 dmesg 可以看到在系統早期啟動階段輸出以下訊息 (如果主機板有成功設定)
$ sudo dmesg | grep ttyS0
[ 0.794988] 00:04: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
表示 OS 將傳統的 COM1 對應到裝置節點 /dev/ttyS0。
我們可以在 grub 中,按下 e 編輯 kernel cmdline,我們嘗試將 Target Machine 的啟動訊息 (Boot Log) 通過 RS232 送到 host 上。
在 kernel cmdline 我們嘗試以下三種設定
console=ttyS0,115200n8
earlycon=uart,io,0x3f8,115200 ignore_loglevel loglevel=8
earlycon=uart,io,0x3f8,115200 keep_bootcon ignore_loglevel loglevel=8
關於這一些參數的詳細資訊,可以參考 kernel doc kernel-parameters.txt
以下為在 kernel cmdline 加上 console=ttyS0,115200n8 的輸出
[ OK ] Started plymouth-start.service - Show Plymouth Boot Screen.
[ OK ] Started systemd-ask-password-plymo…quests to Plymouth Directory Watch.
[ OK ] Reached target paths.target - Path Units.
[ OK ] Stopped systemd-vconsole-setup.service - Virtual Console Setup.
Stopping systemd-vconsole-setup.service - Virtual Console Setup...
Starting systemd-vconsole-setup.service - Virtual Console Setup...
...
[ OK ] Stopped target paths.target - Path Units.
[ OK ] Stopped target remote-fs.target - Remote File Systems.
[ OK ] Stopped target remote-fs-pre.targe…reparation for Remote File Systems.
[ OK ] Stopped target slices.target - Slice Units.
[ OK ] Stopped target sockets.target - Socket Units.
[ OK ] Stopped target sysinit.target - System Initialization.
[ OK ] Stopped target swap.target - Swaps.
Starting plymouth-switch-root.serv…e - Plymouth switch root service...
...
[ OK ] Listening on systemd-creds.socket - Credential Encryption/Decryption.
[ OK ] Listening on systemd-initctl.socke…- initctl Compatibility Named Pipe.
[ OK ] Listening on systemd-journald-audit.socket - Journal Audit Socket.
[ OK ] Listening on systemd-oomd.socket -… Out-Of-Memory (OOM) Killer Socket.
[ OK ] Listening on systemd-udevd-control.socket - udev Control Socket.
[ OK ] Listening on systemd-udevd-kernel.socket - udev Kernel Socket.
[ OK ] Listening on systemd-userdbd.socket - User Database Manager Socket.
...
[ OK ] Started crond.service - Command Scheduler.
Starting plymouth-quit-wait.servic…d until boot process finishes up...
Starting plymouth-quit.service - Terminate Plymouth Boot Screen...
p104p104
Fedora Linux 42 (KDE Plasma Desktop Edition)
Kernel 6.17.0-rc1+ on x86_64 (ttyS0)
apt login: fedora
Password:
Last login: Wed Oct 29 16:12:03 on ttyS0
% fedora@apt ~ ttttyttttytty
/dev/ttyS0
%
以下為在 kernel cmdline 加上 earlycon=uart,io,0x3f8,115200 ignore_loglevel loglevel=8 的輸出
[ 0.000000] printk: debug: ignoring loglevel setting.
[ 0.000000] NX (Execute Disable) protection: active
[ 0.000000] APIC: Static calls initialized
[ 0.000000] efi: EFI v2.9 by American Megatrends
[ 0.000000] efi: ACPI=0x74ce1000 ACPI 2.0=0x74ce1014 SMBIOS=0x794b9000 SMBIOS 3.0=0x794b8000 MEMATTR=0x67710698 ESRT=0x6aa07398 MOKvar=0x79507000 RNG=0x74cc6c18
[ 0.000000] random: crng init done
[ 0.000000] efi: Remove mem59: MMIO range=[0xe0000000-0xefffffff] (256MB) from e820 map
[ 0.000000] e820: remove [mem 0xe0000000-0xefffffff] reserved
[ 0.000000] efi: Remove mem60: MMIO range=[0xf7000000-0xfedfffff] (126MB) from e820 map
[ 0.000000] e820: remove [mem 0xf7000000-0xfedfffff] reserved
...
[ 0.114178] ACPI: DSDT 0x0000000074CB6000 00E321 (v02 ALASKA A M I 01072009 INTL 20230331)
[ 0.124277] ACPI: FACS 0x0000000076CC8000 000040
[ 0.129726] ACPI: SSDT 0x0000000074CD7000 00837C (v02 AMD Splinter 00000002 MSFT 04000000)
[ 0.139835] ACPI: SSDT 0x0000000074CD6000 000221 (v02 ALASKA CPUSSDT 01072009 AMI 01072009)
[ 0.149936] ACPI: FIDT 0x0000000074CCB000 00009C (v01 ALASKA A M I 01072009 AMI 00010013)
...
[ 1.462468] RCU Tasks Trace: Setting shift to 4 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=16.
[ 1.476087] NR_IRQS: 524544, nr_irqs: 1096, preallocated irqs: 16
[ 1.483508] rcu: srcu_init: Setting srcu_struct sizes based on contention.
[ 1.491798] kfence: initialized - using 2097152 bytes for 255 objects at 0x(____ptrval____)-0x(____ptrval____)
[ 1.503802] Console: colour dummy device 80x25
[ 1.509100] printk: legacy console [tty0] enabled
[ 1.514706] printk: legacy bootconsole [uart0] disabled
注意到最後的輸出為 bootconsole disable。
以下為在 kernel cmdline 加上 earlycon=uart,io,0x3f8,115200 keep_bootcon ignore_loglevel loglevel=8 的輸出
[ 0.000000] printk: debug: ignoring loglevel setting.
[ 0.000000] NX (Execute Disable) protection: active
[ 0.000000] APIC: Static calls initialized
[ 0.000000] efi: EFI v2.9 by American Megatrends
[ 0.000000] efi: ACPI=0x74ce1000 ACPI 2.0=0x74ce1014 SMBIOS=0x794b9000 SMBIOS 3.0=0x794b8000 MEMATTR=0x67702018 ESRT=0x6aa07398 MOKvar=0x79507000 RNG=0x74cc6c18
[ 0.000000] random: crng init done
[ 0.000000] efi: Remove mem59: MMIO range=[0xe0000000-0xefffffff] (256MB) from e820 map
[ 0.000000] e820: remove [mem 0xe0000000-0xefffffff] reserved
[ 0.000000] efi: Remove mem60: MMIO range=[0xf7000000-0xfedfffff] (126MB) from e820 map
[ 0.000000] e820: remove [mem 0xf7000000-0xfedfffff] reserved
[ 0.000000] efi: Not removing mem61: MMIO range=[0xfee00000-0xfee00fff] (4KB) from e820 map
...
[ 0.344152] ACPI: SSDT 0x0000000074BBB000 00047C (v02 AMD AMDWOV 00000001 INTL 20230331)
[ 0.354350] ACPI: SSDT 0x0000000074BBA000 00044E (v02 AMD AmdTable 00000001 INTL 20230331)
[ 0.364529] ACPI: Reserving FACP table memory at [mem 0x74cd5000-0x74cd5113]
[ 0.372962] ACPI: Reserving DSDT table memory at [mem 0x74cb6000-0x74cc4320]
[ 0.381396] ACPI: Reserving FACS table memory at [mem 0x76cc8000-0x76cc803f]
[ 0.389811] ACPI: Reserving SSDT table memory at [mem 0x74cd7000-0x74cdf37b]
[ 0.398226] ACPI: Reserving SSDT table memory at [mem 0x74cd6000-0x74cd6220]
[ 0.406650] ACPI: Reserving FIDT table memory at [mem 0x74ccb000-0x74ccb09b]
...
[ 1.430906] rcu: RCU calculated value of scheduler-enlistment delay is 100 jiffies.
[ 1.440025] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=16
[ 1.448154] RCU Tasks: Setting shift to 4 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=16.
[ 1.458674] RCU Tasks Rude: Setting shift to 4 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=16.
[ 1.469696] RCU Tasks Trace: Setting shift to 4 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=16.
[ 1.483301] NR_IRQS: 524544, nr_irqs: 1096, preallocated irqs: 16
[ 1.490783] rcu: srcu_init: Setting srcu_struct sizes based on contention.
...
[ 3.201268] pci 0000:0e:00.3: [1022:15c0] type 00 class 0x0c0330 PCIe Endpoint
[ 3.210157] pci 0000:0e:00.3: BAR 0 [mem 0xf6c00000-0xf6cfffff 64bit]
...
[ 15.787008] NET: Registered PF_QIPCRTR protocol family
[ 16.090232] Realtek Internal NBASE-T PHY r8169-0-700:00: attached PHY driver (mii_bus:phy_addr=r8169-0-700:00, irq=MAC)
[ 16.260987] RPC: Registered named UNIX socket transport module.
[ 16.262647] r8169 0000:07:00.0 eno1: Link is Down
[ 16.268005] RPC: Registered udp transport module.
[ 16.279407] RPC: Registered tcp transport module.
[ 16.284955] RPC: Registered tcp-with-tls transport module.
[ 16.291485] RPC: Registered tcp NFSv4.1 backchannel transport module.
[ 18.909861] r8169 0000:07:00.0 eno1: Link is Up - 1Gbps/Full - flow control off
如果將以上三種情況結合,我們可以得到以下
console=ttyS0,115200n8 earlycon=uart8250,io,0x3f8,115200 keep_bootcon ignore_loglevel loglevel=8
分成兩個部份來看,第一部份為 console,第二部份為 earlycon。
earlycon以下為節錄自 kernel parameters.txt 的內容
earlycon= [KNL,EARLY] Output early console device and options.
When used with no options, the early console is
determined by stdout-path property in device tree's
chosen node or the ACPI SPCR table if supported by
the platform.
cdns,<addr>[,options]
Start an early, polled-mode console on a Cadence
(xuartps) serial port at the specified address. Only
supported option is baud rate. If baud rate is not
specified, the serial port must already be setup and
configured.
uart[8250],io,<addr>[,options[,uartclk]]
uart[8250],mmio,<addr>[,options[,uartclk]]
uart[8250],mmio32,<addr>[,options[,uartclk]]
uart[8250],mmio32be,<addr>[,options[,uartclk]]
uart[8250],0x<addr>[,options]
Start an early, polled-mode console on the 8250/16550
UART at the specified I/O port or MMIO address.
MMIO inter-register address stride is either 8-bit
(mmio) or 32-bit (mmio32 or mmio32be).
If none of [io|mmio|mmio32|mmio32be], <addr> is assumed
to be equivalent to 'mmio'. 'options' are specified
in the same format described for "console=ttyS<n>"; if
unspecified, the h/w is not initialized. 'uartclk' is
the uart clock frequency; if unspecified, it is set
to 'BASE_BAUD' * 16.
...
keep_bootcon [KNL,EARLY]
Do not unregister boot console at start. This is only
useful for debugging when something happens in the window
between unregistering the boot console and initializing
the real console.
先看 earlycon。所謂的 early,指的是在還沒有 tty 子系統,沒有 /dev/ttyS0 裝置節點的時候,我們想要看到一些訊息,如 boot log 時,就會使用到 earlycon。earlycon 為一個低階,最小依賴的 polled console。不需要完整的驅動程式,也沒有中斷等等,而是通過 x86 的 in/out 指令向 UART 暫存器進行操作。
earlycon=uart,io,0x3f8,115200 keep_bootcon
earlycon=uart 表示使用 uart 的 handler,包含 uart 8250/16550A 等等io,0x3f8 表示這顆 UART 的 base I/O port 是 0x3f8115200 表示 baudratekeep_bootcon 阻止 disable bootconsole,保留並繼續輸出訊息設定 earlycon 後,我們就能夠通過 host 看到 target machine 的 early boot log,但是如果和 Target Machine 的 dmesg 訊息比較,我們會發現少了非常多訊息。這和有沒有使用 keep_bootcon 選項有關,如果使用 keep_bootcon 選項,則可以看到與 dmesg 相同,完整的開機訊息。
console接著是 console=ttyS0,115200n8,等待後面 serial driver 以及 tty 系統啟動之後,我們就能夠使用更高階的抽象去進行存取了。在 kernel 開機之後,會有一個 console 綁定到 /dev/ttyS0,也就是 UART 對應到的裝置節點。設定使用 115200 baudrate, 8 data bits,我們可以通過這個 console 和 target machine 進行互動,如下圖所示

可以看到這個 console 對應到 /dev/ttyS0。
首先釐清一下 debug core, kdb, kgdb, gdb 這四個彼此之間的關係,debug core 為 debug 核心機制,可以當作是一個後端提供服務。而 kdb 與 kgdb 為不同的前端,kdb 為內建在 kernel 中,直接在目標機 console 或是可以把這個 console 輸入到 Host 進行操作,而 kgdb 為實作 GDB Remote Protocol 的 stub,所謂 stub 指的是跑在 Target Machine 上的一小段程式碼,負責處理 GDB Remote Serial Protocol (RSP) 的封包。而跑在 Host 上面的 gdb 通過 RSP 發送指令,發送到 Target Machine 之後在 Target Machine 裡面的 stub 進行解析,藉此實現 Target Machine 與 Host 之間的互動。
有兩種方式可以使用 KDB 對 Kernel 進行除錯,第一種是在開機階段修改 kernel cmdline,加入 kgdboc=ttyS0,115200 kgdbwait,修改完成後按下 Ctrl + X 以此 kernel cmdline 開始執行,在 Host 便會收到來自於 Target Machine 的 kdb 界面,如下展示
Entering kdb (current=0xffff8881009d3080, pid 1) on processor 8 due to NonMaskable Interrupt @ 0xffffffff8152d7e4
[8]kdb> bt
Stack traceback for pid 1
0xffff8881009d3080 1 0 1 8 R 0xffff8881009d55e8 *swapper/0
CPU: 8 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.17.0-rc1+ #10 PREEMPT(lazy)
Hardware name: ASUS System Product Name/TUF GAMING B650M-PLUS, BIOS 2613 04/12/2024
Call Trace:
<TASK>
dump_stack_lvl+0x5d/0x80
kdb_show_stack+0x6b/0x80
kdb_bt1+0xbb/0x130
kdb_bt+0x377/0x3e0
kdb_parse+0x300/0x560
? srso_alias_return_thunk+0x5/0xfbef5
? kdb_read+0x43c/0x800
kdb_local.isra.0+0x453/0x840
...
kgdboc_probe+0x3a/0x50
platform_probe+0x39/0x70
really_probe+0xdb/0x340
? pm_runtime_barrier+0x55/0x90
? __pfx___device_attach_driver+0x10/0x10
__driver_probe_device+0x78/0x140
driver_probe_device+0x1f/0xa0
__device_attach_driver+0x89/0x110
? srso_alias_return_thunk+0x5/0xfbef5
bus_for_each_drv+0x8f/0xe0
__device_attach+0xb0/0x1c0
bus_probe_device+0x90/0xa0
device_add+0x4fb/0x6f0
platform_device_add+0xe4/0x240
? __pfx_init_kgdboc+0x10/0x10
init_kgdboc+0x41/0x70
do_one_initcall+0x58/0x300
do_initcalls+0x148/0x170
kernel_init_freeable+0xf6/0x140
? __pfx_kernel_init+0x10/0x10
kernel_init+0x1a/0x140
ret_from_fork+0x159/0x190
? __pfx_kernel_init+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
第二種是在開機之後,設定 kgdboc 通過 ttyS0,也就是 serial com。之後我們就能夠在 Host 上面 Debug Kernel,使用以下指令
$ echo ttyS0,115200 | sudo tee /sys/module/kgdboc/parameters/kgdboc
設定完成之前,如果我們輸入 echo h | sudo tee /proc/sysrq-trigger 會看到以下
[ 1726.913678] sysrq: HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) ...
設定完成之後,我們會發現 dmesg 中出現了 kgdb 相關訊息,以及再次輸入 echo h | sudo tee /proc/sysrq-trigger 會多出其他選項
[ 1908.082419] KGDB: Registered I/O driver kgdboc
[ 1960.191693] sysrq: HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) debug(g) ...
多出一個 g 的選項,使用 echo g | sudo tee /proc/sysrq-trigger 會陷入到 kdb 中,陷入到 kdb 中之後,我們可以看到在 host 上面多出一個 kdb 的互動界面,且 Target Machine 凍結,如下圖所示
[ 2296.966945] sysrq: DEBUG
Entering kdb (current=0xffff888178b43080, pid 70563) on processor 13 due to NonMaskable Interrupt @ 0xffffffff8152d7e4
[13]kdb>
輸入 go 之後可讓 Target Machine 恢復執行。
kdb 的指令比起 gdb 有許多不同,如恢復執行是使用 go 而非 c。中斷點在 gdb 中可以直接使用 b 下中斷點,而在 kdb 中有更多的選項,bl, be, bd, bp 等等。
Debug Kernel 除了上面直接使用 kdb 以外,也可以直接使用 gdb 完成,讓 gdb 與 kgdb 進行互動。先將帶有 Debug Symbol 的 image,如 vmlinux 複製到 host 上,接著 host 執行以下開始遠端 Debug
(gdb) gdb vmlinux
(gdb) source vmlinux-gdb.py
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
同樣在目標機器執行 echo g | sudo tee /proc/sysrq-trigger 之後就會陷入到 gdb 中,如下所示
(gdb) source vmlinux-gdb.py
(gdb) target remote /dev/ttyUSB0
Remote debugging using /dev/ttyUSB0
kgdb_breakpoint () at kernel/debug/debug_core.c:1214
1214 wmb(); /* Sync point after breakpoint */
(gdb) bt
#0 kgdb_breakpoint () at kernel/debug/debug_core.c:1214
#1 0xffffffff8128a7b0 in __handle_sysrq (key=<optimized out>, check_mask=check_mask@entry=false) at drivers/tty/sysrq.c:611
#2 0xffffffff81e74c19 in write_sysrq_trigger (file=<optimized out>, buf=<optimized out>, count=2, ppos=<optimized out>) at drivers/tty/sysrq.c:1222
#3 0xffffffff818aaf47 in pde_write (pde=0x0, file=<optimized out>, buf=<optimized out>, count=<optimized out>, ppos=<optimized out>) at fs/proc/inode.c:330
#4 proc_reg_write (file=0x67, buf=0x1 <error: Cannot access memory at address 0x1>, count=0, ppos=0x0) at fs/proc/inode.c:342
#5 0xffffffff817e207b in vfs_write (file=file@entry=0xffff88812207ac00, buf=buf@entry=0x7ffc4c5068a0 <error: Cannot access memory at address 0x7ffc4c5068a0>, count=count@entry=2,
pos=pos@entry=0xffffc900077bfe08) at fs/read_write.c:684
#6 0xffffffff817e2613 in ksys_write (fd=<optimized out>, buf=0x7ffc4c5068a0 <error: Cannot access memory at address 0x7ffc4c5068a0>, count=2) at fs/read_write.c:738
#7 0xffffffff8250a696 in do_syscall_x64 (regs=0xffffc900077bff58, nr=1) at arch/x86/entry/syscall_64.c:63
#8 do_syscall_64 (regs=0xffffc900077bff58, nr=1) at arch/x86/entry/syscall_64.c:94
#9 0xffffffff8100012f in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:121
#10 0x0000557485a844c0 in ?? ()
#11 0x0000557485a844c0 in ?? ()
#12 0x00007ffc4c5068a0 in ?? ()
#13 0x0000000000000002 in ?? ()
#14 0x00007ffc4c506700 in ?? ()
#15 0x0000000000000002 in ?? ()
#16 0x0000000000000202 in ?? ()
#17 0x0000000000000000 in ?? ()
接著我們可以執行 lx-symbols 後再次執行 bt 觀察結果
(gdb) lx-symbols
loading vmlinux
warning: File "/home/fedora/XXX/scripts/gdb/vmlinux-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load:/usr/lib/golang/src/runtime/runtime-gdb.py:/run/media/fedora/68D3-F08C/vmlinux-gdb.py".
scanning for modules in /home/fedora/XXX
loading @0xffffffffa1e5c000: /home/fedora/XXX/drivers/input/misc/uinput.ko
loading @0xffffffffa1e5b000: /home/fedora/XXX/sound/core/seq/snd-seq-dummy.ko
loading @0xffffffffa11ff000: /home/fedora/XXX/sound/core/snd-hrtimer.ko
loading @0xffffffffa1e13000: /home/fedora/XXX/net/sunrpc/sunrpc.ko
loading @0xffffffffa11fd000: /home/fedora/XXX/net/netfilter/nf_conntrack_netbios_ns.ko
loading @0xffffffffa05ff000: /home/fedora/XXX/net/netfilter/nf_conntrack_broadcast.ko
...
(gdb) bt
#0 kgdb_breakpoint () at kernel/debug/debug_core.c:1214
#1 0xffffffff8128a7b0 in __handle_sysrq (key=<optimized out>, check_mask=check_mask@entry=false) at drivers/tty/sysrq.c:611
#2 0xffffffff81e74c19 in write_sysrq_trigger (file=<optimized out>, buf=<optimized out>, count=2, ppos=<optimized out>) at drivers/tty/sysrq.c:1222
#3 0xffffffff818aaf47 in pde_write (pde=0x0 <__pfx_uinput_misc_init>, file=<optimized out>, buf=<optimized out>, count=<optimized out>, ppos=<optimized out>) at fs/proc/inode.c:330
#4 proc_reg_write (file=0x67, buf=0x1 <__pfx_uinput_misc_init+1> <error: Cannot access memory at address 0x1>, count=0, ppos=0x0 <__pfx_uinput_misc_init>) at fs/proc/inode.c:342
#5 0xffffffff817e207b in vfs_write (file=file@entry=0xffff88812207ac00, buf=buf@entry=0x7ffc4c5068a0 <error: Cannot access memory at address 0x7ffc4c5068a0>, count=count@entry=2,
pos=pos@entry=0xffffc900077bfe08) at fs/read_write.c:684
#6 0xffffffff817e2613 in ksys_write (fd=<optimized out>, buf=0x7ffc4c5068a0 <error: Cannot access memory at address 0x7ffc4c5068a0>, count=2) at fs/read_write.c:738
#7 0xffffffff8250a696 in do_syscall_x64 (regs=0xffffc900077bff58, nr=1) at arch/x86/entry/syscall_64.c:63
#8 do_syscall_64 (regs=0xffffc900077bff58, nr=1) at arch/x86/entry/syscall_64.c:94
#9 0xffffffff8100012f in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:121
#10 0x0000557485a844c0 in ?? ()
#11 0x0000557485a844c0 in ?? ()
#12 0x00007ffc4c5068a0 in ?? ()
#13 0x0000000000000002 in __pfx_uinput_misc_init ()
#14 0x00007ffc4c506700 in ?? ()
#15 0x0000000000000002 in __pfx_uinput_misc_init ()
#16 0x0000000000000202 in ?? ()
#17 0x0000000000000000 in ?? ()
在除錯時,常常會使用輸出 backtrace 的功能,而以下為 kdb 以及 kgdb 針對 hw_access_profiling_start 的輸出
以下為使用 gdb 連線到 kgdb 的輸出
執行 lx-symbols 之前
#0 hw_access_profiling_start () at arch/x86/mm/ibs.c:137
#1 0xffffffff8250d3de in set_debugreg (val=0, reg=7)
at ./arch/x86/include/asm/paravirt.h:138
#2 local_db_restore (dr7=0) at ./arch/x86/include/asm/debugreg.h:161
#3 exc_nmi (regs=0xfffffe000000def8) at arch/x86/kernel/nmi.c:596
#4 0xffffffff81001f2d in asm_exc_nmi () at arch/x86/entry/entry_64.S:1406
#5 0xffffffff83b1c460 in acpi_idle_driver ()
#6 0x0000000000000003 in ?? ()
#7 0xffffffff83b1c460 in acpi_idle_driver ()
#8 0xffffffff83b1c5b0 in acpi_idle_driver ()
#9 0xffff88810286d4cc in ?? ()
#10 0xffff88810286d4cc in ?? ()
#11 0x0000000000000000 in ?? ()
執行 lx-symbols 之後
(gdb) bt
#0 hw_access_profiling_start (period=period@entry=10000) at arch/x86/mm/ibs.c:136
#1 0xffffffff825106da in irqentry_nmi_exit (regs=regs@entry=0xfffffe00001aaef8, irq_state=..., irq_state@entry=...) at kernel/entry/common.c:246
#2 0xffffffff8250d3de in exc_nmi (regs=0xfffffe00001aaef8) at arch/x86/kernel/nmi.c:594
#3 0xffffffff81001f2d in asm_exc_nmi () at arch/x86/entry/entry_64.S:1406
#4 0x0000000000000000 in ?? ()
lx-symbols 可以讓我們重新載入 Linux Kernel 的 symbols 以及目前已經載入核心模組的 symbols。
以下為 kdb 的輸出
...
kdb_local.isra.0+0x453/0x840
kdb_main_loop+0xfb/0x240
kdb_stub+0x1a0/0x3e0
kgdb_cpu_enter+0x46a/0xae0
kgdb_handle_exception+0xb1/0xf0
__kgdb_notify+0x2f/0x80
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
kgdb_ll_trap+0x49/0x70
do_int3+0x2f/0x80
exc_int3+0x8f/0xd0
asm_exc_int3+0x39/0x40
RIP: 0010:hw_access_profiling_start+0x0/0x50
Code: 8b 4c 24 14 48 8b 54 24 08 48 8b 04 24 e9 07 ff ff ff 0f 1f 84 00 00 00 00 00 90 90 90 5
RSP: 0018:fffffe0000347ed0 EFLAGS: 00000083
RAX: ffff889ffb98d000 RBX: ffff889ffb98d000 RCX: 0000000000000004
RDX: 0000000000000001 RSI: 0000000000000000 RDI: 0000000000002710
RBP: fffffe0000347ef8 R08: 000000000006b2dd R09: ffff889f8012cbe0
R10: 0000001cc2d26849 R11: 0000000000000000 R12: 0000000000000000
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
? hw_access_profiling_start+0x1/0x50
exc_nmi+0xce/0x1a0
end_repeat_nmi+0xf/0x53
RIP: 0010:io_idle+0x3/0x30
Code: 8b 00 a8 08 75 0c e8 3c d3 ff ff 90 fa 0f 1f 44 00 00 e9 7b 17 bf fe 90 90 90 90 90 90 5
RSP: 0018:ffffc900001ffe10 EFLAGS: 00000093
RAX: 0000000000000000 RBX: ffff8881027d4c98 RCX: 0000000000000040
RDX: 0000000000000414 RSI: 000000000000000e RDI: 0000000000000414
RBP: 0000000000000002 R08: ffff8881027d4c00 R09: ffffffff83b1c460
R10: 00000011e523a059 R11: 0000000000000000 R12: ffffffff83b1c548
R13: ffffffff83b1c460 R14: 0000000000000002 R15: 0000000000000000
? io_idle+0x3/0x30
? io_idle+0x3/0x30
</NMI>
<TASK>
acpi_idle_do_entry+0x22/0x50
acpi_idle_enter+0xab/0x180
? srso_alias_return_thunk+0x5/0xfbef5
? ct_kernel_exit.isra.0+0x78/0x100
cpuidle_enter_state+0x84/0x660
cpuidle_enter+0x31/0x50
cpuidle_idle_call+0xf5/0x160
do_idle+0x78/0xd0
cpu_startup_entry+0x29/0x30
start_secondary+0x126/0x170
common_startup_64+0x13e/0x141
</TASK>
kdb 的訊息可以看到會分成兩層 call stack 展開,首先是 Debugger 本身的路徑,像是 do_int3 之類的,接著後面是 NMI 的 context,接著後面的 TASK 為被 NMI 打斷的 TASK,kdb 看到的是進入到 debugger,以及 NMI context 和 TASK 的 context。而 gdb 看到的訊息是進入到 int 3 之後的 backtrace。