1、内核调试配置选项

内核拥有多项用于调试的功能,但是这些功能会造成额外的输出并导致性能下降,因此,内核通常都是禁止掉调试功能。
内核调试相关的配置项主要集中在内核配置菜单"Kernel hacking"中,在使用下面的调试手段时,先确保内核相关的调试
配置项已经开启。

2、BUG()和BUG_ON()

#ifndef HAVE_ARCH_BUG
#define BUG() do { \
	printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \
	panic("BUG!"); \
} while (0)
#endif

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
#endif

当调用BUG()时,内核通过panic()引发OOPS,导致函数调用栈的回溯和打印错误消息,可以把这两个调用当做断言使用,如BUG_ON(bad_thing);

3、dump_strack()函数

if(!debug_check)
{
	printk(KERN_DEBUG "provide some information···\n");
	dump_strack();
}

调用dump_stack()函数会在终端打印栈的回溯消息,有助于调试。

4、printk()函数

常用的调试手段,具体参考博客:《修改内核printk函数打印等级》《printk()和printf()的比较》

5、dmesg命令

在终端输入dmesg命令会打印出内核从启动开始的输出打印,根据输出信息定位问题;

6、使用strace跟踪系统调用

6.1、strace使用示例

内核的strict_devmem关掉_内核的strict_devmem关掉

(1)strace命令常用来跟踪进程执行时的系统调用(参数、返回值、执行消耗等)和接收的信号;
(2)输出参数含义:每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。

6.2、strace常用参数

-c 统计每个系统调用的执行时间、运行次数、出错的次数等;
-d 输出strace关于标准错误的调试信息;
-f 跟踪由fork调用产生的子进程;
-t 在输出中的每一行前加上时间信息;
-tt 在输出的每一行前加上微秒级时间信息;
-T 显示每个调用消耗的时间;
-o filename 将strace的输出写入文件filename;
-p pid 跟踪指定的pid进程
-v 输出所有的系统调用信息,一些关于环境变量、状态的信息默认不输出

7、使用OOPS调试系统故障

7.1、OOPS介绍

Linux内核在发生kernel panic时会打印出Oops信息,把目前的寄存器状态、堆栈内容、以及完整的Call trace都show给我们看,这样就可以帮助我们定位错误;

7.2、oops示例

7.2.1、有问题的驱动代码

#include <lim』x/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

void func_B(void)
{
	printk (” Function B\ n");
	*(int*)0 = 0; //出错的地方
}

void func_A(void)
{
	printk("Function A\n");
	func_B();
}

int oops_init(void)
{
	printk("oops init\n");
	func_A();
	return 0;
}

void oops_exit()
{
	printk("oops exit!\n");
}

module_init(oops_init);
mdoule_exit(oops_exit);

7.2.2、OOPS输出

oops init
Function A
Function B
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c39d8000
[00000000] *pgd=339cf031, *pte=OOOOOOOO, *ppte=OOOOOOOO
Internal error: Oops: 817 [#工]
last sysfs file: /sys/devices/platform/soc-audio/sound/cardO/mixer/dev
Modules linked in: oops(+)
CPU: 0 Not tainted (2.6.32.2-GQ2440 US)
PC is at func_B+Oxl8/0x20 [oops]
LR is at release console sem+Oxlbc/Ox214
pc : [<bf000038>) lr : [<c004b7a4>) psr: 60000013
sp : c39d5ee0 ip : c39d5el0 fp : c39d5eec
rlO: 00000000 r9 : c05096cc r8 : bf0000a4
r7 : c39d4000 r6 : 00ldbfd8 r5 : bf000134 r4 : 000009c9
r3 : 00000000 r2 : OOOOOOOb rl : 00004e5e rO : OOOOOOOe
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: c000717f Table: 339d8000 DAC: 00000015
Process insmod (pid: 716, stack limit = Oxc39d4270)
Stack: (Oxc39d5ee0 to Oxc39d6000)
5ee0: c39d5efc c39d5ef0 bf00005c bf00002c c39d5f0c c39d5f00 bf00007c bf000054
5f00: c39d5flc c39d5fl0 bf00009c bf000074 c39d5f2c c39d5f20 bfOOOObc bf000094
Backtrace:
[<bf000064>] (func_B+Ox0/0x20 [oops)) from [<bf00009c>] (func_A+Oxl8/0x20 [oops])
[<bf000084>] (func_A+Ox0/0x20 [oops]) from [<bf0000bc>) (init_module+Oxl8/0x24 [oops))
(<bf0000a4>] (init module+Ox0/0x24 [oo ps)) from (< c003132c>J (do_ one_
initeal! +Ox3c/ OxldO)
[<c00312f0>) (do_ o ne_ i nit cal l +OxO / OxldOJ from [<c0070f24 > ) (s ys_i nit_
module+Oxcc/Ox200)
[<c0070e58>] (sys init module+Ox0/0x200) from [<c0031ec0>] (ret fast
syscall+Ox0/0x28)
r7:00000080 r6:00000000 r5:bec59e68 r4:00000000
Code: e59f0010 eb412 f b6 e3a0200b e3a03000 (e5832000)
--『[ end trace 4e70d5baadf4ccd0 ]---
Segmentation fault

(1)内核提示的第四行"Unable to handle kernel······",很明显是访问了空指针而出错;
(2)“PC is at func_B+Oxl8/0x20 [oops]”:它表示出错时,处理器的PC指针在函数func_B的0xl8偏移处(0x20是函数代码段的总大小),可以推测出错的
地方实在func_B函数的末尾部分代码;
(3)其余的信息包括出错时处理器的寄存器、状态和栈内容,看懂这些信息需要清除处理器各个寄存器的作用,已经函数调用时栈是如何分配的;

8、使用gdb

(1)使用gdb调试内核就是把内核当做一个应用程序,首先要掌握gdb命令、了解目标平台的汇编代码,具备对源代码和优化后的汇编代码进行匹配的能力;
(2)为了让gdb使用内核的符号信息,必须打开CONFIG_DEBUG_INFO编译选项;
(3)内核需要未压缩的内核镜像文件,还需要在命令行提供core文件的命令。例如:gdb /usr/src/linux/vmlinux /proc/kcore

9、使用kdb、Linux跟踪工具包、动态探测

这些调试手段一点不熟悉,暂时也用不到,都是比较底层的调试手段,小白是用不上了,这里只是记录下存在这些调试手段。