嵌入式linux调试技巧

2019-07-12 14:17发布

一、printk

1、打印往往是最常用的调试技巧。 调试内核和驱动都可以采用printk。在Kernel.h (includelinux)中定义了log的等级。 未指定日志级别的 printk() 采用的默认级别是 DEFAULT_MESSAGE_LOGLEVEL,这个宏在kernel/printk.c 中被定义为整数 4,即对应KERN_WARNING。在 /proc/sys/kernel/printk 会显示4个数值(可由 echo 修改),分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最小(最高)允许设置的控制台日志级别、引导时默认的日志级别。当 printk() 中的消息日志级别小于当前控制台日志级别时,printk 的信息(要有 符)就会在控制台上显示。但无论当前控制台日志级别是何值,通过 /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 ", __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 = 0xbf000018 pc : [] 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