---
# System prepended metadata

title: 'Linux Kernel Debug on Real Machine, KGDB'

---

# Linux Kernel Debug on Real Machine, KGDB
## 摘要
在有些時候，可能電腦啟動後沒有成功的彈出 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 的腳位，如下圖所示

<img src="https://hackmd.io/_uploads/ryrguXkk-e.png" width=300>

[source](https://www.asrockrack.com/support/faq.tw.asp?id=31)

對於 Host 端，我們需要一條 USB to RS232(公)
<img src="https://hackmd.io/_uploads/S1I8tmJkZg.png" width=300>
[source](https://shop.cpu.com.tw/product/38016/info)

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 接頭的外觀
<img src="https://hackmd.io/_uploads/HJmbGE1k-g.png" width=300>
[source](https://cablematic.com/en/products/null-modem-adapter-db9-mh-NM065/)

連接完畢後如下圖所示，Host 為筆電，Target Machine 為桌機

<img src="https://hackmd.io/_uploads/H1Tsyl7kZe.jpg" width=400>


Target Machine 連接 com to RS232 公，接著 RS232 公連接 NULL modem，RS232 母 <-> RS232 母，另一端使用 RS232 公 <-> USB 連接到筆電，實現兩台電腦互聯並且能互相通訊。

## 修改 Target 的 BIOS
接著我們需要啟用主機板上的 com，以 ASUS TUF B650M-PLUS 為例子，需要在 BIOS 中進行以下設定

在 Advanced -> Onboard Devices Configuration -> Serial Port Configuration

![image](https://hackmd.io/_uploads/H1tjxWfkZx.png)


啟用 Serial Port，並修改設定，可以看到設定中有以下 4 個選項
- I/O base 0x3F8, IRQ=4
- I/O base 0x2F8, IRQ=3
- I/O base 0x3E8, IRQ=4
- I/O base 0x2E8, IRQ=3

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` 可以看到在系統早期啟動階段輸出以下訊息 (如果主機板有成功設定)
```shell
$ 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`。


## 接收來自於 Target Machine 的啟動訊息

我們可以在 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](https://www.kernel.org/doc/Documentation/admin-guide/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 是 0x3f8
- `115200` 表示 baudrate
- `keep_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 進行互動，如下圖所示

![image](https://hackmd.io/_uploads/rkeRe8JkWx.png)

可以看到這個 console 對應到 `/dev/ttyS0`。

## 使用 kdb/kgdb 進行 Debug
### Debug core, kdb, kgdb, gdb
首先釐清一下 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
有兩種方式可以使用 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，使用以下指令
```shell
$ echo ttyS0,115200 | sudo tee /sys/module/kgdboc/parameters/kgdboc
```
設定完成之前，如果我們輸入 `echo h | sudo tee /proc/sysrq-trigger` 會看到以下
```shell
[ 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` 會多出其他選項
```shell
[ 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` 等等。

### KGDB
Debug Kernel 除了上面直接使用 kdb 以外，也可以直接使用 gdb 完成，讓 gdb 與 kgdb 進行互動。先將帶有 Debug Symbol 的 image，如 vmlinux 複製到 host 上，接著 host 執行以下開始遠端 Debug
```shell
(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 ?? ()
```

### 比較 KDB 與 KGDB 的輸出
在除錯時，常常會使用輸出 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。

## 參考
- [Kernel parameter: kgdboc](https://www.kernel.org/pub/linux/kernel/people/jwessel/kgdb/ch03s03.html)
- [Using kgdb, kdb and the kernel debugger internals](https://www.kernel.org/doc/html/v5.0/dev-tools/kgdb.html)
- [The kernel’s command-line parameters](https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html)