BWDSP是一款我国自主研发的多核高性能通用DSP。BWDSP的体系结构、指令集、配套基础软件全部由中电38所自主设计开发。BWDSP的配套基础软件包括汇编器、编译器、调试器、集成开发环境、芯片模拟器等等。BWDSP芯片的调试器软件包括主机调试器软件和调试链接服务软件两部分。调试链接服务软件运行于ICE(In Circuit Emulator)上,起到主机与芯片之间通信互联的功能。而主机调试器软件是调试系统的核心,负责解析用户命令、分析调试信息、管理可执行文件、管理断点(和观察点)、单步调试控制等等[1]。BWDSP的调试系统支持汇编级调试和C语言级调试。在开发BWDSP芯片调试系统的C语言单步调试功能的过程中,研发人员分析了国内外类似工作的可取之外和不足之处,自主创新开发出实现方案。
国内已经陆续有研究机构自主创新开始调试系统的研发工作[2-7],但这些工作在实现C语言单步调试方面都存在一些不足。文献[2-3]中的单步调试功能是汇编语言级的单步调试。文献[7]中的调试系统虽然实现了C语言级单步调试功能,但并未充分考虑到C语言一行代码可能的复杂性,只能实现简单C语言代码的单步调试功能。本文提出的方案充分考虑了C语言单步调试需要面临的问题,可以实现任意复杂程序的C语言代码行的单步调试功能。本方案原理简单,容易理解,并经过大量BWDSP芯片调试过程的检验,已经证明了其正确性。
1。单步调试功能简介
单步调试指以源代码行为单位对被调试程序的运行进行控制的调试功能。单步调试不需要用户显式地设置断点,可以控制程序运行一个或若干个代码行。通过这种方式,用户可以结合变量查看、堆栈查看、观察点等调试功能,把潜在的程序错误定位至某个代码行。单步调试功能一般分为两大类:汇编级单步调试功能和源代码级单步调试功能。汇编级单步调试功能指单步调试控制运行的单位是汇编语言的代码行。源代码级单步调试指单步调试控制运行的单位是高级语言源代码中的代码行。每类单步调试功能一般又包括三种:跳入、跳过、跳出。
对于汇编级单步调试,三种单步调试功能的实现较为简单,其功能概述如下:
跳入调试功能控制被调试程序运行至当前代码行中调用的函数内部。若当前代码行中无函数调用,则控制被调试程序运行完当前代码行。
跳过指控制被调试程序运行完当前代码行。若当前代码行中有函数调用,则控制被调试程序执行完该函数调用并继续运行完当前代码行。
跳出指控制被调试程序运行至当前函数的返回地址处。
对于高级语言如C语言,其三种单步调试功能从总体概念上和汇编级单步调试功能类似。但由于C语言一行代码经编译后生成一段汇编指令,且一行代码中的语句可能非常复杂,其单步调试功能相对于汇编级单步调试更为复杂。C语言单步调试功能的实现要面对如下几个问题:
1.程序当前PC(Program Counter,指程序地址)并不一定在一行代码的最开始处,而有可能在一行代码生成的一段汇编指令的中间位置。用户有可能先通过汇编级调试功能使PC停在C语言代码行的中间位置,然后再进行C语言级单步调试。
2.一行代码中可能不止一个函数调用。C语言中一行代码中可以有任意多个函数调用。
3.一行代码可能有多个return语句。C语言中,一行代码中可以写任意长的代码,其中可能包含若干个条件控制语句(if…else…),每种条件下都可能有一个return语句,程序有可能从其中任意一个return语句处从本函数返回。
4.一行代码可能有多个出口。若代码行处于循环之中,则代码行中每个break(或continue)语句都可以跳出本行。若代码行中有多个break(或continue)语句,则程序可能从其中任意一个位置离开本行。
5.一行代码中的出口有可能是条件跳转的出口,程序并不一定从该出口跳出。条件跳转根据运行时的具体情况来判断是否需要跳转,有可能跳转,也有可能不跳转。
6.函数调用有可能以函数指针的方式实现。C语言中定义有函数指针,通过函数指针进行函数调用同样是函数调用的一种方式,跳入功能的实现必须考虑这种情况。
7.程序的执行可能不是简单的顺序执行。用户可以把一个完整的循环写到一行代码中,因此程序流可能不是顺序执行的,有可能回到本代码行中已经执行过的某个程序位置继续执行。C语言级单步调试功能的实现不能依赖于程序流的顺序执行。
8.一行代码有可能没有下一行代码。如一行代码位于函数体或源文件的最后一行,则该行代码是没有下一行代码的。运行完该行代码后程序流只会返回到上一层函数,或跳转至本函数中其他位置。
9.单步调试过程中可能由于其他原因使调试过程提前结束。若用户设置了断点或观察点,单步调试过程中可能引起这些断点(或观察点)的触发,使单步调试过程提前结束。
由于C语言单步调试功能面临这些复杂的问题,因此C语言单步调试功能的实现远比汇编级单步调试功能的实现要复杂。
2 方案概述
2.1 已有方案介绍
目前,单步调试功能的实现方案分为两大类。一类通过不断控制程序流按汇编指令行逐行执行,进行汇编级单步执行,每执行一行汇编代码就控制被调试程序停下来,分析程序地址,根据当前程序地址判断单步调试功能是否完成。另一类方案通过反汇编当前源代码行生成的汇编程序段,寻找其中的函数调用、出口地址,在这些位置设置临时断点。设置完临时断点后,控制程序正常运行,直至遇到断点停止为止。程序遇到断点停止又有分为几种情况,若断点是用户显式设置的断点,说明在单步调试的过程中遇到用户断点,一次单步调试结束。若断点是为本次单步调试设置的临时断点,一次单步调试正常结束。单步调试结束后,还要把为本次单步调试设置的临时断点全部删除。
第一种方案的优点是实现简单,容易理解。但是,若源代码行中的代码很复杂,生成的汇编程序段内容很大,使用第一种方案将使程序反复处于运行和停止状态,调试效率不高。芯片执行汇编级单步运行的速度也远远小于正常运行的速度。尤其是针对嵌入式系统的一些远程调试系统,调试器和被调试程序运行在不同的机器上,通过串口或网口进行通信,第一种方法将带来大量的通信负担。显然,对于一个高效的嵌入式系统调试器,使用第一种方法是不可行的。本文介绍的单步调试功能实现方法属于第二类方案。
国内已经有少数研究机构开展单步调试功能的研究,但这些研究并未能完全解决C语言单步调试功能面临的所有问题。文献[4-5]按第一种方案实现了一款调试器的单步调试功能,显然这种实现方式具有不可避免的通信负担。文献[6]实现了一款基于串口通信的嵌入式调试系统,其单步调试功能也是基于第一种方案。文献[7]较为完整地介绍了一款调试器中单步调试功能的实现,其实现方案属于上述第二类方案,基本完成了C语言调试的主要功能。但该项工作并未考虑到C语言中单步调试功能的复杂性,未能解决上节介绍的第1、6、7、8、9等问题。若被调试代码中包含较为复杂的代码,该款调试器的单步调试功能将不能正确完成。
2.2 几种出口类型介绍
BWDSP芯片调试系统软件在充分调研了国内外前人工作的基础上,参考已经成功实现的方案,自主实现了C语言单步调试功能。BWDSP芯片配套调试系统软件的单步调试功能基于在代码行出口处设置临时断点的方案实现。与国内外已有工作相比,该方案充分考虑了C语言代码行中的代码复杂性,可以解决第1节中列举的9种问题。
BWDSP芯片配套调试系统把一行C语言代码行的出口分为如下几类:
1:通过for、while循环、break、continue语句跳转至其他代码行,该类出口称为L(Label)类出口,其出口地址集记为L。C语言中的这些跳转指令最终编码为BWDSP芯片指令集中的跳转指令。BWDSP芯片指令集中的跳转指令分为四类:①条件跳转:跳转有可能发生,也可能不发生,根据运行时的计算结果或寄存器值确定;②绝对跳转:跳转一定会发生,跳转目的地址在指令机器码中编码;③相对跳转:跳转一定会发生,跳转目的地址是当前地址加上一个偏移;④寄存器跳转,跳转一定会发生,跳转目的地址是某个寄存器中的值,不能从可执行文件程序段获取。对于第①②③类跳转,其目的地址可以从程序段反汇编得到。而对第④类跳转,其目的地址不能从程序段得到,需要在运行时从某个寄存器中读取。
2:通过函数调用跳转至其他行,这类出口称为F(Function)类出口,这些函数的入口地址集记为F。C语言中的函数调用最终被编码为BWDSP芯片指令集中的函数调用指令。BWDSP指令集中的函数调用指令分为两类:①绝对地址函数调用:函数调用的入口地址编码在指令中,可以从程序段得到;②寄存器函数调用:函数调用的入口地址是调用时某个寄存器中的值。一般,直接调用C语言中的函数会编译为第一类函数调用指令;通过函数指针调用函数会编译为第二类函数调用指令。同样,第一类函数调用的入口地址可以通过反汇编程序段得到,而第二类函数调用的入口地址只能在运行时读取某个寄存器中的值获得。
3:通过return语句跳转至本帧的返回地址,这类出口称为R(Return)类出口,返回地址记为R。一个函数可能有多个retrun语句,一行C语言代码中也可能有多处函数返回。但由于程序流一定处于函数调用栈中的栈顶,该函数返回后肯定返回至上一帧的现场,所以当前PC所处的函数中的多处return语句其实返回到同一个地址。而且,由于函数可能在多处被调用,所以该返回地址不能通过反汇编程序段获得,只能通过运行时函数调用栈获得。
4:直接运行完本行代码来到的程序地址,这类出口称为N(Next),其出口地址记为N。程序运行完当前行后,会自然地运行下一行的指令,因此下一行指令的开始地址也是本行代码的一个出口地址。对于部分C语言代码行,并没有下一行出口地址。例如,若一个代码行是一函数的最后一行,若执行该行代码时,或者跳转至其他行,或返回调用函数,肯定不会进入下一行代码执行。所以,本类出口可能存在,也可能不存在。
在将一行C语言代码行的出口分为以上四类的基础上,BWDSP调试系统通过在这些出口上设置临时断点实现单步调试功能。设置的临时断点位置不同,实现的单步调试功能也不同。对有些特殊的出口,除了设置临时断点,还要做一些特殊处理。下一节将对三种单步调试功能的实现方法进行具体介绍。
3 具体实现
3.1 跳出
跳出是实现方式最简单的单步调试功能,其功能是跳出当前函数,来到其返回地址处。由于一个函数在运行时返回地址只有一个,即R,所以只需要在R处设置一个临时断点,然后让被调试程序正常运行即可。当程序遇到断点停下时,若该断点为用户设置的断点,说明单步调试过程中用户断点触发,跳出单步调试结束。若该断点为临时断点R,说明跳出功能正常实现,单步调试结束。该方案可以解决第1节中介绍的第3、9点问题,而其他问题对跳出单步调试功能没有影响。
图1 跳出单步调试功能实现流程图
若当前函数是主函数main函数,则函数栈中只有一帧,无需设置临时断点,直接运行即可。
跳出单步调试功能的实现方案流程如图1所示。
3.2 跳过
跳过功能使程序执行完当前代码行。若本代码行中有函数调用,则执行完该函数,并返回本代码行继续运行,直至程序自然地跳出该代码行为止。所以,跳过单步调试功能应在L、N类出口设置临时断点。
图2 跳过单步调试功能实现流程图
一般,C语言程序在进入函数和跳出函数时会有一小段代码用来进行函数栈入栈和出栈的操作。这一小段代码在调试信息中一般与源代码中函数开始和结束时的正反大括号对应。单步调试时,若代码行中有return语句,该语句实际上被编译为一个跳转语句,跳转至函数结尾的大括号处,并不会真正返回上一层调用函数。若代码行中有函数结束时的大括号,则程序在该代码行中真正有可能返回调用函数。返回上一层调用函数的指令在BWDSP指令集中被编码为RET指令,因此可以通过检查代码行中是否有RET指令判断该代码行是否有可能返回调用函数。若代码行中有RET指令,还要在R类出口处设置断点。该方法可以解决第1节中介绍的第8个问题。
程序PC即使在一行代码中间某个位置,也有可能跳回到该行代码的开始位置继续执行。设置临时断点时,需要在该行代码中的全部L类出口处都设置断点,不管该跳转指令是在当前PC之前还是之后。这种方案可以解决第1、7个问题。
对于L类出口中的第①类跳转(条件跳转)出口,不论该跳转是否会发生,都在该出口处设置临时断点。若程序执行了该条件跳转,本方案可以使跳过单步调试功能正常结束。若程序没有执行该条件跳转,设置的该临时断点也不会对单步调试功能产生影响。这种方案解决了第5个问题。
对于L类出口中的第④类跳转(寄存器跳转),其出口地址不能在运行之前获得,因此也不能在出口处设置临时断点。对这类跳转,需要在其跳转指令处设置临时断点,若该临时断点触发,读取其跳转目的地址寄存器的值。若读到的跳转目的地址在本行代码中,则保留所有已经设置的临时断点,继续运行程序。若该跳转的目的地址不在本行代码中,则控制程序进行一次汇编级指令行单步以执行该跳转,然后结束本次单步调试过程。
由于本方案没有在函数调用出口出设置断点,若源代码行中有多个函数调用,这些函数调用会依次执行,不会触发断点。因此,本方案可以解决第2个问题。
被调试程序触发以上设置的临时断点中的任意一个,都意味着单步跳过调试过程的结束。所以,只要临时断点位置是代码行的合法可能出口,设置多个临时断点不会对单步调试功能的正确性产生影响。显然,该方案可以解决第4个问题。
本方案解决了第1节中介绍的第1、2、3、4、5、7、8、9问题,而第6个问题不会对跳过单步调试功能产生影响。跳过单步调试的实现方案流程如图2所示。
3.3跳入
跳入调试功能使程序可以进入代码行中调用的函数内部。若程序在当前代码行中的执行过程中没有遇到函数调用,则执行完本代码行即完成单步调试功能,此时其调试功能与跳过单步调试类似。显然,实现单步跳入调试功能需要在L、F、R、N处都设置临时断点。
对于F类出口中的第②类函数调用(寄存器函数调用),同样需要在函数调用指令处设置临时断点。当该临时断点触发时,控制程序进行一次汇编级指令行单步,使程序进入调用函数。该方法可以解决第6个问题。
/2015/CLanguageStepDebugging05.JPG"
图3 跳入单步调试功能实现流程图
由于本方案在所有函数调用处都设置了断点,所以程序会停止在执行过程中的第一个函数调用出口。调试行为表现为程序会跳入执行过程中遇到的第一个函数。
第1节中介绍的所有问题都对跳入单步调试功能有影响,而本方案可以解决所有9个问题。
跳入调试功能的实现流程如图3所示。
3.4 实验结果
BWDSP芯片配套调试系统的C语言单步调试功能按照本文介绍的方案实施。开发人员以任意复杂的C语言代码行在各种复杂的调试场景下对单步调试功能进行测试,调试系统均可按单步调试预定义的功能完成调试过程。目前,在BWDSP芯片的模拟器、编译器、操作系统开发过程中,均已经在项目组内部试用本调试器进行C语言级调试。经过反复测试、试用、修改,本方案已经证实是一个健壮的实施方案,可以实现任意复杂C语言代码行的单步调试功能。
总结
本文介绍了BWDSP芯片调试系统中C语言单步调试功能的实现方案。该方案充分研究了C语言一行源代码中可能对单步调试功能产生影响的各种情况,充分考虑了C语言代码行的复杂性,可以实现任意复杂C语言代码行的单步调试功能。经BWDSP芯片调试过程的验证,证实了本方案的有效性。
[1]黄光红,刘冠南。可配置多核处理器的调试器模块化分层设计[J]。单片机与嵌入式系统应用,2014,14(7):13-15。
[2]向征。基于C30的嵌入式计算机自主调试环境的建立和软件设计[J]。微电子学与计算机,1999,3,18-20,56。
[3]文跃荣。基于UART的电能芯片在线调试设计[D]。湖南大学,2012。
[4]刘晓升,王宜怀。HC08系列微控制器在线调试的关键技术分析[J]。计算机工程与设计,2009,30(3),532-535。
[5]龚兰兰,刘晓升,朱巧明。远程调试系统的关键技术分析[J]。计算机应用与软件,2010,27(10),258-261。
[6]梁泉。嵌入式系统交叉调试器的设计与实现[D]。电子科技大学,2008。
[7]赵民栋。嵌入式软件集成开发环境中调试器的设计与实现[D]。西北工业大学,2004。