• 用 kGDB 调试 Linux 内核


    简介

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

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

    配置内核

    基本配置

    在内核配置文件:.config中,需要打开如下选项

    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和内核能够找到正确的通信接口。如果是使用串口,则需要配置如下选项:

    console=ttySAC3,115200 kgdboc=ttySAC3,115200

    如果需要调试内核的启动过程,则需要在kgdboc后面加入kgdbwait。

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

    配置完后,就可以正常编译,然后把内核下载到目标板上面。

    串口驱动修改

    如果在内核启动的过程中出现如下错误提示:

    kgdb: Unregistered I/O driver, debugger disabled.

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

    在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

  • 相关阅读:
    MySQL存储引擎--MyISAM与InnoDB区别
    PHP笔试面试题精选(一)
    优化 sql 语句的一般步骤
    EXPLAIN 小秘密
    Git stash 使用的小技巧
    git remote用法
    git subtree用法
    nodejs-------windows安装配置
    MongoDB -----windows上安装服务
    layer父子页面交互
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6432717.html
Copyright © 2020-2023  润新知