一种C语言级单步调试系统的功能实现方案※
2015-07-03林广栋黄光红耿锐
林广栋,黄光红,耿锐
(中国电子科技集团公司 第三十八研究所,合肥230088)
引 言
本文提出的方案充分考虑了C语言单步调试需要面临的问题,可以实现任意复杂程序的C语言代码行的单步调试功能。方案原理简单,容易理解,并经过大量BWDSP芯片调试过程的检验,已经证明了其正确性。
1 单步调试功能简介
单步调试是指以源代码行为单位对被调试程序的运行进行控制的调试功能。单步调试不需要用户显式地设置断点,可以控制程序运行一个或若干个代码行。通过这种方式,用户可以结合变量查看、堆栈查看、观察点等调试功能,把潜在的程序错误定位至某个代码行。
单步调试功能一般分为两大类:汇编级单步调试功能和源代码级单步调试功能。汇编级单步调试功能指单步调试控制运行的单位是汇编语言的代码行。源代码级单步调试指单步调试控制运行的单位是高级语言源代码中的代码行。每类单步调试功能一般又包括三种:跳入、跳过、跳出。
对于汇编级单步调试,3种单步调试功能的实现较为简单,其功能概述如下:
①跳入调试功能控制被调试程序运行至当前代码行中调用的函数内部。若当前代码行中无函数调用,则控制被调试程序运行完当前代码行。
②跳过调试功能控制被调试程序运行完当前代码行。若当前代码行中有函数调用,则控制被调试程序执行完该函数调用并继续运行完当前代码行。
③跳出调试功能控制被调试程序运行至当前函数的返回地址处。
对于高级语言(如C语言),其3种单步调试功能从总体概念上与汇编级单步调试功能类似。但由于C语言一行代码经编译后生成一段汇编指令,且一行代码中的语句可能非常复杂,其单步调试功能相对于汇编级单步调试更为复杂。C语言单步调试功能的实现要面对的问题略——编者注。
2 方案概述
2.1 已有方案介绍
目前,单步调试功能的实现方案分为两大类:一类通过不断控制程序流按汇编指令行逐行执行,进行汇编级单步执行,每执行一行汇编代码就控制被调试程序停下来,分析程序地址,根据当前程序地址判断单步调试功能是否完成;另一类通过反汇编当前源代码行生成的汇编程序段,寻找其中的函数调用、出口地址,在这些位置设置临时断点。
第一种方案的优点是实现简单,容易理解。但是,若源代码行中的代码很复杂,生成的汇编程序段内容很多,使用第一种方案将使程序反复处于运行和停止状态,调试效率不高。本文介绍的单步调试功能实现方法属于第二类方案。
国内已经有少数研究机构开展单步调试功能的研究,但这些研究并未能完全解决C语言单步调试功能面临的所有问题。参考文献[4-5]按第一种方案实现了一款调试器的单步调试功能,显然这种实现方式具有不可避免的通信负担。参考文献[6]实现了一款基于串口通信的嵌入式调试系统,其单步调试功能也是基于第一种方案。参考文献[7]较为完整地介绍了一款调试器中单步调试功能的实现,其实现方案属于第二类方案,基本完成了C语言调试的主要功能。但该项工作并未考虑到C语言中单步调试功能的复杂性,若被调试代码中包含较为复杂的代码,该款调试器的单步调试功能将不能正确完成。
2.2 几种出口类型介绍
BWDSP芯片调试系统软件在充分调研了国内外前人工作的基础上,参考已经成功实现的方案,自主实现了C语言单步调试功能。BWDSP芯片配套调试系统软件的单步调试功能基于在代码行出口处设置临时断点的方案实现。与国内外已有工作相比,该方案充分考虑了C语言代码行中的代码复杂性,可以解决很多问题。
BWDSP芯片配套调试系统把一行C语言代码行的出口分为如下几类:
①通过for、while循环、break、continue语句跳转至其他代码行,该类出口称为L(Label)类出口,其出口地址集记为L。C语言中的这些跳转指令最终编码为BWDSP芯片指令集中的跳转指令。BWDSP芯片指令集中的跳转指令分为4类:
(a)条件跳转。跳转有可能发生,也可能不发生,根据运行时的计算结果或寄存器值确定。
(b)绝对跳转。跳转一定会发生,跳转目的地址在指令机器码中编码。
(c)相对跳转。跳转一定会发生,跳转目的地址是当前地址加上一个偏移。
(d)寄存器跳转。跳转一定会发生,跳转目的地址是某个寄存器中的值,不能从可执行文件程序段获取。
对于前面3类跳转,其目的地址可以从程序段反汇编得到。而对第4类跳转,其目的地址不能从程序段得到,需要在运行时从某个寄存器中读取。
②通过函数调用跳转至其他行,这类出口称为F(Function)类出口,这些函数的入口地址集记为F。C语言中的函数调用最终被编码为BWDSP芯片指令集中的函数调用指令。BWDSP指令集中的函数调用指令分为两类:
(a)绝对地址函数调用。函数调用的入口地址编码在指令中,可以从程序段得到。
(b)寄存器函数调用。函数调用的入口地址是调用时某个寄存器中的值。
一般,直接调用C语言中的函数会编译为第一类函数调用指令;通过函数指针调用函数会编译为第二类函数调用指令。同样,第一类函数调用的入口地址可以通过反汇编程序段得到,而第二类函数调用的入口地址只能在运行时读取某个寄存器中的值获得。
③通过return语句跳转至本帧的返回地址,这类出口称为R(Return)类出口,返回地址记为R。一个函数可能有多个retrun语句,一行C语言代码中也可能有多处函数返回。但由于程序流一定处于函数调用栈中的栈顶,该函数返回后肯定返回至上一帧的现场,所以当前PC所处的函数中的多处return语句其实返回到同一个地址。而且,由于函数可能在多处被调用,所以该返回地址不能通过反汇编程序段获得,只能通过运行时函数调用栈获得。
④直接运行完本行代码来到的程序地址,这类出口称为N(Next),其出口地址记为N。程序运行完当前行后,会自然地运行下一行的指令,因此下一行指令的开始地址也是本行代码的一个出口地址。对于部分C语言代码行,并没有下一行出口地址。例如,若一个代码行是一函数的最后一行,若执行该行代码时,或者跳转至其他行,或返回调用函数,肯定不会进入下一行代码执行。所以,本类出口可能存在,也可能不存在。
在将一行C语言代码行的出口分为以上4类的基础上,BWDSP调试系统通过在这些出口上设置临时断点实现单步调试功能。设置的临时断点位置不同,实现的单步调试功能也不同。对有些特殊的出口,除了设置临时断点,还要做一些特殊处理。
3 具体实现
3.1 跳 出
跳出是实现方式最简单的单步调试功能,其功能是跳出当前函数,来到其返回地址处。由于一个函数在运行时返回地址只有一个,即R,所以只需要在R处设置一个临时断点,然后让被调试程序正常运行即可。当程序遇到断点停下时,若该断点为用户设置的断点,说明单步调试过程中用户断点被触发,跳出单步调试结束。若该断点为临时断点R,说明跳出功能正常实现,单步调试结束。若当前函数是main函数,则函数栈中只有一帧,无需设置临时断点,直接运行即可。
跳出单步调试功能的实现方案流程如图1所示。
图1 跳出单步调试功能实现流程图
3.2 跳 过
跳过功能使程序执行完当前代码行。若本代码行中有函数调用,则执行完该函数,并返回本代码行继续运行,直至程序自然地跳出该代码行为止。所以,跳过单步调试功能应在L、N类出口设置临时断点。
一般,C语言程序在进入函数和跳出函数时会有一小段代码用来进行函数栈入栈和出栈的操作。这一小段代码在调试信息中一般与源代码中函数开始和结束时的正反大括号对应。单步调试时,若代码行中有return语句,该语句实际上被编译为一个跳转语句,跳转至函数结尾的大括号处,并不会真正返回上一层调用函数。若代码行中有函数结束时的大括号,则程序在该代码行中真正有可能返回调用函数。返回上一层调用函数的指令在BWDSP指令集中被编码为RET指令,因此可以通过检查代码行中是否有RET指令判断该代码行是否有可能返回调用函数。若代码行中有RET指令,还要在R类出口处设置断点。
程序PC即使在一行代码中间某个位置,也有可能跳回到该行代码的开始位置继续执行。设置临时断点时,需要在该行代码中的全部L类出口处都设置断点,不管该跳转指令是在当前PC之前,还是之后。
对于L类出口中的第一类跳转(条件跳转)出口,不论该跳转是否会发生,都在该出口处设置临时断点。若程序执行了该条件跳转,本方案可以使跳过单步调试功能正常结束。若程序没有执行该条件跳转,设置的该临时断点也不会对单步调试功能产生影响。
对于L类出口中的第4类跳转(寄存器跳转),其出口地址不能在运行之前获得,因此也不能在出口处设置临时断点。对于这类跳转,需要在其跳转指令处设置临时断点,若该临时断点触发,则读取其跳转目的地址寄存器的值。若读到的跳转目的地址在本行代码中,则保留所有已经设置的临时断点,继续运行程序。若该跳转的目的地址不在本行代码中,则控制程序进行一次汇编级指令行单步以执行该跳转,然后结束本次单步调试过程。
由于本方案没有在函数调用出口处设置断点,若源代码行中有多个函数调用,则这些函数调用会依次执行,不会触发断点。
被调试程序触发以上设置的临时断点中的任意一个,都意味着单步跳过调试过程的结束。所以,只要临时断点位置是代码行的合法可能出口,设置多个临时断点就不会对单步调试功能的正确性产生影响。
本方案解决了第1节中介绍的第1、2、3、4、5、7、8、9问题,而第6个问题不会对跳过单步调试功能产生影响。跳过单步调试的实现方案流程如图2所示。
3.3 跳 入
跳入调试功能使程序可以进入代码行中调用的函数内部。若程序在当前代码行中的执行过程中没有遇到函数调用,则执行完本代码行即完成单步调试功能,此时其调试功能与跳过单步调试类似。显然,实现单步跳入调试功能需要在L、F、R、N处都设置临时断点。
对于F类出口中的第二类函数调用(寄存器函数调用),同样需要在函数调用指令处设置临时断点。当该临时断点触发时,控制程序进行一次汇编级指令行单步,使程序进入调用函数。
由于本方案在所有函数调用处都设置了断点,所以程序会停止在执行过程中的第一个函数调用出口。调试行为表现为程序会跳入执行过程中遇到的第一个函数。
第1节中介绍的所有问题都对跳入单步调试功能有影响,而本方案可以解决所有9个问题。跳入调试功能的实现流程如图3所示。
3.4 实验结果
图2 跳过单步调试功能实现流程图
BWDSP芯片配套调试系统的C语言单步调试功能按照本文介绍的方案实施。开发人员以任意复杂的C语言代码行在各种复杂的调试场景下对单步调试功能进行测试,调试系统均可按单步调试预定义的功能完成调试过程。目前,在BWDSP芯片的模拟器、编译器、操作系统开发过程中,均已经在项目组内部试用本调试器进行C语言级调试。
经过反复测试、试用、修改,本方案已经证实是一个理想的实施方案,可以实现任意复杂C语言代码行的单步调试功能。
结 语
本文介绍了BWDSP芯片调试系统中C语言单步调试功能的实现方案。该方案充分研究了C语言一行源代码中可能对单步调试功能产生影响的各种情况,充分考虑了C语言代码行的复杂性,可以实现任意复杂C语言代码行的单步调试功能。经BWDSP芯片调试过程的验证,证实了本方案的有效性。
图3 跳入单步调试功能实现流程图
编者注:本文为期刊缩略版,全文见本刊网站www.mesnet.com.cn。
[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.