内核调试器kdb与kgdb模式相互切换

/ 16评 / 2

自从kdb集成到内核后,调试内核就多了一种选择,可以选择仅需要目标机,自己调试自己的kdb,
或选择需要额外一台机器来辅助调试的kgdb

引人kdb,也引发一个新功能需求,即调试器之间的切换选择,如从kdb模式切换到kgdb模式或者反过来。

而且由于kdb和kgdb并不互斥,可在系统中同时存在,而kdb不需要额外的机器来辅助调试这一便利性,
所以在kgdb和kdb同时被使能的情况下,系统调试器缺省设置为kdb模式,这时kgdb用户使用gdb尝试
去连接内核的话,由于缺省设置为kdb模式,为了方便用户使用,还要求系统可以自动从kdb切换到kgdb
模式的功能(当gdb连接的时候)。

1: kdb和kgdb的实现架构

这里我们要搞的是为什么可以互换。这得从kdb和kgdb的实现原理来说了。

先看看现在目前内核调试框架图(Cutting from Jason's PPT of linuxcon2010)

其中
Debug Core:
1:Generic debug API
2:Handles exceptions
3:Syncs/saves/restores CPUs
4:API for SW/HW breakpoints

Arch specific KGDB:
1:Interface to read/write registers
2:Arch specific exceptions and watch dogs
3:Single stepping interface

GDB Stub:
Speaks the gdb serial protocol

Polled I/O Driver (kgdboc / kgdboe / kgdbou / kgdb_8250):
1:Uses the console UART driver to multiplex a single serial line
2:Another host's gdb connects to this port

由框架图可知,在debug core框架下,已经将KGDB和KDB模式抽象成它的"stub",
所以要切换模式,只要切换"stub"处理函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
关门放代码, linux/kernel/debug/debug_core.c:  kgdb_cpu_enter()
{
...
while (1) {
/* 根据当前设定模式,选择stub */
 if (dbg_kdb_mode) {
            kgdb_connected = 1;
            error = kdb_stub(ks);
            if (error == -1)
                continue;
            kgdb_connected = 0;
        } else {
            error = gdb_serial_stub(ks);
        }
 
/* stub函数的返回值是否是切换 stub的指令 */
if (error == DBG_PASS_EVENT) {
            dbg_kdb_mode = !dbg_kdb_mode;
        }
}
...
}

2: kdb模式与kgdb模式互换实现

我们需要实现如下功能:
1: kgdb模式到kdb模式切换,即通过增加 gdb 命令来切换
2: kdb模式到kgdb模式切换,即通过增加 kdb 命令来切换
3: kdb刚启动(缺省状态时),kdb检测gdb的连接,自动切换到kgdb模式

2.1: kgdb模式到kdb模式切换,即通过增加 gdb 命令来切换

这个实现不复杂,可在gdb的远程协议里增加一个子协议表明要切换到kdb模式。
目前那个子协议暂定为 “$3#33“, 即当kgdb stub收到“$3#33“后,返回
DBG_PASS_EVENT 到 debug_core.

1
2
3
4
5
6
7
8
9
10
11
12
13
继续关门放代码, linux/kernel/debug/gdbstub.c: gdb_serial_stub()
{
...
while (1) {
switch (gdb协议发送的数据) {
        case '3': /* Escape into back into kdb */
            if (remcom_in_buffer[1] == '\0') {
                gdb_cmd_detachkill(ks);
                return DBG_PASS_EVENT;
            }
}
...
}

2.2 : kdb模式到kgdb模式切换,即通过增加 kdb 命令来切换

这个也不复杂,即增加一个‘kgdb‘命令来实现,

1: 先注册'kgdb' 命令到kdb, 其命令对于处理函数为kdb_kgdb(), 即用户
在kdb里敲入'kgdb' 命令后,立刻调用kdb_kgdb()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
放代码 linux/kernel/debug/kdb/kdb_main.c:
void kdb_inittab(void)
{
 kdb_register_repeat("kgdb", kdb_kgdb, "",
      "Enter kgdb mode", 0, KDB_REPEAT_NONE);
}
 
static int kdb_kgdb(int argc, const char **argv)
{
    return KDB_CMD_KGDB;
}
 
linux/kernel/debug/kdb/kdb_debugger.c:
int kdb_stub()
{
  if (error == KDB_CMD_KGDB) {
        if (KDB_STATE(DOING_KGDB) || KDB_STATE(DOING_KGDB2)) {
    /*
     * This inteface glue which allows kdb to transition in into
     * the gdb stub.  In order to do this the '?' or '' gdb serial
     * packet response is processed here.  And then control is
     * passed to the gdbstub.
     */
            if (KDB_STATE(DOING_KGDB))
                gdbstub_state(ks, "?");
            else
                gdbstub_state(ks, "");
            KDB_STATE_CLEAR(DOING_KGDB);
            KDB_STATE_CLEAR(DOING_KGDB2);
        }
        return DBG_PASS_EVENT;
    }
}

2.3: kdb刚启动(缺省状态时),kdb检测gdb的连接,自动切换到kgdb模式

这里的主要点在于如何检测gdb的连接。

这得关注gdb的远程调试协议,而且还得兼容新老版本的gdb。
由于各个系统支持程度不一样,gdb在连接远程调试时候,一般会先询问下远程调试系统支持哪些指令,
这个动作是通过协议"qSupported"+"一些子命令"来实现的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
放gdb的代码
gdb/gdb/remote.c: remote_start_remote()
{
if (interrupt_on_connect)
    send_interrupt_sequence ();
 
  /* The first packet we send to the target is the optional "supported
     packets" request.  If the target can answer this, it will tell us
     which later probes to skip.  */
remote_query_supported ();
...
}
 
void remote_query_supported ()
{
 /* The packet support flags are handled differently for this packet
     than for most others.  We treat an error, a disabled packet, and
     an empty response identically: any features which must be reported
     to be used will be automatically disabled.  An empty buffer
     accomplishes this, since that is also the representation for a list
     containing no features.  */
 
  rs->buf[0] = 0;
  if (remote_protocol_packets[PACKET_qSupported].support != PACKET_DISABLE)
    {
      char *q = NULL;
      struct cleanup *old_chain = make_cleanup (free_current_contents, &q);
 
      if (rs->extended)
    q = remote_query_supported_append (q, "multiprocess+");
 
      if (remote_support_xml)
    q = remote_query_supported_append (q, remote_support_xml);
 
      q = remote_query_supported_append (q, "qRelocInsn+");
 
      q = reconcat (q, "qSupported:", q, (char *) NULL);
      putpkt (q);
}

从上面的代码中可以得知,qSupported的命令是根据gdb的配置和目标target的配置而决定的,
比如说在i386上, 这个第一个发出去的包可能是:

1
2
3
1) $qSupported:xmlRegisters=i386;qRelocInsn+#25
2) $qSupported:qRelocInsn+#9a
3) $qSupported#37

因此我们需要在kdb的输入数据流里检测以$qSupported打头的包,如果检测到了,则说明是gdb
在尝试连接,则需要切换到kgdb模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关门放kdb处理代码: linux/kernel/debug/kdb/kdb_io.c:  char* kdb_read()
{
 
 /* Special escape to kgdb */
            if (lastchar - buffer >= 5 &&
                strcmp(lastchar - 5, "$?#3f") == 0) {
                strcpy(buffer, "kgdb");
                KDB_STATE_SET(DOING_KGDB);
                return buffer;
            }
            if (lastchar - buffer >= 11 &&
                strcmp(lastchar - 11, "$qSupported") == 0) {
                strcpy(buffer, "kgdb");
                KDB_STATE_SET(DOING_KGDB2);
                return buffer;
            }
}

其中上面的strcpy(buffer, "kgdb");代码表示 当检测到上面的事件时,模拟一个
kgdb命令,这样就可以走2.2里面的实现路程来完成工作了。。

16条回应:“内核调试器kdb与kgdb模式相互切换”

  1. cuic139说道:

    你好,我现在kgdb的调试环境已经搭建起来了,不过没太搞懂怎样在kgdb里切换成kdb模式,代码部分写的是3,你的文章里是$3#33,不是太明白这是什么意思,请教了

    [回复]

    DDD 回复:

    @cuic139 @cuic139

    $3#33是gdb与kgdb进行通信的远程协议的封包格式。

    RSP 是一种基于 ASCII 码的协议,以 gdb 发送命令,目标端(在这里是 kgdb)返回执行
    结果或信息的方式来进行的,而命令和回复在传输过程中又是封装在一个包(packet)里面
    的.每个包以一个’$’开始,接着是实际数据,数据以一个’#’结束,后面跟着两位十六进制数
    字用作校验和($packet-data#checksum).校验和需要对 256 取模,因为只有两位。

    http://www.kgdb.info/kgdb/understand_kgdb/kgdb_source_chapter_three/

    [回复]

    cuic139 回复:

    @DDD 按照这样,应该是在kgdb的控制台里输入$3#33就可以进入kdb了吧?
    不过我现在输入之后提示:
    Program received signal SIGTRAP, Trace/breakpoint trap.
    kgdb_breakpoint (key=103) at kernel/debug/debug_core.c:960
    960 in kernel/debug/debug_core.c
    (gdb) $3#33
    Undefined command: “$3”. Try “help”.

    编译选项是按照您说的确认过了的:
    CONFIG_DEBUG_KERNEL=Y
    CONFIG_KGDB=Y
    CONFIG_KGDB_SERIAL_CONSOLE=Y
    Sysrq-g must be used to break in initially.
    Selecting this will automatically set:
    CONFIG_CONSOLE_POLL=N
    CONFIG_MAGIC_SYSRQ=Y
    CONFIG_KGDB_KDB=Y
    Optional other configuration settings:
    CONFIG_FRAME_POINTER=Y
    CONFIG_DEBUG_RODATA=N
    CONFIG_KALLSYMS=Y
    CONFIG_KDB_KEYBOARD
    CONFIG_KGDB_TESTS
    只有这个CONFIG_CONSOLE_POLL=N 现在是不一样的,不知道是否有影响
    现在想进kdb但是进不去。。。
    非常感谢

    [回复]

    DDD 回复:

    @cuic139 在gdb端,应该使用packet管理模式…

    在连接kgdb成功后,使用
    (gdb) maintenance packet
    (gdb) 3

    “maintenance packet”命令比较生僻,所以….

    还有一种方法就是直接修改kgdb里面的核心变量值:

    在连接kgdb成功后,
    (gdb) set dbg_kdb_mode=1
    在退出kgdb,然后就是kdb模式了。

    在进入kdb模式成功后,可以使用
    echo g >/proc/sysrq-trigger
    来触发验证下.

  2. DDD说道:

    CONFIG_CONSOLE_POLL=N
    这个应该是选 Y的,

    可能是http://www.kgdb.info/kgdb/kdb_introduce/
    这个文章里面应该笔误了,我现在手上没有环境,回头等我验证下. :-)

    [回复]

    cuic139 回复:

    @DDD 您说的对,是选Y的,是当kgdb和使用串口都为Y时自动选上的,我今天又研究了一下文档重新试验了一下,问题已经解决了,非常感谢您,O(∩_∩)O~

    [回复]

  3. cuic139说道:

    在还要请教个问题,哈哈,新内核里的kdb命令,id这个命令貌似给去掉了!!,您关注过这方面的问题吗?
    我查看了2.6.39和3.1.5的源代码,都是这样,是有什么可以替代的命令吗…如果只想要一台机器调试的话,kdb的id指令还是对分析问题很有帮助的

    [回复]

    cuic139 回复:

    @cuic139 我在kdb的官方网站上看到有3.1的patch,patch里面有id这个命令,之前的版本像2.6.39则都没有相应的patch,这种情况莫非要手动patch。。。?

    [回复]

    DDD 回复:

    @cuic139 目前内核版本里是没有做这个支持的。
    In the merged kdb & kgdb, presently there is no disassembler (known as the “id” command)

    我不清楚kdb是否有特殊都patch,方便都话,你可以发给我下。(kdb的官网打不开)

    [回复]

  4. cuic139说道:

    In the merged kdb & kgdb, presently there is no disassembler (known as the “id” command)
    这个您是在哪里看到的?官方文档里好像没有,我是看了代码实现才知道不支持的;

    怎么发给您呢?不知道您的邮箱,您给我发个邮件吧,[email protected] ,O(∩_∩)O

    [回复]

    cuic139 回复:

    @cuic139 那个补丁根据里面的readme是用在3.0的,虽然写的是文件名字写的是3.1,我打了之后编译现在还没有过呢,
    struct lock_manager_operations 里面没有成员变量 lm_compare_owner和lm_notify,代码里没有定义。。。不知道是不是官方的bug,~~~~(>_<)~~~~

    [回复]

    cuic139 回复:

    @cuic139 从james911那里看到了您的邮箱,已经给您发过去了

    [回复]

    DDD 回复:

    @cuic139 @cuic139
    这里有描述 集成到内核的KDB和原生KDB的一些差别
    https://kgdb.wiki.kernel.org/articles/k/d/b/KDB_FAQ_9859.html

    收到你邮件,我找空去看看。

    非常感谢. :-)

    [回复]

    cuic139 回复:

    @DDD 我手动patch到2.6.*的内核上,有一些patch和编译错误,修正后现在id反汇编能用了,
    开心死了,哈哈

    [回复]

  5. cuic139说道:

    哈哈,我又来了,还需要请教一下,我现在是用虚拟机装的linux,无论我是采用kgdb远程调试,还是kdb本机调试,只要已进入debug模式,cpu的占用率就满了,之前的时候虽然cpu占用高不过还有反应,今天进了kdb后虚拟机就莫有反应了,所以想求教一下DDD大神有莫有遇到过类似的问题?我kgdboc哪里已经设置kbd了

    [回复]

    DDD 回复:

    @cuic139 @cuic139
    呵呵,欢迎欢迎。
    虚拟机是比较慢,进入调试模式的时候,虚拟机一直在模拟CPU空转循环。
    不知道你机器性能怎么样,在我这台式机这,cpu一般在50%左右(双核,qemu)。

    [回复]

cuic139进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据