Raspberry pi KGDB using FT2232H
===
KGDB顧名思義就是Kernel GDB,可以除錯Linux kernel。想要試著實做的原因是之前在COSCUP 2021聽了如何使用KGDB trace linux kernel的conference。一直想來實做看看,這篇紀錄我的實作過程。
[參考 COSCUP 2021 KGDB conference](https://hackmd.io/@coscup/Bk8qLaw0d/%2F%40coscup%2FrJu8I6PCO)
本文章使用FT2232H連接Raspberry pi的JTAG,並藉由OpenOCD和KGDB trace Linux kernel開機過程。以function kernel_init()為本次trace的範例。[參考](https://elixir.bootlin.com/linux/latest/source/init/main.c#L1480)
```Clike=
static int __ref kernel_init(void *unused)
{
int ret;
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
free_initmem();
mark_readonly();
/*
* Kernel mappings are now finalized - update the userspace page-table
* to finalize PTI.
*/
pti_finalize();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
do_sysctl_args();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
```
:::info
kernel_init讀取Rootfs下的init( ("/sbin/init") 或 ("/etc/init") 或 ("/bin/init") ),PID為1。
:::
---
# Environment
Host: Ubuntu x64
Board: Raspberry pi 4b
JTAG: FT2232H-56Q(FT2232H Mini Module) [FTDI DATASHEET](https://ftdichip.com/wp-content/uploads/2020/07/DS_FT2232H_Mini_Module.pdf)
Linux kernel [Raspberry pi linux kernel GITHUB](https://github.com/raspberrypi/linux)
UBOOT [uboot GITHUB](https://github.com/u-boot/u-boot)
Toolchain: gcc-aarch64-linux-gnu
GDB: gdb-multiarch
OpenOCD [OpenOCD GITHUB](https://github.com/openocd-org/openocd)
Screen Console 工具
杜邦線
:::info
安裝toolchain
```bash=
sudo apt install -y gcc-aarch64-linux-gnu
```
安裝GDB
```bash=
sudo apt install -y gdb-multiarch
```
安裝Screen
```bash=
sudo apt install -y screen
```
:::
:::info
OpenOCD不要使用APT套件庫的,請使用GITHUB上的版本。APT套件庫的版本過舊。
編譯
```bash=
./configure --enable-ftdi
make -j6
sudo make install
```
:::
---
# KGDB Trace 流程:
1. Raspberry pi上電
2. 透過Console操作,讓他停在Uboot
3. HOST端開啟OpenOCD,啟動GDB Server
4. HOST端啟動GDB Client連接OpenOCD
5. GDB Client設定中斷點,並讓Raspberry pi繼續開機
6. Raspberry pi會停在中斷點。
:::info
一共會用到三個Terminator。
* Raspberry pi Console
* OpenOCD
* GDB Client
:::
---
# Linux kernel 和 Uboot compile
請參考我之前Raspberry pi 4 Build System文章,這裡我就不多概述了。
[參考 Raspberry pi 4 Build System (64bits)](https://hackmd.io/p_eQ-5QdRE-NpweHBE4_Eg?view)
比較需要注意的有兩個地方。
1. 過去在Compile linux kernel 並沒有將KGDB相關功能加入Linux kernel,因此需要加入KGDB設定。
2. 將Raspberry pi開機參數寫入config.txt
### Linux kernel 加入KGDB設定
請依照以下步驟將KGDB設定加入設定檔後再編譯。
1. 在Linux kernel底下找到Raspberry pi的設定檔案
```bash=
cd ${linux_kernel 目錄}
find . -name bcm2711_defconfig
```
會出現以下兩行。第一行給32位元(ARM)用的設定檔,本文章使用第二個給64位元(ARM64)的設定檔案。
```bash=
./arch/arm/configs/bcm2711_defconfig
./arch/arm64/configs/bcm2711_defconfig
```
2. 在檔案之中(./arch/arm64/configs/bcm2711_defconfig)加入以下設定
```text=
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_FRAME_POINTER=y
```
3. 設定,並編譯
```bash=
make -j6 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
make -j6 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- all
```
:::info
GDB會用到原始為壓縮的Linux kernel -- vmlinux,所以編譯選擇all,將所有東西都編譯。
:::
### 將Raspberry pi開機參數寫入config.txt
* config.txt
```text=
kernel=u-boot.bin
arm_64bit=1
enable_uart=1
# Disable pull downs
gpio=22-27=np
# Enable jtag pins (i.e. GPIO22-GPIO27)
enable_jtag_gpio=1
```
將設定檔放到Raspberry pi第一分區(FAT32開機磁區)
---
# JTAG on raspberry pi
要說到Raspberry pi的JTAG Pin位置,首先我們得先知道Raspberry pi使用的SOC(ARM architecture)JTAG Pin,是哪幾隻。
如下圖,ARM JTAG主要使用TMS(測試模式狀態PIN),TDO(輸出),TDI(輸入),TCLK(時脈)傳輸訊號,VCC,GND供電,nRESET重置腳位。[ARM Cortex A JTAG interface](https://www.keil.com/support/man/docs/ulinkpro/ulinkpro_hw_if_jtag10.htm)

接著在Raspberry PI上找到一模一樣功能的腳位(GPIO22 ~ GPIO27)。
[Raspberry pi 4b SPEC](https://datasheets.raspberrypi.org/rpi4/raspberry-pi-4-datasheet.pdf)

實際將GPIO對應到PIN header[參考 COSCUP 2021 KGDB conference](https://hackmd.io/@coscup/Bk8qLaw0d/%2F%40coscup%2FrJu8I6PCO)。

[參考 JTAG - Joint Test Action Group](https://pinout.xyz/pinout/jtag#)

---
# FT2232H JTAG
FT2232H 是一個由FTDI設計,USB對多用途的IC。支援功能如下[SPEC](https://ftdichip.com/wp-content/uploads/2020/07/DS_FT2232H_Mini_Module.pdf):
```text=
Single chip USB to dual channel UART (RS232,
RS422 or RS485).
* Single chip USB to dual channel FIFO.
* Single chip USB to dual channel JTAG.
* Single chip USB to dual channel SPI.
* Single chip USB to dual channel I2C.
* Single chip USB to dual channel Bit-Bang.
* Single chip USB to dual combination of any of
above interfaces.
* Single chip USB to Fast Serial Optic Interface.
* Single chip USB to CPU target interface (as
memory), double and independent.
* Single chip USB to Host Bus Emulation (as
CPU).
* PDA to USB data transfer
* USB Smart Card Readers
* USB Instrumentation
* USB Industrial Control
* USB MP3 Player Interface
* USB FLASH Card Reader / Writers
* Set Top Box PC - USB interface
* USB Digital Camera Interface
* USB Bar Code Readers
```
腳位定義如下,可以看出GPIO16~GPIO19,是JTAG的Pin。

---
# FT2232H 與 Raspberry pi 接線
[參考 Baremetal Raspberry Pi 4 with FT2232H](https://www.vinnie.work/blog/2021-04-02-ft2232h-rpi4/)

:::info
為了操作Raspberry pi,有將兩者UART對接,以便可透過Console操作Raspberry pi。
:::
---
# 設定OpenOCD
1. 建立OpenOCD設定檔案(minimodule.cfg和raspi4.cfg)
* minimodule.cfg:
```text=
#
# FTDI MiniModule
#
# http://www.ftdichip.com/Support/Documents/DataSheets/Modules/DS_FT2232H_Mini_Module.pdf
#
echo "WARNING!"
echo "This file was not tested with real interface, it is based on code in ft2232.c."
echo "Please report your experience with this file to openocd-devel mailing list,"
echo "so it could be marked as working or fixed."
adapter driver ftdi
ftdi_device_desc "FT2232H MiniModule"
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0018 0x05fb
ftdi_layout_signal nSRST -data 0x0020
```
* raspi4.cfg:
```text=
set _CHIPNAME bcm2711
set _DAP_TAPID 0x4ba00477
adapter_khz 1000
transport select jtag
reset_config trst_and_srst
telnet_port 4444
# create tap
jtag newtap auto0 tap -irlen 4 -expected-id $_DAP_TAPID
# create dap
dap create auto0.dap -chain-position auto0.tap
set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000}
set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000}
set _cores 4
set _TARGETNAME $_CHIPNAME.a72
set _CTINAME $_CHIPNAME.cti
set _smp_command ""
for {set _core 0} {$_core < $_cores} { incr _core} {
cti create $_CTINAME.$_core -dap auto0.dap -ap-num 0 -ctibase [lindex $CTIBASE $_core]
set _command "target create ${_TARGETNAME}.$_core aarch64 \
-dap auto0.dap -dbgbase [lindex $DBGBASE $_core] \
-coreid $_core -cti $_CTINAME.$_core"
if {$_core != 0} {
set _smp_command "$_smp_command $_TARGETNAME.$_core"
} else {
set _smp_command "target smp $_TARGETNAME.$_core"
}
eval $_command
}
eval $_smp_command
targets $_TARGETNAME.0
```
# 開始Trace囉
#### 開啟Raspberry pi Console
1. 先將FT2232H接上電腦,並開啟Console:
會出現兩個USB Interface,選擇第二個

```bash=
sudo screen /dev/ttyUSB1 115200
```
2. Raspberry PI上電後,持續按下鍵盤上的Enter(持續點放,不是持續按著):
直到進入Uboot命令列,如下圖

#### 開啟OpenOCD 和 GDB server
開啟另一個終端機,輸入以下命令(記得先切到minimodule.cfg,raspi4.cfg存放的目錄):
```bash=
sudo openocd -f minimodule.cfg -c "set USE_SMP 1" -f raspi4.cfg -c "reset_config trst_only"
```

* 以上步驟我們將Console,OpenOCD,GDB Server準備好了。
#### 開啟OpenOCD 和 GDB client
1. 開啟另一個終端機,切換到剛剛Build Linux kernel的目錄,並輸入:
```bash=
gdb-multiarch -q vmlinux
```

2. 連接GDB Server:
```bash=
target extended-remote :3333
```

:::info
這時會發現Raspberry pi的Console停住了,無法輸入文字或是控制。這是因為Raspberry pi被OpenOCD freeze住了。
:::
3. 下中斷點:
```bash=
hbreak kernel_init
```

4. 讓Raspberry PI繼續執行:
```bash=
c
```

:::info
c (continue),解除freeze
:::
#### Raspberry PI console:
輸入開機參數,載入Linux kernel,並開機:
```bash=
setenv bootargs 8250.nr_uarts=1 console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw
fatload mmc 0:1 ${kernel_addr_r} Image
booti ${kernel_addr_r} - ${fdt_addr}
```

#### 發現GDB Client停在我們下的中斷點:

* 使用backtrace可以追蹤function caller:

:::info
到這裡可以使用GDB指令去追蹤kernel_init了
:::
:::info
GDB詳細使用方式可參考這篇文章[透過 GDB 進行遠端除錯](https://hackmd.io/@sysprog/gdb-example)
:::