嵌入式linux调试技巧

时间:2021-09-01 20:17:26

一、printk

1、打印往往是最常用的调试技巧。

调试内核和驱动都可以采用printk。在Kernel.h (include\linux)中定义了log的等级。

未指定日志级别的 printk() 采用的默认级别是 DEFAULT_MESSAGE_LOGLEVEL,这个宏在kernel/printk.c 中被定义为整数 4,即对应KERN_WARNING。在 /proc/sys/kernel/printk 会显示4个数值(可由 echo 修改),分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最小(最高)允许设置的控制台日志级别、引导时默认的日志级别。当 printk() 中的消息日志级别小于当前控制台日志级别时,printk 的信息(要有\n符)就会在控制台上显示。但无论当前控制台日志级别是何值,通过 /proc/kmsg (或使用dmesg)总能查看。另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/messages.log 中。

#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>" /* debug-level messages

2、打印的东西在哪输出?

uboot中设置了命令行参数:

set bootargs console=ttySAC0,115200   root=/dev/mtdblock3  

正是console环境决定了printk的输出地方。

3、如何利用printk调试?

在程序中合适的地方加入printk()函数,当无法正常输出的时候,认定此处可能存在bug。

printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);


二、内核Oops信息

1、定位位置(模块还是内核)

通过Oops查看PC值pc = 0x00000000 它属于什么的地址?

是内核的地址,还是通过insmod加载的驱动程序的地址?先判断是否属于内核的地址 : 

看内核编译makefile目录下的 System.map(编译完内核都会发现在内核根目录下面多出来一个System.map文件)确定内核的函数的地址范围 : c0004000~c03faa94。

所以可以确定 : 导致错误的指令不在内核的地址范围,则它属于insmod加载的驱动程序的地址范围。

Modules linked in: 会指明是哪个驱动程序,但是很多时候加载的驱动程序很多,是不会指明具体是哪个。所以还是需要根据PC值来确定究竟是哪个驱动程序。先看看加载的驱动程序的地址范围。在开发板目录下 : cat /proc/kallsyms >> kallsyms.txt (内核函数的地址、加载的函数的地址)kallsyms.txt文件中的内容介绍 //T : 表示全局函数 t : 表示静态函数。

模块:

  1. cat /proc/kallsyms >> kallsyms.txt找一个相近的
  2. arm-linux-objdump -D lcd.ko > lcd.dis 反汇编
  3. 根据PC定位那一句出现了问题

内核:

  1. arm-linux-objdump -D vmlinux > vmlinux.dis反汇编内核
  2. 搜索PC值

(1)oops信息的序号,#1,表示是第1次。
Internal error: Oops: 5 [#1]
(2)内核中加载的模块的名称

Modules linked in: first_drv
(3)发生错误时, CPU的序号,对于单处理器系统,序号为0。
CPU: 0    Not tainted  (2.6.22.6 #1)
(4) PC就是发生错误时,指令的地址。大多时候,PC值只会给出一个地址,不会指示说是在哪个函数里面。
PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]
(5) LR寄存器的值。
LR is at chrdev_open+0x14c/0x164
LR寄存器的值
(6)发生错误时,CPU各个 寄存器的值
pc = 0xbf000018pc : []    lr : []    psr: a0000013
sp : c3c7be88 ip : c3c7be98 fp : c3c7be94
r10: 00000000 r9 : c3c7a000 r8 : c049abc0
r7 : 00000000 r6 : 00000000 r5 : c3e740c0 r4 : c06d41e0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
执行这条导致错误的指令时各个寄存器的值
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32 Segment user
Control: c000717f  Table: 33eb0000  DAC: 00000015
(7)发生错误时,当前进程是它,并不是说发生错误的是这个进程
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
发生错误时当前进程的名称是firstdrvtest
(8) 栈信息
Stack: (0xc3c7be88 to 0xc3c7c000)be80:                  c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0 
bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0c0089e48 c008d74c
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000c3c7befc c3c7bee8
(9)栈回溯信息,可以从中看出函数调用关系:从最后一个函数 sys_init_module 开始,向上可以找到函数调用的关系。
可以通过内核配置信息 make menuconfig 来指定是否输出栈回溯信息。
Backtrace: (回溯)
[] (first_drv_open+0x0/0x3c [first_drv]) from [](chrdev_open+0x14c/0x164)[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0r4:c049abc0
[] (__dentry_open+0x0/0x1e8) from [](nameidata_to_filp+0x34/0x48)
[] (nameidata_to_filp+0x0/0x48) from [](do_filp_open+0x40/0x48)
r4:00000002
[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
r5:bec1fee0 r4:00000002
[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault