Translated By LinFangquan and KongLiGDB 远程串行协议RSP 是一系列的基于 GNU 的嵌入式开发系统的一部分,作者提出了他自己使用 GDB 远程地调试嵌入式应用的一些论述在 9 月份,我介绍了 GDB我论述了它的远程调试是如何能够调试执行在一个通过串口或者以太网或者其他方式连接到 PC 上的嵌入式系统上的代码的尽管也存在着具有这种能力的商业的产品,但是在我的心目中,免费的 GDB 是一个很不错的解决方案,因为它提供了一个轻型的、功能强大的调试器,它可以工作在嵌入式系统的广大的范围上,包括驱动通讯接口或者资源受限无法得到一般商业化的产品的支持在那篇文章我还提到了,为了实现远程调试,gdb 需要一个服务——调试代理——一个很小的代码库,它在被调试的目标机器上管理寄存器和主存,通过通信链接响应断点,向 gdb 报告应用的状态那篇文章包括了 SH-2 微控制器的调试桩的一个摘要,但是我实际上在那里并没有打算详细地说明一个完整的调试桩是如何工作的我将在本月的 GDB RSP——gdb 的标准远程通信协议一文(本文)中详细说明如果你对于你的处理器处理断点和其它事件很满意,那么你需要掌握的全部知识是一些基本的 RSP 消息格式,它可以让你的嵌入式系统和 gdb 对话。
协议定义RSP 是一种简单的基于 ASCII 编码的协议,它使用串口,局域网或者其它任何支持半双工数据交换的通讯方式RSP 报文以一个美元符($)开头,接着是若干个 ASCII 字节流,它们是消息的主体,最后以一个井号符(#)结束,两个 ASCII十六进制的字符作为消息的校验和附在消息的后面例如,下面是一个完整的 RSP 消息包:$m4015bc,2#5a接收者接到消息之后,立刻回复一条消息内容为”+”或者“-”,以表示他正确无误地收到消息(校验通过) ,或者接收失败一个典型的事件是,gdb 给调试目标发出调试命令,调试目标接收消息,并返回一个简单的确认或者错误码如果是后者被返回了,gdb 将报告给使用者(程序员) ,并且暂停一切正在活动的进程控制台输出消息,调试目标会输出文本到 gdb 的控制台,输出内容是典型的命令-确认信息的顺序除非当另一个命令已经在执行,否则这个消息可以在任何时候从调试桩发送到 gdb接下来的段落,描述了 RSP 若干常用命令为了说明的需要,我将 RSP 消息分为三类:寄存器和主存相关的命令,程序控制命令和其它命令寄存器和主存相关命令以下是读写寄存器的命令Read registers (“g”)Example: $g#67当它想要知道调试目标当前寄存器的所有信息时,gdb 会发送这条命令。
以下是一个目标应答消息的例子:+ $123456789abcdef0...#xx(0 号寄存器(译者按:32 位寄存器)内容为: 0x12345678,1 号寄存器内容为: 0x9abcdef0, 以此类推)这个应答是一个有序的字节流,它顺序地指出了寄存器的数据,按照目标宏文件(gdb/config//tm-.h (例如, Hitachi SH的目标宏文件gdb/config/sh/tm-sh.h))中定义的顺序Write registers (“G”)Example: $G123456789abcdef0...#xx(Set register 0 to x12345678, register1 to 0x9abcdef0, 以此类推)这个消息是和读寄存器命令互补的通过这条命令,gdb提供了一个顺序的字节流,它将要存储到目标处理机的寄存器中的数据在程序执行中断前立即写入一个应答消息的例子:+ $OK#9aWrite register N (“P”)Example: $P10=0040149c#b3(Set register 10(注意是16进制0x10) to the value 0x0040149c.)一个返回应答:+ $OK#9a以下是读写主存的几条命令。
Read memory (“m”)Example: $m4015bc,2#5a (从地址0x4015bc开始,读两个字节的内容)一个应答消息示例:+ $2f86#06Write memory (“M”)Example: M4015cc,2:c320#6d(将长度为2个字节的0xc320写入主存0x4015cc中)一个应答消息示例:+ $OK#9a程序控制命令程序控制命令是gdb用来控制目标机被调试的应用的行为的消息这些命令要比上面讲述的寄存器-主存控制命令要稍微复杂一些Get last signal (“?”)Example: $?#3f这条命令用来找出目标机是如何到达当前状态的Step (“s”)Example: $s#73当用户发出单步调试Step命令时,gdb向目标机发送Continue (“c”)Example: $c#63当用户发出Continue命令时,gdb向目标机发送Last signal” response (“S”)Example: $S05#b8Expedited response (“T”)Example: $T0510:1238;F:FFE0...#xx其它命令Console output (“O”)—optionalExample:$O48656c6c6f2c20776f726c64210a#55(Prints “Hello, world!\n” on the gdb console)其它命令其它命令控制台输出(”O”)——非强制的例:$048656c6c6f2c20776f726c64210a#55(在控制台显示“Hello World!\n”)这条命令让 gdb 调试桩向 gdb 控制台发送一个文本信息,这些显示在控制台上的符号对应它们 16 进制的 ASCII 码(‘H’=0x48)。
Gdb会连续接收信息直到遇见换行符(’\n’,0x0a) 这类信息一般是来源于目标机;gdb 从不会向目标机上发送控制台输出信息空响应(” ”)如果调试桩遇到不支持或无法识别的命令,它会返回一个空响应Gdb 在接收到空响应后,如果有相似的命令则会选择相似命令例:目标机响应:+ $#00错误响应(”E”)当调试桩在命令执行过程中遇到一个错误时,它必须向 gdb 返回一个错误报告在内存操作中,总线错误和/或非法地址就是典型的例子此时,调试桩会向 gdb 发送一个错误报告例:目标机响应:+$E01#xx并非所有错误 gdb 都进行过预定义;当 gdb 接收到错误报告时,它将会把信息显示在控制台上并中断正在执行的操作总结总结现在,我已经将所有在嵌入式系统与 gdb 通信过程中所需要的基本知识分别介绍过了在上一篇文章中,我介绍了陷阱报文,单步执行和一些 gdb 特性;在上面的章节中,我还介绍了 gdb 与调试桩之间的通信协议现在我们要做的就是把和谐分立的信息总结在一起实际上,在真正开始调试前,我们还要解决一个次要问题:鸡和蛋的问题,即在第一次通信时将调试桩下载进嵌入式系统从而为以后建立通信打下基础。
为了解决这个问题,可以有多种方法对于我,最简单的方法就是将最简化的调试桩固化到目标机上的非易失性存储器中,用这些代码启动嵌入式系统并协助下载余下的信息到 RAM 中当 gdb 启动应用程序时,gdb 的控制命令传输到第二个与应用程序本身相连的调试桩这个方法的最大优点在于,可以使用户继续改进开发与应用程序绑定的调试桩代码,而不需要重新编辑在目标机非易失性存储器中的代码,尤其是对于那些非易失性存储器是一次性 ROM 的目标机另外,由于固化在目标机上的调试桩代码只包含最简单的命令——读/写内存,写寄存器和单步执行等,在这一块出现严重错误的概率也会很低另一种方法是将整个调试桩固化到目标机中并使用它执行所有调试指令这种方法不需要将目标机应用程序绑定一个调试桩然而在固化程序出错或者要添加新的功能时就会显示出起的缺点如果你使用的是商用的微处理器插板,你可能根部不需要自己编写调试桩,因为 gdb 本身已经支持供应商的标准,或者你只要通过串口分析工具为开发板提供支持 gdb 的环境测试调试桩测试调试桩如果你已经有一个调试桩就绪,你必须在投入使用之前对其进行测试下面是我推荐的一种测试流程无论你什么时候对调试桩进行修改,记住要使它们实现所需的功能。
第一步,为了方便起见,将下面的内容加入到你的 gdbint 文件中:set remotedebug 1set remotelogfile gdb_logfile这些命令让 gdb 显示宿主机和目标机之间所有的 RSP 信息,并把它们记录到文件 gdb_logfile 中第二步,将gdb与远程目标相联,输入命令“target remote [port]”因为gdb进行连接,观察两者之间的交互信息以保证你的调试桩能够对gdb的命令做出正确回应在启动的过程中,你的调试桩需要将所有数据载入到每个处理器的寄存器中使用gdb的” info registers”命令来保证gdb能够正确的接收和显示这些数据第三步,使用gdb的set命令来改变一些寄存器的值,保证调试桩既能准确响应write register命令,同时能在以后的read register命令中返回正确结果第四步,对内存做相似的工作举个例子:print *(long*)0x1234set *(long*)0x1234=5678print *(long*)0x1234如果所有工作都能正常工作,你就可以试验gdb的load命令这时控制台上的显示会非常杂乱无章,你需要借助log文件。
一旦这一步完成,检查一些内存位置的值以保证有期望的数值如果你的调试桩支持控制台输出功能,那么你的测试要包含一些gdb控制台输出在控制台输入‘continue’,看输出结果是否是你所期望的复位目标平台,再装入测试程序在控制台输出命令前的一行设置断点再输入continue,核实断点是否在正确位置以及程序恢复后其输出是否正确接着,重新载入测试程序,在次设置断点,设在有变量的行,检查程序的变量变化是否正确,检查程序计数器,堆栈指针和其它寄存器的值,保证它们的变化是按照你所预想的经过以上步骤后,你的调试桩就可以正常工作了结束语结束语在我看来,在调试工具领域,gdb是无可代替的,无论是开源软件还是其他的再加上它的稳定性和一系列有用的特性,gdb为嵌入式开发工作者以最小的代价提供了灵活的开发环境如果你投入了必要的时间来开发一个调试桩,作为回报,你会对目标平台的架构有一个透彻的理解,并且获得一个功能强大的,可扩展的调试器。