APP下载

Linux 内核调试技术的方法研究

2012-09-12洪永学余红英姜世杰林丽蓉

电子测试 2012年11期
关键词:驱动程序内核寄存器

洪永学, 余红英, 姜世杰, 林丽蓉

(中北大学信息与通信工程学院, 山西太原 030051)

0 引言

Linux操作系统最大的优势在于其源码的开放性,人们可以根据应用的需要对其内核驱动进行必要的裁剪或修改,但是由于内核是整个计算机软件系统的基础,其与无操作系统的调试有着很大的差异。调试中所获得的错误信息也不同,尤其是涉及到Oops信息的段错误。为了能有效地调试Linux内核,人们采用了很多方法,下面介绍两种常用的Linux内核调试方法。

1 内核打印函数printk调试

1.1 printk函数的记录级别

调试内核、驱动是最简单,最常用,也是效率较高的方法,是使用printk函数打印信息。printk函数与用户空间的printf函数的格式完全相同,它所打印的字符串头部可以加入""样式的字符,其中n为0~7,表示这条信息的记录级别。

在内核代码include/linux/kernel.h中,下面几个宏控制了printk函数所输出的信息记录级别。

#define console_loglevel (console_printk[0])#define default_message_loglevel (console_printk[1])

#define minimum_console_loglevel (console_printk[2])

#define default_console_loglevel (console_printk[3])

1.2 在用户空间修改printk函数的记录级别

挂接在proc文件系统后,读取/proc/sys/kernel/printk文件可以得知console_loglevel,fault_message_loglevel,minimum_console_loglevel 和default_console_loglevel这个4个值。

可以修改/proc/sys/kernel/printk文件来改变这4个值,比如:

# echo "1 4 1 7" > /proc/sys/kernel/printk

这使得console_loglevel被改为1,于是所有的printk信息都不会被打印。

1.3 printk函数记录级别的名称及使用

在内核代码include/linux/kernel.h中有如下代码,它们表示0~7这个8个记录级别的名称。

#define KERN_EMERG "<0>" /* system is unusable */

#define KERN_ALERT "<1>" /* action must be taken immediately */

#define KERN_CRIT "<2>" /* critical conditions */

#define KERN_ERR "<3>" /* error conditions */

#define KERN_WARNING "<4>" /*warning conditions */

#define KERN_NOTICE "<5>" /*normal but significant condition */

#define KERN_INFO "<6>" / *informational */

#define KERN_DEBUG "<7>" /* debuglevel messages */

在使用printk函数时,可以这样的使用记录级别。

printk(KERN_WARNING"there is a warning here ! ")

在被调试的内核中相应的位置加入printk函数,可以根据串口中打印出的信息获得其错误的位置从而实施相应的解决方法。

2 Oops信息及栈回溯的调试

在进行Linux内核驱动调试时,经常会出现Segmentation fault错误信息,其大部分都是NULL指针引用或使用其他不正确的指针数值。这些错误通常会导致一个Oops消息。由于处理器使用的地址都是“虚”地址,而且通过一个复杂的称为页表的结构映射为物理地址。当引用一个非法指针时,页面映射机制就不能将”虚”地址映射到物理地址,因此处理器向操作系统发出一个“页面失效”。如果地址确实是非法的,内核就无法从失效地址上“换页”;如果此时处理器工作在超级用户态,系统就会产生一个“Oops”消息。Oops显示故障时的处理器状态,模块CPU寄存器内容,页描述符表的位置,以及其他较为难以理解的信息。利用ksymoopsJ工具可以将这些十六进制数据解析为内核符号,然后再进一步判断错误的所在。

2.1 Oops信息来源及格式

Oops这个单词含义为“惊讶”,当内核出错时(比如访问非法地址)打印出来的信息被称为Oops信息。 Oops信息包含以下几部分内容:

(1)一段文本描述信息。比如类似”Unable to handle kernel NULL pointer dereference at virtual address 00000000”的信息,他说明了发生的是哪类错误。

(2)Oops信息的序号。比如是第几次等。这些信息与下面类似,括号内的数据表示序号。

Internal error: Oops: 806 [#1]

(3)内核中加载的模块名称,也可能没有,以下面字样开头。

Modules linked in:

(4)发生错误的CPU的序号,对于单处理器系统,序号为0,如:

CPU: 0 Not tainted (2.6.22.6 #36)

(5)发生错误时CPU的各个寄存器值。

(6)当前进程的名字及进程ID,比如:

Process swapper (pid: 1, stack limit =0xc0480258)

这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发生在内核代码、驱动程序,也可能就是这个进程的错误。

(7)栈信息。

(8)栈回溯信息,可以从中看出函数调用关系,形式如下:

Backtrace:

[](s3c2410fb_probe+0x0/0x560)from [](platform_drv_probe+0x20/0x24)

(9)出错指令附近的指令机器码,比如(出错指令在小括号内):

Code: e24cb004 e24dd010 e59f34e0 e3a07000(e5873000)

2.2 使用 Oops 的栈信息手工进行栈回溯

从 Oops 信息的 PC寄存器值可知得知崩溃发生时的函数、出错指令。但是错误有可能是它的调用者引入的,所以还要找出函数的调用关系。由于内核配置了 CONFIG_FRAME_POINTER,当出现 Oops 信息时,会打印栈回溯信息。如果内核没有配置 CONFIG_FRAME_POINTER,这时可以自己分析栈信息,找到函数的调用关系。

2.3 栈的作用

一个程序包含代码段、数据段、BSS 段、堆、栈;其中数据段用来中存储初始值不为 0 的全局数据,BSS 段用来存储初始值为 0 的全局数据,堆用于动态内存分配,栈用于实现函数调用、存储局部变量。被调用函数在执行之前,它会将一些寄存器的值保存在栈中,其中包括返回地址寄存器lr。如果知道了所保存的 lr 寄存的值,那么就可以知道它的调用者是谁。在栈信息中,一个函数一个函数地往上找出所有保存的 lr 值,就可以知道各个调用函数,这就是栈回溯的原理。

3 栈回溯实例分析

故意修改 LCD 驱动程序 drivers/video/s3c2410fb.c,加入错误代码:在 s3c2410fb_ probe函数的开头增加下面两条代码:

根据 pc 寄存器值找到第一个函数,确定它的栈大小,确定调用函数。

从 Oops 信息 :

可知 pc 值为 c0018f78,使用它在内核反汇编程序 vmlinux.dis 中可以知道它位于 s3c2410fb_probe 函数内。

lr存放的是函数返回值地址,为c01d3f88,根据这个地址,搜索内核反汇编程序 vmlinux.dis ,可知它位于:

也就是说,函数s3c2410fb_probe() 被platform_drv_probe()调用。再看platform_drv_probe()的反汇编代码,其中c01d3f70: e92d4010 push {r4, lr} ,栈中存放的是 r4, lr 对应(查看上文中的栈的信息) ,其中,lr对应的值为c01d2e7c,用此值检索vmlinux.dis,位于

可知,platform_drv_probe()被driver_probe_device()调用,再用同样的方法就可以找出所有函数调用关系。 从而就能找到导致Segmentation fault错误的语句。

4 结论

在Linux内核调试技术中,由于printk函数调试的使用方法简单,分析问题效率较高,因此内核打印函数printk是众多开发者所最喜爱的一种调试技术。但在驱动开发的情况下出现的错误几乎都会涉及到段错误,所以掌握通过Oops信息使用栈回溯技术在Linux嵌入式驱动开发中变得不可或缺的一项调试技能。

[1]毛德操,胡希明.LINUX内核代码情景分析[M].杭州:浙江大学出版社,2001.

[2]Rubimi A.Linux设备驱动程序[M].聊鸿斌译.北京:中国电力出版社,1999.

[3]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2010.

[4]何绍然,史海滨,吴国成,宋宝华.精通linux设备驱动程序开发[M].北京:人民邮电出版社,2010.

[5]李红卫,李翠萍.kgdb调试Linux内核的剖析与改进[J].微型机与应用,2004(10):7-10.

[6]张磊,王学慧.Linux内核调试技术[J].计算机工程,2003,29(10):8l-83.

[7]孙悦红.编译原理及实现[M].北京:清华大学出版社,2005:14-17.

[8]李善平,刘文峰.Linux内核2.4版源代码分析大全[M].北京:机械工业出版社,2002.

猜你喜欢

驱动程序内核寄存器
STM32和51单片机寄存器映射原理异同分析
强化『高新』内核 打造农业『硅谷』
Lite寄存器模型的设计与实现
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
计算机硬件设备驱动程序分析
微生物内核 生态型农资
基于MPC8280的CPU单元与内部总线驱动程序设计
高速数模转换器AD9779/AD9788的应用
一种可重构线性反馈移位寄存器设计