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 初始化时机.
3.gdb 远程串行协议
在研究 kgdb 命令具体执行过程之前,我们先来看看 gdb 和远程的 kgdb 是通过一种
什么的方式通信的。在这里我们只做简略的介绍,详细说明可以在这里找到:http:
//www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gdb/remote-
protocol.html
这个现实世界中充满了各种各样的协议,很多事情都是遵循协议来做事,网络有 TCP
/IP,SPX/IPX, 电信有 ISUP,PRI,SIP,就业,租房,离婚都有协议.gdb 也不例外,gdb 和远程目
标之间用 gdb 远程串行协议(下面简称为 RSP)来通信。
RSP 是一种基于 ASCII 码的协议,以 gdb 发送命令,目标端(在这里是 kgdb)返回执行
结果或信息的方式来进行的,而命令和回复在传输过程中又是封装在一个包(packet)里面
的.每个包以一个'$'开始,接着是实际数据,数据以一个'#'结束,后面跟着两位十六进制数
字用作校验和($packet-data#checksum).校验和需要对 256 取模,因为只有两位。
当收到一个数据包,(无论是命令还是回复,无论是gdb端还是目标端)收到数据包的一
方应该根据收到的数据和校验和检查这个包的合法性,如果是一个正常的包,应该返回
一个'+',如果是一个损坏的包,则应该返回一个'-'。在包里面的数据,可以用',',';',':'三个
符号来分隔成不同的段,他们的用法根据不同的命令不同而不同.
gdb提供了一个很方便的方式让我们把通信过程记录下来,在gdb里面使用
"set remotelogfile [filepath]"指定你要记录的文件,但是这个文件一定要在开始记录前
手动创建,下面一段是在sys_mount设置一个断点的log:
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 | c info source c b sys_mount w $mc018ae77,1#fa r +$55#6a w +$mc018ae77,1#fa r +$55#6a w +$mc018ae77,1#fa r +$55#6a w +$mc018ae77,a#2a r +$5589e5565383ec148d55#e6 w +$mc018ae77,1#fa r +$55#6a w +$mc018ae78,1#fb r +$89#71 w +$mc018ae79,5#00 r +$e5565383ec#a0 w +$mc018ae79,5#00 r +$e5565383ec#a0 w +$mc018ae78,1#fb r +$89#71 w +$mc018ae79,1#fc r +$e5#9a w +$mc018ae7a,1#24 r +$56#6b w +$mc018ae7a,1#24 r +$56#6b w +$mc018ae7b,1#25 r +$53#68 w +$mc018ae7c,1#26 r +$83#6b w +$mc018ae7c,1#26 r +$83#6b w +$mc018ae77,1#fa r +$55#6a w+ c info breakpoints cc w $Z0,c018ae7f,1#72 |
gdb规定目标端起码应该实现:'g' 'G' 'm' 'M' 'c' 's',kgdb当然也有实现了,而且还不
止这些,有一些是和体系结构没有太紧密的关系,比如断点的设置和删除,kgdb只是记录
起这个地址,和设置这个断点的状态;内存的读取和修改,也许这个和体系结构有关,但是
具体实现内核已经搞定,kgdb不用关心。另外一方面一些比如's'单步执行这种命令就和体
系结构关系十分密切,x86上是通过设置标志寄存器的Trap标记,让cpu运行完下一条指令
后触发一个debug中断,其他的体系结构的做法都有所不同。
下面具体看看一些具体的命令:
'g'
格式:$g#67
描述:gdb向目标端发送这个命令来获取目标机当前寄存器的值
回复:+ $123456789abcdef0...#xx
'+'用来应答'g'这个命令,表明目标端正确地收到这个命令,然后就是目标端的回复包,
gdb规定用8十六进制个数字来表示一个寄存器的值,所以第一个寄存器的值为
12345678,第二个为9abcdef0,依此类推,而具体每个寄存器的含义和寄存器个数又体系
结构决定,定义在gdb的代码中. 当然这里8个数字是对32位系统来说的,为什么是8位?
限于我们这个协议是基于ASCII的,一个十六进制数只能标记4位,那32位自然是8个十六进制数了。
'G'
格式:$GXXXXXXXXXXX...#xx
描述:和g相反,这个命令用来设置目标机当前寄存器的值
回复: + $OK#9a
OK表示设置成功,后面我们会讲到不成功的情况.
'm'
格式:$m6a1bbb,2#b9
描述:读取一段内存的值,这里是读取以6a1bbb位起始地址的两个字节
回复: + $f488#0a 目标端把值返回.
'M'
格式:$Mccc5cc,2:a340#01
描述:设置一段内存的值,这里是把以ccc5cc位开始地址的两个字节设成a340
回复: + $OK#9a
's'
格式:$sADDR#xx
描述:用户进行单步调试时用到,ADDR指明了程序将从那个地址恢复运行,如果忽略ADDR,程序就从断点处继续运行.
回复:+ 目标端会马上返回数据正确或错误接收,但不会马上返回信息,具体信息要到下一次断点被触发时才会返回.下面会提到.
'c'
格式:$cADDR#xx
描述:让程序恢复正常运行
回复:和's'一样.
'Z'
格式: $ZTADDR,LENGTH#xx
'Z'命令用来设置断点或watch点,用过gdb的同志应该不会陌生了
'T'字段定义了这个命令的对象,0:软件断点,1:硬件断点,2:写watch点,3:读watch点,4:访问watch点.
'ADDR'就是我们所关心的内存地址,'LENGTH',对于软件中断它指明被断点指令覆盖
的内存长度,kgdb对于软件断点忽略掉它,因为触发 kgdb的指令与体系结构相关,已经定义
在kgdb这边,就如x86的int3在内存里面的二进制指令为"0xcc";对于硬件断点和watch 点,
'LENGTH'指明gdb关注的内存长度.
'z'
格式: $zTADDR,LENGTH#xx
各项与'Z'相同.用来取消断点。
回复:
错误回复:
格式:+ $E01#a6
描述:如果目标端在执行gdb的命令时出错时返回错误回复,比如访问内存时出错.E后面根
两位的错误码,错误码在gdb里面没有定义,没有定义其实更加方便,可以让开发端和目标端
对错误码的使用带来灵活.
空回复:
格式:+ $#00
描述:当目标端不认识gdb发来的命令时,返回空回复表示自己不支持这个命令.
对'c','s'的回复:
有好几种对'c','s'的回复,其中比较常见的是'S'和'T'
'S'
格式: $SAA#b8
作用: AA表明触发这次通信的那个异常相关的信号,这个信号就是posix标准中的信号.
'T'
格式: $TAAN..:R..;N..:R..#xx
作用: AA同样是信号号, N..:R.. 这表明一个寄存器和它的值,N标记寄存器号,R是它对应的
值,其中,如果N不是一个16进制数而是"thread",那么后面R的值就指明当前的进程号.如果
是其它的字符串gdb会省略.
Kgdb在回复's','c'时选用了'T'的方式,不过在'T'消息里面只有thread一个字段,没
有给gdb传更多的寄存器信息.
协议就在这里打住,有兴趣的同学可以继续深入研究各种高级用法.
顺便把gdb_serial_stub()开头的代码列出来:
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 | (kernel/kgdb.c) 1215 /* 1216 * This function performs all gdbserial command procesing 1217 */ 1218 static int gdb_serial_stub(struct kgdb_state *ks) 1219 { 1220 int error = 0; 1221 int tmp; 1222 1223 /* Clear the out buffer. */ 1224 memset(remcom_out_buffer, 0, sizeof(remcom_out_buffer)); 1225 1226 if (kgdb_connected) { 1227 unsigned char thref[8]; 1228 char *ptr; 1229 1230 /* Reply to host that an exception has occurred */ 1231 ptr = remcom_out_buffer; 1232 *ptr++ = 'T'; 1233 ptr = pack_hex_byte(ptr, ks->signo); 1234 ptr += strlen(strcpy(ptr, "thread:")); 1235 int_to_threadref(thref, shadow_pid(current->pid)); 1236 ptr = pack_threadid(ptr, thref); 1237 *ptr++ = ';'; 1238 put_packet(remcom_out_buffer); 1239 } 1240 1241 kgdb_usethread = kgdb_info[ks->cpu].task; 1242 ks->kgdb_usethreadid = shadow_pid(kgdb_info[ks->cpu].task->pid); 1243 ks->pass_exception = 0; |
实现传输的两个函数为 put_packet()和 get_packet()它们的实现十分简单直接,而传输
时使用的是底层驱动的读写操作,这部分的实现和 kgdb 没有直接的关系,kgdb 只是使用了
现成的东西而已,这里就不罗嗦了.前面这段就是'T'回复.
[…] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]
[…] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]
[…] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]
[…] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]
[…] 概述 1. 异步通知 2. 准备工作 3.gdb 远程串行协议(GDB remote serial protocol) 4. 命令实现: 4.1 断点. 4.2 continue 和 step 5 […]
很感谢博主的分享,解决了我RSP协议概念方面的问题。但是我还有如下问题:
在使用GDB时,如果设置断点,命令是 b (example : b CServer::SetPtk), 但是在gdbserver 端处理的却是‘Z’ $ZTADDR,LENGTH#xx,我不知道这其中是怎么样的一个过程。
[回复]
DDD 回复:
十二月 31st, 2010 at 10:13 上午
@miaomiao 这个过程是由gdb计算出来的。
example:
(gdb) b CServer::SetPtk
–> gdb 查找CServer::SetPtk 符号对应的代码地址 ZTADDR,
然后在封装成RSP协议格式‘Z’ $ZTADDR,LENGTH#xx.
[回复]
miaomiao 回复:
一月 5th, 2011 at 1:12 下午
@DDD 我明白了,Thank you very much!
[回复]
看了你的文章,受益非浅,但我还是没搞懂continue时, 按ctrl+c怎样停止内核的,我这边用kgdb调试时没法按ctrl+c停止内核执行,谢谢!
[回复]