用 kGDB 调试 Linux 内核

简介

这个文档记录了用kGDB调试Linux内核的全过程,都是在前人工做基础上的一些总结。如下操做都是基于特定板子来进行,可是大部分都能应用于其余平台。html

要使用KGDB来调试内核,首先须要修改config配置文件,打开相应的配置,配置内核启动参数,甚至修改串口驱动添加poll支持,而后才能经过串口远程调试内核。linux

配置内核

基本配置

在内核配置文件:.config中,须要打开以下选项git

CONFIG_KGDB 加入KGDB支持
CONFIG_KGDB_SERIAL_CONSOLE 使KGDB经过串口与主机通讯(打开这个选项,默认会打开CONFIG_CONSOLE_POLL和CONFIG_MAGIC_SYSRQ)
CONFIG_KGDB_KDB 加入KDB支持
CONFIG_DEBUG_KERNEL 包含驱动调试信息
CONFIG_DEBUG_INFO 使内核包含基本调试信息
CONFIG_DEBUG_RODATA=n 关闭这个,能在只读区域设置断点

可选选项

CONFIG_PANIC_TIMEOUT=5
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=1
CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=1
CONFIG_S3C2410_WATCHDOG_ATBOOT=0
CONFIG_FRAME_POINTER -- 使KDB可以打印更多的栈信息
CONFIG_KALLSYMS -- 加入符号信息
CONFIG_KDB_KEYBOARD -- 若是是经过目标版的键盘与KDB通讯,须要把这个打开,且键盘不能是USB接口
CONFIG_KGDB_TESTS

启动参数

打开相应的选项后,须要配置kernel启动参数,使KGDB和内核可以找到正确的通讯接口。若是是使用串口,则须要配置以下选项:web

console=ttySAC3,115200 kgdboc=ttySAC3,115200

若是须要调试内核的启动过程,则须要在kgdboc后面加入kgdbwait。多线程

在其余板子上,若使用以太网口来和KGDB进行通讯,则要把kgdboc换成kgdboe(kgdb 
over ethernet))。函数

配置完后,就能够正常编译,而后把内核下载到目标板上面。spa

串口驱动修改

若是在内核启动的过程当中出现以下错误提示:线程

kgdb: Unregistered I/O driver, debugger disabled.

则须要根据这一部分,修改串口驱动程序,若能正常进入kgdb,则忽略该节,直接进入下一节使用KGDB。debug

在drivers/tty/serial/kgdboc.c中的configure_kgdboc函数,会经过tty_find_polling_driver(cptr, 
&tty_line)来找寻内核启动参数中指定的串口驱动。而后经过kgdboc_get_char()和kgdboc_put_char()来和主机串口正常通讯。调试

能够看到在config配置文件的CONFIG_CONSOLE_POLL就是使能串口与kgdboc的接口。若是 tty_find_polling_driver没有找到对应的串口通讯接口,则会调用kernel/debug/debug_core.c中的 kgdb_unregister_io_module进行错误处理。

有的板子的串口驱动并无加入对kgdboc通讯的支持,例如Samsung的串口驱动须要在drivers/tty/serial/samsung.c中手动添加。 
添加与kgdboc通讯的接口,只需添加一个发送函数和接收函数,而后在驱动操做结构体中加入对应的函数就能够了。具体的PATCH以下:

drivers/tty/serial/samsung.c | 22 ++++++++++++++++++++++
1 files changed, 22 insertions(+), 0 deletions(-)

diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c
index ff6a4f8..5ceb7d7 100755
--- a/drivers/tty/serial/samsung.c
+++ b/drivers/tty/serial/samsung.c
@@ -893,7 +893,29 @@ static struct console s3c24xx_serial_console;
#define S3C24XX_SERIAL_CONSOLE NULL
#endif

+#ifdef CONFIG_CONSOLE_POLL
+static void s3c24xx_serial_poll_put_char(struct uart_port *port, unsigned char c)
+{
+    while (!(rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE))
+       ;
+
+     wr_regl(port, S3C2410_UTXH, c);
+}
+
+static int s3c24xx_serial_poll_get_char(struct uart_port *port)
+{
+    while (!(rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_RXDR))
+       ;
+
+    return rd_regl(port, S3C2410_URXH);
+}
+#endif
+
static struct uart_ops s3c24xx_serial_ops = {
+#ifdef CONFIG_CONSOLE_POLL
+    .poll_get_char = s3c24xx_serial_poll_get_char,
+    .poll_put_char = s3c24xx_serial_poll_put_char,
+#endif
.pm = s3c24xx_serial_pm,
.tx_empty = s3c24xx_serial_tx_empty,
.get_mctrl = s3c24xx_serial_get_mctrl,
--
1.7.5.4

加入这个patch,从新编译内核,以后就能正常进入kgdb

gdb远程调试

若是在内核启动参数中加入了kgdbwait,则内核会在完成基本的初始化以后,停留在kgdb的调试陷阱中,等待主机的gdb的远程链接。

因为大部分的板子只有一个调试串口,因此你须要把以前与串口通讯的minicom退出来,而后在内核源码的目录下,执行如下命令:

$ arm-linux-gnueabi-gcc vmlinux
(gdb) target remote /dev/ttyUSB0
(gdb) set detach-on-fork on
(gdb) b panic()
(gdb) c

固然,你也能够agent-proxy来复用一个串口,经过虚拟出两个TCP端口。这时候,gdb就须要用target 
remote命令链接kgdb,例如:

(gdb) target remote localhost:5551

agent-proxy能够经过这里获取:git://git.kernel.org/pub/scm/utils/kernel/kgdb/agent-proxy.git,具体用法,请看该repo下的README。

在用gdb来调试内核的时候,因为内核在初始化的时候,会建立不少子线程。而默认gdb会接管全部的线程,若是你从一个线程切换到另一个线程,gdb会立刻把原先的线程暂停。可是这样很容易致使kernel死掉,因此须要设置一下gdb。 
通常用gdb进行多线程调试,须要注意两个参数:follow-fork-mode和detach-on-fork。

  • detach-on-fork参数,指示GDB在fork以后是否断开(detach)某个进程的调试,或者都交由GDB控制:

    set detach-on-fork [on|off]

    • on: 断开调试follow-fork-mode指定的进程。
    • off: gdb将控制父进程和子进程。
  • follow-fork-mode指定的进程将被调试,另外一个进程置于暂停(suspended)状态。follow-fork-mode的用法为:

    set follow-fork-mode [parent|child]

    • parent: fork以后继续调试父进程,子进程不受影响。
    • child: fork以后调试子进程,父进程不受影响。

REFERENCE

相关文章
相关标签/搜索