kgdb源代码分析(2.6.27)第四章-命令实现

/ 6评 / 0

kgdb源代码分析(2.6.27)

本文由penny编写, 你可以通过发邮件到[email protected]联系penny,
也可以直接评论本文章与penny交流.

0. 概述
1. 异步通知
2. 准备工作
3.gdb 远程串行协议(GDB remote serial protocol)
4. 命令实现:
4.1 断点.
4.2 continue 和 step
5 初始化时机.

4. 命令实现:

kgdb 实现了众多的命令,这里不一一进行说明,有些实现十分简单,有些就需要仔细看
看,不过其实很多工作 gdb 端已经完成了,到 kgdb 这边的都是很直接的,比如用户试图在
gdb中打印一个局部变量的值,gdb 通过和 kgdb 得到当前进程寄存器的信息,最终计算出要
读的内存地址和长度,kgdb 只要应 gdb 的要求提供相应的信息和设置相应的地址就够了,所
以 gdb 是调试里面的主角.不过我们这里关心的还是 kgdb, gdb 的工作就忽略了.

我们不妨用一个 gdb 用户的角度来看看,大多数情况下我们只要:设置断点,让程序继续,
单步调试,打印内存这些功能.对应 gdb 发给 kgdb 的命令并不就是'Z','c','s','m'命令,一般一
个 gdb 的用户的命令都对上十几二十条 kgdb 命令的发送和接收.

4.1 断点:

先从数据结构入手,kgdb用一个数组kgdb_break[]来描述软件断点(我们这里暂且只讨论软断点):

1
2
3
4
5
6
7
8
(kernel/kgdb.c)
107 /*
108 * Holds information about breakpoints in a kernel. These breakpoints are
109 * added and removed by gdb.
110 */
111 static struct kgdb_bkpt kgdb_break[KGDB_MAX_BREAKPOINTS] = {
112 [0 ... KGDB_MAX_BREAKPOINTS-1] = { .state = BP_UNDEFINED }
113 };

每个元素为kgdb_bkpt定义在

1
2
3
4
5
6
7
(include/linux/kgdb.h)
98 struct kgdb_bkpt {
99 unsigned long bpt_addr;
100 unsigned char saved_instr[BREAK_INSTR_SIZE];
101 enum kgdb_bptype type;
102 enum kgdb_bpstate state;
103 };

结构体里面的每个成员都很好理解,bpt_addr就是断点设置的地址,saved_instr[]放的
是被断点指令所替换的真实指令.大家也知道一般调试器的实质就是在断点对于的内存地址
中把原来的指令或部分指令替换成一条断点指令,对于x86就是"int 3",这条指令要能让系统
进入被gdb控制的状态.type对应上面所说的'Z'命令的type字段,state标志每个断点的状态4中状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(include/linux/kgdb.h)
  83 enum kgdb_bptype {
  84         BP_BREAKPOINT = 0,
  85         BP_HARDWARE_BREAKPOINT,
  86         BP_WRITE_WATCHPOINT,
  87         BP_READ_WATCHPOINT,
  88         BP_ACCESS_WATCHPOINT
  89 };
  90
  91 enum kgdb_bpstate {
  92         BP_UNDEFINED = 0,
  93         BP_REMOVED,
  94         BP_SET,
  95         BP_ACTIVE
  96 };

在 kgdb_break[]被初始化时 state 成员都被设置成 BP_UNDEFINED,表明这个数组元
素可用,当gdb设置一个断点时,一个数组元素会被设成BP_SET,相反取消断点时则把相应的
数组项设成 BP_REMOVED. 当断点被激活时元素被设置为 BP_ACTIVE.

几个断点状态的重要切换点:
1. BP_UNDEFINED/BP_REMOTED -> DB_SET 和 BP_SET -> BP_REMOVED分别发生
在收到gdb的'Z'和'z'指令时.由函数kgdb_set_sw_break()和 kgdb_remove_sw_break()实现

2. BP_SET -> DB_ACTIVE 一个断点处于BP_SET状态并没有生效,只是说明这个断点是有
效的.当gdb发来's'或'c'命令恢复原来程序执行时才会让断点变成 BP_ACTIVE.函数
kgdb_activate_sw_breakpoints()实现了这个状态转换.

3. BP_ACTIVE -> BP_SET 发生在进入和gdb_serial_stub()之前,先把断点都失效在与远
端的gdb通信,在讲主函数的时候几经提过.

4. 其它状态 -> DB_UNDEFINED 函数remove_all_break()完成这个功能,它主要是在远
程gdb与kgdb断开链接时,即进行'D','k','C15'等命令时kgdb对所有断点进行重置.

下面具体看看 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
(kernel/kgdb.c)
 616 /*
 617 * SW breakpoint management:
 618 */
 619 static int kgdb_activate_sw_breakpoints(void)
 620 {
 621           unsigned long addr;
622   int error = 0;
623   int i;
624
625   for (i = 0; i < KGDB_MAX_BREAKPOINTS; i++) {
626            if (kgdb_break[i].state != BP_SET)
627                      continue;
628
629            addr = kgdb_break[i].bpt_addr;
630            error = kgdb_arch_set_breakpoint(addr,
631                              kgdb_break[i].saved_instr);
632            if (error)
633                      return error;
634
635            kgdb_flush_swbreak_addr(addr);
636            kgdb_break[i].state = BP_ACTIVE;
637   }
638   return 0;
639 }

kgdb_arch_set_breakpoint()是一个和体系结构相关的函数,但是暂时在内核代码里
面只有一个地方实现了, 这里所做的就是读取断点地址对应的指令并保存起来, 然后用一条
可以触发断点的指令替换这段内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(kernel/kgdb.c)
 167 /*
 168 * Weak aliases for breakpoint management,
 169 * can be overriden by architectures when needed:
 170 */
 171 int __weak kgdb_arch_set_breakpoint(unsigned long addr, char *saved_instr)
 172 {
 173        int err;
 174
 175       err = probe_kernel_read(saved_instr, (char *)addr, BREAK_INSTR_SIZE);
 176        if (err)
 177                    return err;
 178
 179        return probe_kernel_write((char *)addr, arch_kgdb_ops.gdb_bpt_instr,
 180                                    BREAK_INSTR_SIZE);
 181 }

这里出来一个有趣的单词__weak,它是一个宏,和__packed是同一种东西都是gcc的扩展属性:

#define __packed __attribute__((packed))
#define __weak __attribute__((weak))

如果这个关键字用在函数定义上面,一般情况下和一般函数没有两样。但是当有一个同
名函数但是不带__weak被定义时,所有对这个函数的调用都是指向后者(不带__weak那个),
如果有两个一样的函数都用了__weak,那么真正调用那个,就要看连接器了。

设置断点的过程其实就是像大家想像的那样,把相应地址的指令读上来并保存起来,把
一条断点指令写到这个地址上去.

关于断点的函数像kgdb_set_sw_break(),kgdb_remove_sw_break()这些函数的实
现都是直接了当的,这里就不一一说明了.

6条回应:“kgdb源代码分析(2.6.27)第四章-命令实现”

  1. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

  2. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

  3. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

  4. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

  5. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

  6. […] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]

发表评论

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

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