简述嵌入式linux交叉编译软件开发流程中的交叉调试

嵌入式开发
您当前所在位置:&&&技术支持与下载&&&嵌入式开发&&&正文
解析基于ELF的嵌入式软件源码级交叉调试技术
开发任何一个软件都不可避免地存在各种错误,要修正错误必须找出其错误原因。通常程序员利用调试器来跟踪程序执行情况,快速有效地定位错误产生的位置从而找到引起错误的原因,并改正错误。
开发任何一个软件都不可避免地存在各种错误,要修正错误必须找出其错误原因。通常程序员利用调试器来跟踪程序执行情况,快速有效地定位错误产生的位置从而找到引起错误的原因,并改正错误。
调试器为用户提供的主要功能包括:在目标程序中设置、删除断点;以单步执行或连续执行等方式控制目标程序运行;浏览程序中的变量或表达式的值;查看、修改目标机寄存器的内容;查看、修改目标机内存的内容。源码级调试器是面向高级语言的符号调试工具,它基于源代码的语句和符号跟踪观察目标程序,同时提供基于汇编级的程序跟踪功能以满足用户底层的调试需要。通用计算机软件一般在同一台机器上进行编辑、编译、调试;而嵌入式软件的目标系统多为特殊的专用系统,通常采用宿主机/目标机开发环境,借助通用计算机作为编辑源文件的宿主机,利用交叉编译器在宿主机上编译生成目标机的可执行代码,调试时通过通讯介质(串线或网络)将目标代码下载到目标系统上运行,利用交叉调试器进行跟踪调试。
一、源码级交叉调试器的实现途径
程序运行过程中目标程序的指令代码和数据都映射到目标机上相应的内存内容,为了实现源码级调试,利用目标文件中在程序编译链接时生成的调试信息来实现目标程序与源程序之间的映射,从而在源码级实现对程序执行情况的控制和观察。其关键在于找到调试控制点和数据在源程序与目标程序之间的映射关系。
任何数据都有名和值两个侧面,数据名与数据值之间的映射关系为:根据数据名得到存放该数据值的内存地址,再从目标机的内存地址取出其内容即为数据值:
调试中的程序控制点通常为源程序中的函数、语句行等,它们对应于装载到目标内存中的相应目标代码,要实现程序的运行控制关键在于得到源代码与目标代码之间的映射关系:由源码定位信息得到相应的目标码信息;由目标码地址得到相应的源码定位信息。源码定位信息为源文件名+行号或函数名;目标码信息为目标指令在目标机内存中的起始和终止地址。
嵌入式软件以宿主机/目标机模式开发,其交叉调试器分为宿主机部分和目标机部分,两者以统一的通讯协议进行通信,宿主机向目标机发送命令,目标机接收、执行命令并将结果返回宿主机,从而实现两机之间的交互控制。免费软件基金会FSF提供的调试工具gdb具有一套比较成熟的通讯协议----remote通讯协议,该协议作为开放软件被广为采用,在此我们选择了rmote协议作为交叉调试器的远程通讯协议。
二、ELF格式目标文件
目标文件是实现源码级调试的基础,需要详细分析文件的格式及内容以从中获取有用的调试信息。在设计调试器时采用可执行连接格式――ELF格式目标文件作为开发基础,ELF(Executable and Linking Format)是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface(ABI))而开发和发布的,已被软件业广泛采用,在Linux系统中ELF格式是其默认的目标文件格式,许多嵌入式软件都采用ELF格式作为目标文件格式。
ELF目标文件主要有三种类型:可重定位文件,可执行文件,共享的目标文件,我们以可执行文件为分析对象。
ELF头固定在文件的起始位置,其它各部分的位置由ELF头及其它相关信息获得。
ELF头是整个文件的入口,具有固定的长度,52个字节,包含14个值。包括ELF文件标识,程序头表和节头表的位置、长度,文件中段的数目和节的数目等信息。
2、程序头表与段
程序头表中有多个表项,每个表项是一个程序段的信息,固定长度为32个字节,包含8个值,包括段在文件中的位置,段在内存中的起始虚拟地址,段的长度及其它属性等。调试器根据程序头表中的信息来确定需要下载到目标机上的目标文件内容(指令与数据)及其在目标机中的内存地址。
3、节头表与节
节头表中也有多个表项,每个表项是一个节的信息,固定长度为40个字节,包含10个值,包括节名、节的类型、该节在文件中的位置、该节在内存中的起始地址(如果该节出现在内存映象中)、节的长度等信息。某些节是程序段的组成部分,如包含程序二进制指令代码的正文节.text和数据节.rodata,.hash等,某些节不作为段的组成部分,只提供其它的额外信息。为源码调试服务的有 .debug,.line,.symtab,.debug_ pubname,.debug_range等节,其中.debug, .line节包含了源码调试信息的基本内容。
.debug节中有多种类型的记录,可分为几大类:
(1)、编译模块信息:包含组成该文件的各个模块的源文件名,路径,及该模块的代码地址范围等。
(2)、子程序信息:包含程序名,程序类型,起始终止地址,程序返回结果存放地址等。
(3)、变量信息:包含变量名、变量类型、变量存放地址信息等,变量有多种类型,简单变量、结构变量等类型的变量其信息内容各有不同。
将.debug节中各项内容的结构关系抽象为家族关系。以节的起始为根,首先是一个编译单元的信息,它给出下一个编译单元(兄弟关系)在文件中的相对位置。紧跟着编译单元的是该编译单元中的子程序与公共变量信息(父子关系),同样的,编译单元中头一个函数记录或变量记录将给出它的兄弟的位置信息。紧随该函数记录的是该函数内部的子程序与局部变量信息。相邻层次成员是父子关系,同一层次上的成员是兄弟关系,如图4所示:
.line节中包含目标代码地址与源代码行号之间的对应关系。对每个编译单元给出其行记录信息的长度和目标码的起始基地址,以及该编译单元中所有的行记录,每条记录以固定的格式表示:“该行目标码相对于基地址的偏移,列号(保留,暂未使用),行号”。
综合上述程序段和节的内容,即可确定源码与目标码的映射关系。如给定一个文件名及行号,确定其目标代码的信息。首先根据文件名确定其在.debug节中的编译模块信息,从中可得该文件模块的起始终止地址;再由其起始地址找到该编译模块的行记录信息在.line节中的位置,根据行号找到行记录,得到该行目标码的地址范围;由这些地址信息,可直接从目标机内存中取得目标代码,也可结合程序段信息从目标文件的程序段中取得该行所对应的目标代码指令内容。调试器利用地址与指令信息就可以查看、修改、执行相应目标代码,供用户进行调试。
三、源码级交叉调试器实现的技术要点
在设计交叉调试器JDBG时,首先完成与目标文件无关的部分:连接目标机,查看修改目标机寄存器和内存;然后实现与目标文件有关的部分:下载目标文件到目标机,源码级调试功能,包括断点控制、执行控制、变量观察等,以下重点讨论各项功能的设计与实现。
1、 &下载目标文件
目标文件中包含多种类型的内容,目标程序在目标机上运行时只需要程序的二进制指令代码与相关数据,这些内容包含在文件中的可执行程序段中。下载目标文件时在宿主机上提取目标文件中的代码与数据段,根据其地址映射关系利用remote协议中写内存的功能在目标机上建立程序的远程映像。
断点是调试器控制程序执行的基本手段。各种机器有其特殊的断点指令(如X86的int3指令),设置断点就是将机器断点指令替换所指定程序单元中的指令,使得程序运行到断点指令处时,产生“断点异常”,用户程序不再继续执行下去,目标机向宿主机返回断点停止信号,由宿主机调试器接管对用户程序的控制。
断点分为逻辑断点与物理断点,两者是多对一的关系。逻辑断点与源代码对应,提供源级断点信息(源文件名+行号或物理地址等);物理断点与目标码对应,提供断点的目标码地址及断点处的指令内容。插入一个断点即根据逻辑断点信息确定物理断点地址,将该地址对应指令用机器断点指令替代;删除断点即恢复断点指令处预先保存的原指令,使得程序可继续执行。
检查点是一种条件断点,使程序在条件满足时停止执行。通常条件都涉及表达式的值变化,利用写内存保护来检查表达式的值是最常用的方法,但这种方法不适用于不具备MMU功能的处理器。某些处理器(如i386)提供了专门的调试控制寄存器,通过在调试控制寄存器中设置相应线性地址及中止条件(读或写)即可中止程序的运行。通过查询方式检查表达式的值也是一种可行的方法,其效率相对较低。当检查点的条件满足时,检查点的操作与普通断点类似。
对一个断点应具有基本的插入、删除、使能、使不能等基本操作功能。在设计中采用双向链表结构作为断点的数据结构,使得断点控制更加方便高效。
3、 &启动程序运行
启动程序运行首先从目标文件的ELF头中得到应用程序的入口,将目标机的PC寄存器置为该入口地址,从入口处开始执行程序指令。如果程序中没有设置任何断点或检查点,则程序一直运行到结束;如果程序中有断点,则程序运行到第一个断点处停止并返回其停止位置。
4、 &源码级单步调试
源码级单步以行为单位,一行源代码对应多条机器指令,因此一个源语句的单步执行需要多个机器指令的单步执行。最简单的实现方法就是从源语句对应目标代码的起始地址开始逐条单步执行机器指令。显然这种方法效率很低,尤其调试嵌入式软件需要大量宿主机/目标机间的通讯,而且有的处理器自身并不提供机器单步执行指令。因此,更有效的方法是用“内部临时断点+连续执行”的方式来实现。
用机器单步执行时,每执行完一条机器指令立即读取当前PC,判断程序停止位置。调试器对程序的控制是即时交互的,当单步执行结束、跳转或进入函数调用时,调试器都会即时得知。不采用机器单步,则不具有这种即时掌握程序运行状态的便利,需要在运行前反汇编目标代码,通过分析汇编指令预测程序单步执行可能的出口位置,并在该处设上断点。可能的出口位置包括该行指令的终止地址、跳转、分枝跳转、函数调用等指令。对于跳转指令,能得到绝对地址则直接在跳转地址处设断点,不能得到跳转地址则在指令处设断点,再执行跳转指令由当前PC值确定程序停止的位置。对于函数调用指令,如果采用单步跳过(stepover)方式,则将其作为普通指令看待,直接执行调用函数;如果采用单步进入(stepinto)方式,则需在指令处设断点,再执行调用指令,以进入函数内部让用户继续跟踪程序的执行。
以单步跳出方式(stepout)执行程序时,以子程序为单位,需执行完当前子程序的所有代码,在调用该子程序的下一条语句处停止。通过反汇编当前子程序的所有指令,可得到该子程序所有可能的函数返回出口,在这些函数返回指令处设临时断点,程序运行到断点处再执行该返回指令跳出当前子程序,处理调用该子程序的语句行的后继信息,最终确定程序停止位置。
5、数据浏览
数据与指令同样是构成程序的根本。数据类型繁多,不同的类型处理起来有所不同,其基本原理是由数据名找到存放该数据的地址,再从地址中取出数据的值。关键在于找到名与地址的映射关系。这些信息可从.debug,.symtab等节中得到。全局变量一般可得到直接的地址信息,而局部变量采用堆栈方式存放在内存中,需根据从.debug中得到的该变量在堆栈中的位置信息来确定地址。采用表结构来管理变量,将每个变量的名、地址等信息登记在表中,将对数据值的操作转化为对相应地址的内存单元操作,如查看变量即为读取该变量所在内存单元的内容。
四、交叉调试器JDBG简述
JDBG采用图形用户界面,相应的命令提供图形按钮或菜单,并提供快捷键。进入调试之前,先连接目标机,在连接时设置连接参数并保存,在以后的连接中可直接使用已保存的设置参数。当宿主机/目标机处于连接状态时,下载目标文件,启动调试器。调试器启动后,用户可在打开的源文件中设置断点,运行下载的目标程序,查看/修改寄存器、内存的内容。当运行的目标程序停止后,返回停止点源码信息,用户可以观察数据,添加新的断点或删除已设断点,控制程序的单步执行或连续执行,或退出调试状态。
(编辑:admin)
上一篇:下一篇:
直接点击发布即可作为游客留言。
版权所有 @ 龙人集团有限公司 保留一切权利!
专业PCB抄板,芯片解密,反汇编,PCB克隆,样机制作服务提供商! 粤ICP备号
商务中心地址:广东省深圳市福田区福虹路世贸广场B座26F
24小时服务热线:1 /
传真:086-7
E-mail:.cn嵌入式硬件调试和软件调试_pcb吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0可签7级以上的吧50个
本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:8,360贴子:
嵌入式硬件调试和软件调试
嵌入式硬件调试和软件调试相对于软件调试而言,使用硬件调试器可以获得更强大的调试功能和更优秀的调试性能。硬件调试器的基本原理是通过仿真硬件的执行过程,让开发者在调试时可以随时了解到系统的当前执行情况。目前嵌入式系统开发中最常用到的硬件调试器是ROM Monitor、ROM Emulator、In-Circuit Emulator和In-Circuit Debugger。采用ROM Monitor方式进行交叉调试需要在宿主机上运行调试器,在目标机上运行ROM监视器(ROM Monitor)和被调试程序,宿主机通过调试器与目标机上的ROM监视器建立通信连接,它们之间的通信遵循远程调试协议。ROM监视器可以是一段运行在目标机ROM上的可执行程序,也可以是一个专门的硬件调试设备,它负责监控目标机上被调试程序的运行情况,能够与宿主机端的调试器一同完成对应用程序的调试。在使用这种调试方式时,被调试程序首先通过ROM监视器下载到目标机,然后在ROM监视器的监控下完成调试,目前使用的绝大部分ROM监视器能够完成设置、单步执行、查看、修改内存空间等各项调试功能。采用ROM Emulator方式进行交叉调试时需要使用ROM仿真器,它通常被插入到目标机上的ROM插槽中,专门用于仿真目标机上的ROM芯片。在使用这种调试方式时,被调试程序首先下载到ROM仿真器中,它等效于下载到目标机的ROM芯片上,然后在ROM仿真器中完成对目标程序的调试。ROM Emulator调试方式通过使用一个ROM仿真器,虽然避免了每次修改程序后都必须重新烧写到目标机ROM中这一费时费力的操作,但由于ROM仿真器本身比较昂贵,功能相对来讲又比较单一,因此只适应于某些特定场合。采用In-Circuit Emulator(ICE)方式进行交叉调试时需要使用,它是仿照目标机上的而专门设计的硬件,可以完全仿真处理器芯片的行为,并且提供了非常丰富的调试功能。在使用在线仿真器进行调试的过程中,可以按顺序单步执行,也可以倒退执行,还可以实时查看所有需要的数据,从而给调试过程带来了很多的便利。嵌入式系统应用的一个显著特点是与现实世界中的硬件直接相关,存在各种异变和事先未知的变化,从而给微处理器的指令执行带来各种不确定因素,这种不确定性在目前情况下只有通过在线仿真器才有可能发现,因此尽管在线仿真器的价格非常昂贵,但仍然得到了非常广泛的应用。采用In-Circuit Debugger(ICD)方式进行交叉调试时需要使用在线调试器。由于ICE的价格非常昂贵,并且每种都需要一种与之对应的ICE,使得开发成本非常高,一个比较好的解决办法是让CPU直接在其内部实现调试功能,并通过在开发板上引出的调试端口,发送调试命令和接收调试信息,完成调试过程。目前Motorola公司提供的开发板上使用的是DBM调试端口,而ARM公司提供的开发板上使用的则是JTAG调试端口,使用合适的软件工具与这些调试端口进行连接,可以获得与ICE类似的调试效果。软件调试软件调试通常要在不同的层次上进行,有时可能需要对嵌入式操作系统的内核进行调试,而有时可能仅仅只需要调试嵌入式应用程序就可以了。在嵌入式系统的整个开发过程中,不同层次上的软件调试需要使用不同的调试方法。嵌入式操作系统的内核调试相对来讲比较困难,这是因为在内核中不便于增加一个调试器程序,而只能通过远程调试的方法,通过串口和操作系统内置的&调试桩&(debug stub)进行通信,共同完成调试过程。调试桩可以看成是一个调试服务器,它通过操作系统获得一些必要的调试信息,并且负责处理宿主机发送来的调试命令。具体到嵌入式Linux系统内核,调试时可以先在Linux内核中设置一个调试桩,用作调试过程中和宿主机之间的通信服务器,然后就可以在宿主机中通过调试器的串口与调试桩进行通信,并通过调试器控制目标机上Linux内核的运行。嵌入式应用软件的调试可以使用本地调试和远程调试两种方法,相对于操作系统的调试而言,这两种方式都比较简单。如果采用的是本地调试,首先要将所需的调试器移植到目标系统中,然后就可以直接在目标机上运行调试器来调试应用程序了;如果采用的是远程调试,则需要移植一个调试服务器到目标系统中,并通过它与宿主机上的调试器共同完成应用程序的调试。在嵌入式Linux系统的开发中,远程调试时目标机上使用的调试服务器通常是gdbserver,而宿主机上使用的调试器则是gdb,两者相互配合共同完成调试过程。专业嵌入式linux、ARM、STM32、单片机、安卓、PCB、电子、FPGA等技术学习,所有课程均可免费试听,详情请咨询付老师:电话,QQ。深圳(南山+民治+龙岗+西乡),广州,郑州,长沙,南宁八大实训基地统一授课中!!
使用签名档&&
保存至快速回贴推荐这篇日记的豆列
······}

我要回帖

更多关于 嵌入式gdb调试 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信