当前位置:Linux教程 - Linux - 嵌入式操作系统调试

嵌入式操作系统调试

调试是开发过程中必不可少的环节,通用的桌面操作系统与嵌入式操
作系统在调试环境上存在明显的差别。前者,调试器与被调试的程序往
往是运行在同一台机器、相同的操作系统上的两个进程,调试器进程
通过操作系统专门提供的调用接口(早期UNIX系统的ptrace调用、如
今的进程文件系统等)控制、访问被调试进程。

后者(又称为远程调试),为了向系统开发人员提供灵活、方便的调
试界面,调试器还是运行于通用桌面操作系统的应用程
序,被调试的程序则运行于基于特定硬件平台的嵌入式操作系统
(目标操作系统)。

这就带来以下 问 题:

调试器与被调试程序如何通信,被调试程序产生异常如何及
时通知调试器,调试器如何控制、访问被调试程序,调试器
如何识别有关被调试程序的多任务信息并控制某一特定任务 ,
调试器如何处理某些与目标硬件平台相关的
信息(如目标平台的寄存器信息、机器代码 的 反汇编等)。

我们介绍两种远程调试的方案,看它们怎样解决这些问题。

调试方案 一 插桩(stub)
第一种方案是在目标操作系统和调试器内分别加入某些功能模块,
二者互通信息来进行调试 。

上述问题可通过以下途径解决:

调试器与被调试程序的通信

调试器与目标操作系统通过指定通信端口(串口、网卡、并口)
遵循远程调试协议进行通信
(远程调试协议详见http://rtos.ict.ac.cn/rtos/debugger/)。

被调试程序产生异常及时通知调试器
目标操作系统的所有异常处理最终都要转向通信模块,告知调
试器当前的异常号;调试器据 此向用户显示被调试程序产生了
哪一类异常。 调试器控制、访问被调试程序 调试器的这类请
求实际上都将转换成对被调试程序的地址空间或目标平台的某些寄存器
的 访问,目标操作系统接收到这样的请求可以直接处理。对于
没有虚拟存储概念的简单的嵌入式 操作系统而言,完成这些
任务十分容易。调试器识别有关被调试程序的多任务信息并
控制某一特定任务 由目标操作系统提供相关接口。目标系统根
据调试器发送的关于多任务的请求,调用该接口 提供相应信息
或针对某一特定任务进行控制,并返回信息给调试器。

调试器处理与目标硬件平台相关的信息

第2条所述调试器应能根据异常号识别目标平台产生异常的类型
也属于这一范畴,这类工作完全可以由调试器独立完成。支持多
种目标平台正是GNU GDB的一大特色。

综上所述,这一方案需要目标操作系统提供支持远程调试协议的
通信模块(包括简单的设备驱动)和多任务调试接口,并改写异
常处理的有关部分。另外目标操作系统还需要定义一个设置断点的
函数;因为有的硬件平台提供能产生特定调试陷阱异常(debug trap)
的断点指令以支持调试(如X86的INT 3),而另一些机器没有类似的
指令,就用任意一条不能被解释执行的非法(保留)指令代替。

目标操作系统添加的这些模块统称为""插桩""(见下图),驻留于ROM
中则称为ROM monitor。通用操作系统也有具备这类模块的:

编译运行于Alpha、Sparc或PowerPC平台的LINUX内核时若将kgdb开
关打开,就相当于加入了插桩。

http://www.cn.ibm.com/developerWorks/linux/embed/debug/fig1.gif
图1

运行于目标操作系统的被调试的应用程序要在入口处调用这个设置
断点的函数以产生异常,异常处理程序调用调试端口通信模块,
等待主机(host)上的调试器发送信息。双方建立连接后调试器便
等待用户发出调试命令,目标系统等待调试器根据用户命令生成的指令。

这一 过程如下图所示。

http://www.cn.ibm.com/developerWorks/linux/embed/debug/fig2.gif
图2

这一方案的实质是用软件接管目标系统的全部异常处理(exception
handler)及部分中断处理,在其中插入调试端口通信模块,与主机的调试器
交互。它只能在目标操作系统初始化,特别是调试通信端口初始化完成
后才起作用,所以一般只用于调试运行于目标操作系统之上的应用程序,
而不宜用来调试目标操作系统,特别是无法调试目标

操作系统的启动过程。而且由于它必然要占用目标平台的某个通信端口,
该端口的通信程序就无法调试了。最关键的是它必须改动目标操作系统,
这一改动即使没有对操作系统在调试过程中的表现造成不利影响,至少
也会导致目标系统多了一个不用于正式发布的调试版。



二 片上调试(On Chip Debugging)及
Embedded PowerPC Background Debug Mode

片上调试是在处理器内部嵌入额外的控制模块,当满足了一定的触发条件
时进入某种特殊状态。在该状态下,被调试程序停止运行,主机的调试器
可以通过处理器外部特设的通信接口
访问各种资源(寄存器、存储器等)并执行指令。为了实现主机通信端
口与目标板调试通信接口各引脚信号的匹配,二者往往通过一块简单的
信号转换电路板连接(如下图所示)。

内 嵌的控制模块以基于微码的监控器(microcode monitor)或纯硬件
资源的形式存在,包括一些提供给用户的接口(如断点寄存器等)。具体
产品有Motorola CPU16、CPU32、Coldfire系列的BDM(Background Debug
Mode),Motorola PowerPC 5xx、8xx系列的
EPBDM(Embedded PowerPC Background Debug Mode),IBM、TI的
JTAG(Joint Test Action Debug,IEEE标准),还有OnCE、MPSD等等。

下面以MPC860的EPBDM为例介绍片上调试方式。



http://www.cn.ibm.com/developerWorks/linux/embed/debug/fig3.gif
图3



EPBDM的运作相当于用处理器内嵌的调试模块接管中断及异常处理。用户
通过设置调试许可寄存器(debug enable register)来指定哪些中断或
异常发生后处理器直接进入调试状态,而不是操作系统的处理程序。进入
调试状态后,内嵌调试模块向外部调试通信接口发出信号,通知一直在通信接口
监听的主机调试器,然后调试器便可通过调试模块使处理器执行任意系统
指令(相当于特权 态)。所有指令均通过调试模块获取,所有load/store
均直接访问内存,缓存(cache)及存储管理单元(MMU)均不可用;数据寄
存器被映射为一个特殊寄存器DPDR,通过mtspr和mfspr指令访问。调试器
向处理器送rfi(return from interrupt)指令便结束调试状态,被调试程序
继续运行。


与插桩方式的缺点相对应,OCD不占用目标平台的通信端口,无需修改目
标操作系统,能调试目标操作系统的启动过程,大大方便了系统开发人
员。

随之而来的缺点是软件工作量的增加:调试器端除了需补充对目标操作
系统多任务的识别、控制等模块,还要针对使用同一芯 片的不同开发板
编写各类ROM、RAM的初始化程序。

下面就以调试运行于MPC860的LINUX为例,说明用OCD方式调试OS 启动的
某些关键细节。



首先,LINUX内核模块以压缩后的zImage形式驻留于目标板的ROM,目标
板上电后先运行RO M 中指定位置的程序将内核移至RAM并解压缩,然后
再跳转至内核入口处运行。要调试内核,
必须在上电后ROM中的指令执行之前获得系统的控制权,即进入调试
状态、设断点,这样才能开展调试过程。MPC860的EPBDM提供了这一手段。

MPC860没有类似X86的INT
3那样能产生特定调试陷阱异常的指令,而操作系统内核往往具有针对
非法指令的异常处理

;为了使对内核正常运行的干扰降至最小,调试时应尽量设置硬件
断点,而不是利用非法
指 令产生异常的""软""断点。

LINUX实现了虚存管理,嵌入式LINUX往往也有这一功能。地址空间从实
到虚的转换在内核启动过程中便完成了,不论调试内核还是应用程序,调
试器都无法回避对目标系统虚地址空间的访问,否则断点命中时根本无法
根据程序计数器的虚地址显示当前指令,更不用说访问变量了。由于调试
状态下转换旁视缓冲器(Translation Lookaside Buffer)无法利用,
只能仿照LINUX内核TLB失效时的异常处理程序,根据虚地址中的页表索
引位访问特定寄存器查两级页表得出物理页面号,从而完成虚实地址的
转换。MPC860采用哈佛结构(Harvard architecture),指令和数据缓
存分离设置(因为程序的指令段和数据段是分离的,这种结构可以消除取
指令和访问数据之间的冲突),二者的TLB也分离设置;然而TLB失效时查找页
表计算物理地址的过程是相同的,因为页表只有一个,不存在指令、数据
分离的问题。虚实地址转换这一任务虽然完全落在了调试器一方,

由于上述原因,再加上调试对象是嵌入式系统,一般不会有外存设备,
不必考虑内存访问缺页的情况,所以增加的工作量并不大。



深入话题
传统的调试方法可概括为如下过程:设断点--程序暂停--观察程序状态
--继续运行。被调试的如果是实时系统,即使调试器支持批处理命令避免了
用户输入命令、观察结果带来的延迟,它与目标系统之间的通信也完全可
能错过对目标平台外设信号的响应。于是,针对某些调 试器(如GDB)
提供的监视点(trace point)这一特殊调试手段,目标方的插桩在原有
的基础上被改进,称为代理(agent)。调试时用户首先在调试器设置监
视点,以源代码表达式的形式指定感兴趣的对象名。为了减少
代理解析表达式的工作,调试器将表达式转换为简单的字节码,传送至
代理。程序运行后命中监视点、唤醒代理,代理根据字节码记录用户所
需数据存入特定缓冲区(不仅仅是表达式的最终结果,还有中间结果),
令程序继续运行;这一步骤无需与调试器通信。当调试器再度得到控制
时,就可以发出命令,向代理查询历次监视记录。较之于插桩,代理
增加了对接受到的字节码的分析模块,相应的目标代码体积只有大
约3K字节;当然,监视记录缓冲区也要占用目标平台的存储空间,不过
缓冲区的大小可在代理生成时由用户决定。总之,这一改进以有限的目
标系统资源为代价,为实时监视提供了一个低成本的可行方案。


调试并不仅仅意味着设断点--程序暂停--观察--继续这一过程,往往还
需要profiling、跟踪(trace)等多种手段,而现代微处理器的技术进
步却为这些调试手段的实行带来了困难。

以跟踪为例,其目的无非是记录真实的程序运行流;可现代处理器指令
缓存都集成于芯片内(RISC处理器尤为如此),运行指令时""取指""这一
操作大多在芯片内部针对指令缓存进行,芯片外部总线上只能观察到多
条指令的预取(prefetch),预取的指令并不一定执行(由于跳转等原因);
另外,指令往往经过动态调度后在流水线中乱序执行,如何再现其原始
顺序也是个问题。解决方案大致有以下三种:

有的处理器除了正常运行外,还能以串行方式运行,所有的取指周期都
可呈现于片外总线 (相当于禁用缓存与流水线)。
这样一来,跟踪容易多了,处理器性能也大大降低了,根本不适用于
实时要求严格的系统。编译器自动在指定的分支及函数出入口插入对特
定内存区域的写指令(与gprof等profiling 工具采用的手段类似),它
们都是不通过缓存而直接向内存写的,这就能反映于芯片外总线

从而被外接的逻辑分析仪记录,最终由主机端的调试工具分析并结合符号
表重构程序流。这种方法虽被广泛使用,但毕竟是干扰式的(intrusive),
对系统性能也有影响。像上文所述的片上调试那样,也有处理器在片
内附加了跟踪电路,收集程序流运行时的""不连贯""(discontinuities)
信息(分支和异常处理的跳转目的及源地址等),压缩后送至特定端
口,再由逻辑分析仪捕获送至主机端调试工具重构程序流。该方案对
系统性能影响最小。


总之,处理器厂家提供集成于片内的调试电路为高档嵌入式系统开发提
供各种非干扰式的调试手段早已是大势所趋。为了解决该领域标准化的
需要,一些处理器厂家、工具开发公司和 仪器制造商于1998年组成了
Nexus 5001 Forum,这是一个旨在为嵌入式控制应用产生和定义嵌入式
处理器调试接口标准的联合组织,以前的名称是
Global Embedded Processor Debug Interface Standard Consortium
(全球嵌入式处理器调试接口标准协会)。

Nexus现在有24个成员单位,包括创始成员Motorola、Infineon
Technologies、日立、ETAS和HP等公司。该组织首先处理的是汽车
动力应用所需要的调试,现在已发展成为调试数据通信、无线系统和其
他实时嵌入式应用的通用接口。


参考文献



MPC860 PowerQUICC Users Manual MOTOROLA http://www.vas-gmbh.de/software/mpcbdm/
http://www.metrowerks.com/tools/documentation/embedded/zenofbdm
http://www.redhat.com/support/wpapers/cygnus_heinsenberg/trace.html
http://www.ednmag.com/reg/2000/05112000/10tt.htm