新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 嵌入式Linux内核调试技术

嵌入式Linux内核调试技术

作者:宋尚春时间:2007-12-04来源:电子产品世界收藏

宋尚春,北京麦克泰软件技术有限公司

本文引用地址:http://www.eepw.com.cn/article/73300.htm

摘 要:由于没有对任何硬件平台都通用的嵌入式Linux操作系统,开发相应的嵌入式产品时,必须对和驱动程序进行裁减、移植和调试。因此调试器对于程序员来说是不可或缺的必备工具。本文主要讲述如何使用BDI2000仿真器与LinuxScope-JTD调试器进行嵌入式的调试,为嵌入式调试提供了一个便捷、高效的解决方案。
关键词:嵌入式Linux  内核调试 BDI2000 LinuxScope-JTD

The Embedded Linux Kernel Debugging Technology
Abstract: There is no Embedded Linux can work with all CPU, it is reduced、migrated and debugged。So,the debugger is very important. Here we talk about how to use BDI2000 and LinuxScope-JTD to debug Embedded Linux kernel, it is a very convenience and useful method.

Keywords:Embedded Linux; Kernel Debugging; BDI2000; LinuxScope-JTD

  近年来微电子技术迅猛发展,处理器技术发展速度也随之加快,嵌入式领域发生了翻天覆地的变化。特别是网络的普及,消费电子异军突起,嵌入式与互联网成为最热门的技术。在所有操作系统中,Linux是发展最快、应用最广泛的一种操作系统。Linux的开放性,以及其他优秀特性使其越来越成为嵌入式系统开发的操作系统首选。

1. 嵌入式系统开发所面临的问题

  嵌入式软件开发有别于桌面软件系统开发的一个显著的特点是一般需要一个交叉编译和调试环境,即编辑和编译软件在主机上进行,编译好的软件需要下载到目标机上运行 ,主机和目标机之间建立起通讯连接,并传输调试命令和数据。由于主机和目标机往往运行着不同的操作系统,而且处理器的体系结构也彼此不同,这就提高了嵌入式开发的复杂性。
总的来说,嵌入式开发所面临的问题主要表现在以下几个方面:

1.1 涉及多种CPU 及多种OS

  嵌入式的CPU或处理器可谓多种多样,包括MIPS、PPC、ARM,XScale等不同的架构,而且它们都很广泛地应用在众多领域。在这些处理器上运行的操作系统也有不少,如VxWorks、Linux、uC/OS、WinCE等等。在一个公司之内,可能会同时使用好几种处理器,甚至几种嵌入式操作系统。如果需要同时调试多种类型的板子,那复杂性是可想而知的。这也是我们选用BDI2000的原因之一。BDI2000是一款功能强大且非常稳定耐用的JTAG/BDM通用仿真器,由瑞士Abatron公司研制生产的性价比非常高的一款仿真器。它支持多种处理器:PPC/MIPS/ARM/XSCALE/ CPU12/CPU32/M-CORE/ColdFire等,支持Windows/Linux系统平台,支持多种第三方调试器,并且对Flash的烧写也很简单方便。

1.2 开发工具种类繁多

  通常各种操作系统有各自的开发工具,在同一系统下开发的不同阶段也会应用不同的开发工具。如在用户的目标板开发初期,需要硬件仿真器来调试硬件系统和基本的引导程序,然后进行操作系统及驱动程序的开发调试。在调试应用程序阶段可以使用交互式的开发环境进行软件调试,在测试阶段需要一些专门的测试工具软件进行功能和性能的测试。在生产阶段需要固化程序及出厂检测等等。每一种工具都要从不同的供应商购买,都要单独去学习和掌握,这无疑增加了整个公司的支出和管理的难度。BDI2000可以适应开发的各个阶段,可以节约企业的支出和简化管理难度。

1.3 对目标系统的观察和控制

  由于嵌入式硬件系统千差万别,软件模块和系统资源也多种多样,要使系统能正常工作,软件开发者必须要对目标系统具有完全的观察和控制能力,例如硬件的各种寄存器、内存空间,操作系统的信号量、消息队列、任务、堆栈等。
  此外,嵌入式系统变化更新比较快,对开发时间要求比较紧,尤其是消费类产品更是如此,如果有一套功能强大的嵌入式软件集成开发工具可以满足嵌入式软件开发各个阶段的需求,同时又使用方便,界面友好,那是最理想不过了。LinuxScope-JTD调试器就是这样一个理想的选择,它是美国 Ultimate Solution, Inc公司开发的专门用于配合BDI2000的一款调试器:基于Eclipse的集成开发环境和插件技术;提供脚本定制功能实现目标机的特殊操作;提供Linux内核调试功能,容易修改和观察硬件寄存器;增强的MI接口提供在Eclipse下识别硬件断点及模块跟踪;支持标准调试特性: 观察/修改内存,观察汇编代码, 观察变量,观察堆栈,跟踪内存断点(watch point);支持Linux应用程序的开发,支持Windows/Linux系统平台。

2. 嵌入式Linux的调试

2.1嵌入式Linux内核的调试

2.1.1 编译内核

  本文所调试的是Montavista Linux,硬件是Intel xscale pxa250。已经拥有超过两千多用户和数以千万计的Montavista Linux产品在市场上销售,它们覆盖从智能手机,高清电视,机器人,无线网络设备到3G电信服务器等各种嵌入式应用。Montavista Linux本身就是用BDI2000来开发调试的。
  进入内核源码目录下,配置完成相关选项并准备编译。配置时不要选中KGDB,否则会和BDI2000冲突。

{{分页}}
  为了调试内核,需要在编译时加入调试信息,否则将无法看到源代码。修改内核源码根目录下的Makefile,在CFLAGS宏定义的末尾添加“–ggdb”选项,保存退出。这里加上gdb是为了更好的使代码适合gdb调试器,另外注意Makefile中的优化设置,有些时候优化会调整代码执行的顺序,在内核的调试阶段,不要加入优化选项。
  编译:make dep; make zImage

2.1.2 内核调试

  首先,配置BDI2000,确保目标机的正常初始化。通常来说到了调试内核的阶段,板子的boot应该是正常的,所以利用boot来完成目标机的初始化;另一种方式是通过BDI的配置文件来完成。
  接下来就是下载代码进行调试了。如果代码已经固化,那仅下载调试信息给仿真器即可;否则需要把代码下载到RAM里运行,同时下载调试信息给仿真器。本文所用的是后一种方式。
  由于Linux运行之后会启动MMU而使地址重映射,因此第一个断点通常在函数start_kernel( ),而且只能设置为硬断点。硬件断点是非常有限的,有的处理器甚至只能设置一个。所以,在调试Linux内核时,使用普通的GDB进行断点设置会非常不方便。LinuxScope可以很方便的切换断点模式,并支持软断点,使断点的设置不再受到限制,为调试Linux内核提供强有力的支持。具体步骤如下:
1) BDI配置文件的断点模式:soft
2) LinuxScope配置默认的断点模式:soft /hard 都可以
3)用BDI下载压缩的内核:load  0x20000 zImage bin
4) 把PC指针指到内核入口地址:ti 0x30000
5) 运行LinuxScope,在start_kernel处设置硬件断点
6) go,停下来后再设置软断点即可



2.2 模块内核调试

  我们使用BDI2000来调试Linux内核的另外一个重要原因就是它可以支持调试内核模块。内核模块是一些可以让操作系统内核在需要时载入和执行的代码,这意味着它可以在不需要时由操作系统卸载。这种方式可以扩展操作系统内核的功能,而不需要重新启动系统,这一点对调试驱动的工程师特别有用。因为如果驱动程序编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。

2.2.1 调试Linux 2.4内核模块

  2.4内核模块的调试比较简单,使用命令“insmod -m”来加载模块。参数“-m”非常重要,它的功能是在把模块加载到内存时产生一个加载map表。然后通过LinuxScope调试器加载相应的调试信息。例如:
[root@lisl tmp]# insmod -m hello.o >modaddr
查看模块加载信息文件modaddr如下:

  .this           00000060  c88d8000  2**2
  .text           00000035  c88d8060  2**2
  .rodata         00000069  c88d80a0  2**5
  ……
  .data           00000000  c88d833c  2**2
  .bss            00000000  c88d833c  2**2
  ……

  在这些信息中,我们用到的只有.text、.rodata、.data、.bss。当然,把相关的信息输入LinuxScope调试器,它会把以上地址信息加入到gdb中进行模块功能的调试。

  这里需要注意的是对模块进行编译时,也需要增加“-g”选项。
另外,这种方法也存在一定的不足,它不能调试模块初始化的代码,因为此时模块初始化代码已经执行过了。如果不执行模块的加载又无法获得模块插入地址,更不可能在模块初始化之前设置断点了。如果初始化部分有问题,那么将无法进行调试。遇到这样的情况可以修改代码,延迟初始化部分的执行。另外,也可以采用以下替代方法:当插入内核模块时,内核模块机制将调用函数sys_init_module (kernel/modle.c)执行对内核模块的初始化,该函数将调用所插入模块的初始化函数。程序代码片断如下:

  …… ……
if (mod->init != NULL)
 ret = mod->init();
  …… ……

  在该语句上设置断点,也能在执行模块初始化之前停下来。

2.2.2 调试Linux 2.6内核模块

  在Linux 2.6内核系统中,由于module-init-tools工具的更改,insmod命令不再支持-m参数,只有采取其他的方法来获取模块加载到内核的地址。
  比较简单的方式是修改内核配置文件,使系统支持CONFIG_KALLSYMS,这样就可以把相关的符号信息放到目录/sys下,然后通过LinuxScope调试器加载相应的调试信息。
Genernal Steup-→
--- Configure standard kernel features (for small systems) [*] Load all symbols for debugging/ kksymoops
有的系统可能还要:
[*] Include all symbols in kallsyms
   当然也可以分析ELF文件以获得模块加载到内存中的地址。我们知道程序中各段的意义如下:
  .text(代码段):用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。
  .data(数据段):数据段用来存放可执行文件中已初始化全局变量,也就是存放程序静态分配的变量和全局变量。
  .bss(BSS段):BSS段包含了程序中未初始化全局变量,在内存中 bss段全部置零。
  .rodata(只读段):该段保存着只读数据,在进程映象中构造不可写的段。
通过在模块初始化函数中放置一下代码,也可以获得模块加载到内存中的地址,只是这样要麻烦一些。

2.3 应用程序的调试 

  到了应用程序调试的阶段,仿真器就可以“功成身退”了,剩下的调试任务就由LinuxScope调试器来独自完成。此时只要在目标系统中启动“gdbserver”,调试应用程序非常的方便。

3 结语 

  面向行业,应用和设备的嵌入式Linux工具软件和嵌入式Linux操作系统平台是未来发展的必然趋势。跟踪Linux社区的发展,符合标准,遵循开放是大势所趋,人心所向,嵌入式Linux也不例外。Linux的进步为Linux在嵌入式领域的应用广泛性提供了保证。本文所讲述的仿真器技术加商业调试器技术可以极大的提高开发者的效率。

参考文献:
[1] Ultimate Solution, Inc. LinuxScopeUserManual_1.0.0.2007
[2] Peter Jay Salzman. Linux内核驱动模块编程指南. 2003
[3] Karim Yaghmour. Building Embedded Linux Systems. 2003
[4] Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman. Linux Device Drivers, Third Edition. 2005

linux操作系统文章专题:linux操作系统详解(linux不再难懂)

linux相关文章:linux教程




评论


相关推荐

技术专区

关闭