首页 > 深入kgdb > kgdb源代码分析(2.6.27)第三章-gdb 远程串行协议

kgdb源代码分析(2.6.27)第三章-gdb 远程串行协议

2010年7月1日 发表评论 阅读评论

kgdb源代码分析(2.6.27)

本文由penny编写, 你可以通过发邮件到lianglip@gmail.com联系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:

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()开头的代码列出来:

(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’回复.

本文地址:
http://www.kgdb.info/kgdb_source_chapter_three/
版权所有 © 转载时必须以链接形式注明作者和原始出处!

  1. miaomiao
    2010年12月30日18:48 | #1

    很感谢博主的分享,解决了我RSP协议概念方面的问题。但是我还有如下问题:

    在使用GDB时,如果设置断点,命令是 b (example : b CServer::SetPtk), 但是在gdbserver 端处理的却是‘Z’ $ZTADDR,LENGTH#xx,我不知道这其中是怎么样的一个过程。

    [回复]

    DDD 回复:

    这个过程是由gdb计算出来的。

    example:

    (gdb) b CServer::SetPtk
    –> gdb 查找CServer::SetPtk 符号对应的代码地址 ZTADDR,
    然后在封装成RSP协议格式‘Z’ $ZTADDR,LENGTH#xx.

    [回复]

    miaomiao 回复:

    我明白了,Thank you very much!

    [回复]

  2. wyl
    2011年6月6日11:27 | #2

    看了你的文章,受益非浅,但我还是没搞懂continue时, 按ctrl+c怎样停止内核的,我这边用kgdb调试时没法按ctrl+c停止内核执行,谢谢!

    [回复]

  1. 2010年6月30日16:08 | #1
  2. 2010年7月1日00:47 | #2
  3. 2010年7月2日11:08 | #3
  4. 2010年7月2日11:09 | #4
  5. 2010年7月2日11:10 | #5