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