1、Oops 信息来源及格式

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

 

5.打印到proc虚拟文件

2.1 一段文本描述信息。

比如类似“Unable to handle kernel NULL pointer dereference at virtual
address 00000000”的信息,它说明了发生的是哪类错误。

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <poll.h>
  6 #include <signal.h>
  7 #include <sys/types.h>
  8 #include <unistd.h>
  9 #include <fcntl.h>
 10 #include <stdlib.h>
 11 #include <string.h>
 12 
 13 #define KER_RW_R8      0
 14 #define KER_RW_R16     1
 15 #define KER_RW_R32     2
 16 
 17 #define KER_RW_W8      3
 18 #define KER_RW_W16     4
 19 #define KER_RW_W32     5
 20 
 21 
 22 /* Usage:
 23  * ./regeditor r8  addr [num]
 24  * ./regeditor r16 addr [num]
 25  * ./regeditor r32 addr [num]
 26  *
 27  * ./regeditor w8  addr val
 28  * ./regeditor w16 addr val 
 29  * ./regeditor w32 addr val
 30  */
 31 
 32 void print_usage(char *file)
 33 {
 34     printf("Usage:\n");
 35     printf("%s <r8 | r16 | r32> <phy addr> [num]\n", file);
 36     printf("%s <w8 | w16 | w32> <phy addr> <val>\n", file);
 37 }
 38 
 39 int main(int argc, char **argv)
 40 {
 41     int fd;
 42     unsigned int buf[2];
 43     unsigned int i;
 44     unsigned int num;
 45     
 46     if ((argc != 3) && (argc != 4))
 47     {
 48         print_usage(argv[0]);
 49         return -1;
 50     }
 51 
 52     fd = open("/dev/ker_rw", O_RDWR);
 53     if (fd < 0)
 54     {
 55         printf("can't open /dev/ker_rw\n");
 56         return -2;
 57     }
 58 
 59     /* addr */
 60     buf[0] = strtoul(argv[2], NULL, 0);
 61 
 62     if (argc == 4)
 63     {
 64         buf[1] = strtoul(argv[3], NULL, 0);
 65         num    = buf[1];
 66     }
 67     else
 68     {
 69         num = 1;
 70     }
 71 
 72     if (strcmp(argv[1], "r8") == 0)
 73     {
 74         for ( i = 0; i < num; i++)
 75         {
 76             ioctl(fd, KER_RW_R8, buf);  /* val = buf[1] */
 77             printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]);
 78             buf[0] += 1;
 79         }
 80     }
 81     else if (strcmp(argv[1], "r16") == 0)
 82     {
 83         for ( i = 0; i < num; i++)
 84         {
 85             ioctl(fd, KER_RW_R16, buf);  /* val = buf[1] */
 86             printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned short)buf[1]);
 87             buf[0] += 2;
 88         }
 89     }
 90     else if (strcmp(argv[1], "r32") == 0)
 91     {
 92         for ( i = 0; i < num; i++)
 93         {
 94             ioctl(fd, KER_RW_R32, buf);  /* val = buf[1] */
 95             printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned int)buf[1]);
 96             buf[0] += 4;
 97         }
 98     }
 99     else if (strcmp(argv[1], "w8") == 0)
100     {
101         ioctl(fd, KER_RW_W8, buf);  /* val = buf[1] */
102     }
103     else if (strcmp(argv[1], "w16") == 0)
104     {
105         ioctl(fd, KER_RW_W16, buf);  /* val = buf[1] */
106     }
107     else if (strcmp(argv[1], "w32") == 0)
108     {
109         ioctl(fd, KER_RW_W32, buf);  /* val = buf[1] */
110     }
111     else
112     {
113         printf(argv[0]);
114         return -1;
115     }
116 
117     return 0;
118     
119 }

3、配置内核使 Oops 信息的栈回溯信息更直观

可以通过配置 CONFIG_FRAME_POINTER 来实现。

 

    printk(KERN_DEBUG”%s %s %d\n”, __FILE__, __FUNCTION__,
__LINE__);
//将打印出 绝对路径,函数,行数

2.6 当前进程的名字及进程 ID

比如:
Process swapper (pid: 1, stack limit = 0xc0480258)
这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发
生在内核代码、驱动程序,也可能就是这个进程的错误。

可以直接使用myprintk,然后用cat /proc/mymsg指令查到我们的打印信息。

2、Oops 信息包含以下几部分内容

进一步优化

4、实例

使用 Oops 信息调试内核的实例获得 Oops 信息本小节故意修改 LCD 驱动程序
drivers/video/s3c2410fb.c,加入错误代码:在 s3c2410fb_probe
函数的开头增加下面两条代码:int *ptest = NULL;*ptest =
0x1234;重新编译内核,启动后会出错并打印出如下 Oops 信息:

 1 Unable to handle kernel NULL pointer dereference at virtual address 00000000
 2 pgd = c0004000
 3 [00000000] *pgd=00000000
 4 Internal error: Oops: 805 [#1]
 5 Modules linked in:
 6 CPU: 0
 7 Not tainted (2.6.22.6 #36)
 8 PC is at s3c2410fb_probe+0x18/0x560
 9 LR is at platform_drv_probe+0x20/0x24
10 pc : [<c001a70c>]
11 lr : [<c01bf4e8>]
12 psr: a0000013
13 sp : c0481e64 ip : c0481ea0 fp : c0481e9c
14 r10: 00000000 r9 : c0024864 r8 : c03c420c
15 r7 : 00000000 r6 : c0389a3c r5 : 00000000 r4 : c036256c
16 r3 : 00001234 r2 : 00000001 r1 : c04c0fc4 r0 : c0362564
17 Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment kernel
18 Control: c000717f Table: 30004000 DAC: 00000017
19 Process swapper (pid: 1, stack limit = 0xc0480258)
20 Stack: (0xc0481e64 to 0xc0482000)
21 1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c
22 1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704
23 1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c
24 1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14
25 1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c
26 1f00: c0389a44 c038d9dc c0481f24 c0481f18 c01bd808 c01bc568 c0481f4c c0481f28
27 1f20: c01bcd78 c01bd7f8 c0389a3c 00000000 00000000 c0480000 c0023ac8 00000000
28 1f40: c0481f60 c0481f50 c01bdc84 c01bcd0c 00000000 c0481f70 c0481f64 c01bf5fc
29 1f60: c01bdc14 c0481f80 c0481f74 c019479c c01bf5a0 c0481ff4 c0481f84 c0008c14
30 1f80: c0194798 e3c338ff e0222423 00000000 00000001 e2844004 00000000 00000000
31 1fa0: 00000000 c0481fb0 c002bf24 c0041328 00000000 00000000 c0008b40 c00476ec
32 1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
33 1fe0: 00000000 00000000 00000000 c0481ff8 c00476ec c0008b50 c03cdf50 c0344178
34 Backtrace:
35 [<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_
36 probe+0x20/0x24)
37 [<c01bf4c8>] (platform_drv_probe+0x0/0x24) from [<c01bd5a8>] (driver_probe_
38 device+0xe8/0x18c)
39 [<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_
40 attach+0x80/0xe0)
41 r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644
42 [<c01bd708>] (_ _driver_attach+0x0/0xe0) from [<c01bc5a8>] (bus_for_each_
43 dev+0x50/0x84)
44 r5:c0481eec r4:00000000
45 [<c01bc558>] (bus_for_each_dev+0x0/0x84) from [<c01bd808>] (driver_attach+
46 0x20/0x28)
47 r7:c038d9dc r6:c0389a44 r5:c0389a3c r4:00000000
48 [<c01bd7e8>] (driver_attach+0x0/0x28) from [<c01bcd78>] (bus_add_driver+
49 0x7c/0x1b4)
50 [<c01bccfc>] (bus_add_driver+0x0/0x1b4) from [<c01bdc84>] (driver_register+
51 0x80/0x88)
52 [<c01bdc04>] (driver_register+0x0/0x88) from [<c01bf5fc>] (platform_driver_
53 register+0x6c/0x88)
54 r4:00000000
55 [<c01bf590>] (platform_driver_register+0x0/0x88) from [<c019479c>] (s3c2410fb_
56 init+0x14/0x1c)
57 [<c0194788>] (s3c2410fb_init+0x0/0x1c) from [<c0008c14>] (kernel_init+0xd4/
58 0x28c)
59 [<c0008b40>] (kernel_init+0x0/0x28c) from [<c00476ec>] (do_exit+0x0/0x760)
60 Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)
61 Kernel panic - not syncing: Attempted to kill init!

 

 (1)明确出错原因。
由出错信息“Unable to handle kernel NULL pointer dereference at virtual
address 00000000”
可知内核是因为非法地址访问出错,使用了空指针。
(2)根据栈回溯信息找出函数调用关系。
内核崩溃时,可以从 pc
寄存器得知崩溃发生时的函数、出错指令。但是很多情况下,错
误有可能是它的调用者引入的,所以找出函数的调用关系也很重要。
部分栈回溯信息如下:
[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from
[<c01bf4e8>] (platform_drv_
probe+0x20/0x24)
这行信息分为两部分,
表示后面的 platform_drv_probe 函数调用了前面的 s3c2410fb_probe
函数。
前半部含义为:
“c001a6f4”是 s3c2410fb_probe 函数首地址偏移 0 的地址,这个函数大
小为 0x560。
后半部含义为:
“c01bf4e8”是 platform_drv_probe 函数首地址偏移 0x20 的地址,这个函
数大小为 0x24。
另外,后半部的“[<c01bf4e8>]”表示 s3c2410fb_probe
执行后的返回地址。
对于类似下面的栈回溯信息,其中是 r8~r4 表示 driver_probe_device
函数刚被调用时这
些寄存器的值。
[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from
[<c01bd788>] (__driver_
attach+0x80/0xe0)
r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,
最后在 s3c2410fb_probe
函数内部崩溃。
do_exit ->
kernel_init ->
s3c2410fb_init ->
platform_driver_register ->
driver_register ->
bus_add_driver ->
driver_attach ->
bus_for_each_dev ->
__driver_attach ->
driver_probe_device ->
platform_drv_probe ->
s3c2410fb_probe
(3)根据 pc 寄存器的值确定出错位置。
上述 Oops 信息中出错时的寄存器值如下:
PC is at s3c2410fb_probe+0x18/0x560
LR is at platform_drv_probe+0x20/0x24
pc : [<c001a70c>]
lr : [<c01bf4e8>]
psr: a0000013

“PC is at s3c2410fb_probe+0x18/0x560”表示出错指令为 s3c2410fb_probe
函数中偏移为
0x18 的指令。
“pc : [<c001a70c>]”表示出错指令的地址为 c001a70c(十六进制)。
(4)结合内核源代码和反汇编代码定位问题。
先生成内核的反汇编代码 vmlinux.dis,执行以下命令:
$ cd /work/system/linux-2.6.22.6
$ arm-linux-objdump -D vmlinux > vmlinux.dis
出错地址 c001a70c 附近的部分汇编代码如下:
c001a6f4 <s3c2410fb_probe>:
c001a6f4: e1a0c00d mov ip, sp
c001a6f8: e92ddff0 stmdb
c001a6fc: e24cb004 sub fp, ip, #4 ; 0x4
c001a700: e24dd010 sub sp, sp, #16 ; 0x10
c001a704: e59f34e0 ldr r3, [pc, #1248] ; c001abec
<.init+0x1284c>
c001a708: e3a07000 mov r7, #0
c001a70c: e5873000 str r3, [r7]
c001a710: e59030fc ldr r3, [r0, #252]
sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
; 0x0
<===========出错指令
出错指令为“str r3, [r7]”
,它把 r3 寄存器的值放到内存中,内存地址为 r7 寄存器的值。
根据 Oops 信息中的寄存器值可知:r3 为 0x00001234,r7 为 0。0
地址不可访问,所以出错。
s3c2410fb_probe 函数的部分 C 代码如下:
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info;
struct fb_info
*fbinfo;
struct s3c2410fb_hw *mregs;
int ret;
int irq;
int i;
u32 lcdcon1;
int *ptest = NULL;
*ptest = 0x1234;
mach_info = pdev->dev.platform_data;
结合反汇编代码,很容易知道是“*ptest = 0x1234;”导致错误,其中的 ptest
为空。
对于大多数情况,从反汇编代码定位到 C
代码并不会如此容易,这需要较强的阅读汇编
程序的能力。通过栈回溯信息知道函数的调用关系,这已经可以帮助定位很多问题了。

 

  set bootargs loglevel=10                      //打印所有东西
  执行dmsg本质是读取/proc/kmsg文件

2.4 发生错误的 CPU 的序号。

对于单处理器的系统,序号为 0,比如:CPU: 0Not tainted (2.6.22.6 #36)

输出信息和测试

2.7 栈信息。

我们在命令行输入:cat
/proc/interrupts

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

Backtrace:[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from
[<c01bf4e8>] (platform_drv_probe+0x20/0x24)

 

2.2 Oops 信息的序号。

比如是第 1 次、第 2
次等。这些信息与下面类似,中括号内的数据表示序号。Internal error: Oops:
805 [#1]

 

2.5 发生错误时 CPU 的各个寄存器值。

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/fs.h>
  4 #include <linux/init.h>
  5 #include <linux/delay.h>
  6 #include <linux/irq.h>
  7 #include <asm/uaccess.h>
  8 #include <asm/irq.h>
  9 #include <asm/io.h>
 10 #include <asm/arch/regs-gpio.h>
 11 #include <asm/hardware.h>
 12 #include <linux/poll.h>
 13 #include <linux/device.h>
 14 
 15 #define KER_RW_R8      0
 16 #define KER_RW_R16     1
 17 #define KER_RW_R32     2
 18 
 19 #define KER_RW_W8      3
 20 #define KER_RW_W16     4
 21 #define KER_RW_W32     5
 22 
 23 
 24 static int major;
 25 static struct class *class;
 26 static struct class_device    *ker_dev;
 27 
 28 
 29 static int ker_rw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 30 {
 31     volatile unsigned char  *p8;
 32     volatile unsigned short *p16;
 33     volatile unsigned int   *p32;
 34     unsigned int val;
 35     unsigned int addr;
 36 
 37     unsigned int buf[2];
 38 
 39     copy_from_user(buf, (const void __user *)arg, 8);
 40     addr = buf[0];
 41     val  = buf[1];
 42     
 43     p8  = (volatile unsigned char *)ioremap(addr, 4);
 44     p16 = p8;
 45     p32 = p8;
 46 
 47     switch (cmd)
 48     {
 49         case KER_RW_R8:
 50         {
 51             val = *p8;
 52             copy_to_user((void __user *)(arg+4), &val, 4);
 53             break;
 54         }
 55 
 56         case KER_RW_R16:
 57         {
 58             val = *p16;
 59             copy_to_user((void __user *)(arg+4), &val, 4);
 60             break;
 61         }
 62 
 63         case KER_RW_R32:
 64         {
 65             val = *p32;
 66             copy_to_user((void __user *)(arg+4), &val, 4);
 67             break;
 68         }
 69 
 70         case KER_RW_W8:
 71         {
 72             *p8 = val;
 73             break;
 74         }
 75 
 76         case KER_RW_W16:
 77         {
 78             *p16 = val;
 79             break;
 80         }
 81 
 82         case KER_RW_W32:
 83         {
 84             *p32 = val;
 85             break;
 86         }
 87     }
 88 
 89     iounmap(p8);
 90     return 0;
 91 }
 92 
 93 static struct file_operations ker_rw_ops = {
 94     .owner   = THIS_MODULE,
 95     .ioctl   = ker_rw_ioctl,
 96 };
 97 
 98 static int ker_rw_init(void)
 99 {
100     major = register_chrdev(0, "ker_rw", &ker_rw_ops);
101 
102     class = class_create(THIS_MODULE, "ker_rw");
103 
104     /* 为了让mdev根据这些信息来创建设备节点 */
105     ker_dev = class_device_create(class, NULL, MKDEV(major, 0), NULL, "ker_rw"); /* /dev/ker_rw */
106     
107     return 0;
108 }
109 
110 static void ker_rw_exit(void)
111 {
112     class_device_unregister(ker_dev);
113     class_destroy(class);
114     unregister_chrdev(major, "ker_rw");
115 }
116 
117 module_init(ker_rw_init);
118 module_exit(ker_rw_exit);
119 
120 
121 MODULE_LICENSE("GPL");

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

Modules linked in:

3.2 确定它属于哪个函数
反汇编first_drv.ko

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

Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)

 

所以我们可以仿照该文件写出我们自己的mymsg

5.1 proc机制分析

b. 编入内核
Modules linked in:
CPU: 0    Not tainted  (2.6.22.6 #2)
PC is at first_drv_open+0x18/0x3c
LR is at chrdev_open+0x14c/0x164
pc : [<c014e6c0>]    lr : [<c008638c>]    psr:
a0000013
sp : c3a03e88  ip : c3a03e98  fp : c3a03e94
r10: 00000000  r9 : c3a02000  r8 : c03f3c60
r7 : 00000000  r6 : 00000000  r5 : c38a0c50  r4 : c3c1e780
r3 : c014e6a8  r2 : 56000050  r1 : c031a47c  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 339f0000  DAC: 00000015
Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)

看/proc/kallsyms                                
first_drv.dis
00000000 <first_drv_open>:                     bf000000 t
first_drv_open    [first_drv]         
0000003c <first_drv_write>:
  3c:    e1a0c00d     mov    ip, sp
  40:    e92dd800     stmdb    sp!, {fp, ip, lr, pc}
  44:    e24cb004     sub    fp, ip, #4    ; 0x4
  48:    e24dd004     sub    sp, sp, #4    ; 0x4
  4c:    e3cd3d7f     bic    r3, sp, #8128    ; 0x1fc0
  50:    e3c3303f     bic    r3, r3, #63    ; 0x3f
  54:    e5933008     ldr    r3, [r3, #8]
  58:    e0910002     adds    r0, r1, r2
  5c:    30d00003     sbcccs    r0, r0, r3
  60:    33a03000     movcc    r3, #0    ; 0x0
  64:    e3530000     cmp    r3, #0    ; 0x0
  68:    e24b0010     sub    r0, fp, #16    ; 0x10
  6c:    1a00001c     bne    e4 <init_module+0x5c>
  70:    ebfffffe     bl    70 <first_drv_write+0x34>
  74:    ea00001f     b    f8 <init_module+0x70>
  78:    e3520000     cmp    r2, #0    ; 0x0
  7c:    11a01002     movne    r1, r2
  80:    1bfffffe     blne    80 <first_drv_write+0x44>       //
卡死的地方
  84:    ea00001f     b    108 <init_module+0x80>

9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000
c3e69fa8
                                          lr               
sys_open’sp

4.语句

如果不属于System.map里的范围,则它属于insmod加载的驱动程序

    first_drv.dis文件里              insmod后
00000000 <first_drv_open>:       bf000000 t first_drv_open  
 [first_drv]
00000018                         pc = bf000018
                                 

pc = 0xbf000018

要打印的信息会存放在log_buf中,通过文件/proc/kmsg可以来访问这个buf,然后将信息打印出来。由此我们就想了,我们是否可以构造这样一个mylog_buf,里面存放我们所需要的打印信息,通过一个/proc/kmsg文件可以访问该buf,然后打印出来?答案是肯定的!

Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33eb0000  DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
发生错误时当前进程的名称是firstdrvtest

./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
内核使用56000050来访问时发生了错误

 1  static pid_t pre_pid;
 2        static int cnt=0;
 3         //时钟中断的中断号是30
 4         if(irq==30)
 5         {
 6         if(pre_pid==current->pid)
 7         {
 8             cnt++;
 9         }
10         else
11         {
12             cnt=0;
13             pre_pid=current->pid;
14         }
15 
16         if(cnt==10*HZ)
17         {
18             cnt=0;
19             printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
20             printk("pc = %08x\n",regs->ARM_pc);//打印pc值
21         }
22         }
23 
24 我们在次测试的话,会打印出如下信息:
25 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
26 pc = bf000084
27 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
28 pc = bf000084
29 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
30 pc = bf000084
31 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
32 pc = bf000084

pgd = c3eb0000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0    Not tainted  (2.6.22.6 #1)
PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小)
[first_drv]
PC就是发生错误的指令的地址
大多时候,PC值只会给出一个地址,不到指示说是在哪个函数里

 

自制寄存器修改器

  1. 内核处理UBOOT传入的参数
    console_setup
        add_preferred_console // 我想用名为”ttySAC0″的控制台,先记录下来

  2. 硬件驱动的入口函数里:
        drivers/serial/s3c2410.c
            register_console(&s3c24xx_serial_console);        

  3. printk
            vprintk
                /* Emit the output into the temporary buffer */
                // 先把输出信息放入临时BUFFER
                vscnprintf
                
                // Copy the output into log_buf.
                // 把临时BUFFER里的数据稍作处理,再写入log_buf
                // 比如printk(“abc”)会得到”<4>abc”, 再写入log_buf
                //
    可以用dmesg命令把log_buf里的数据打印出来重现内核的输出信息
                
                
                // 调用硬件的write函数输出
                release_console_sem();
                    call_console_drivers(_con_start, _log_end);
                        // 从log_buf得到数据,算出打印级别
                        _call_console_drivers(start_print, cur_index,
    msg_level);            
                            // 如果可以级别够格打印
                            if ((msg_log_level < console_loglevel
                                __call_console_drivers
                                    con->write(con, &LOG_BUF(start), end

pc : [<bf000018>]    lr : [<c008d888>]    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
执行这条导致错误的指令时各个寄存器的值

图片 1图片 2

  1. 根据栈信息分析函数调用过程
    # ./firstdrvtest on
    Unable to handle kernel paging request at virtual address 56000050
    pgd = c3e78000
    [56000050] *pgd=00000000
    Internal error: Oops: 5 [#1]
    Modules linked in: first_drv
    CPU: 0    Not tainted  (2.6.22.6 #48)
    PC is at first_drv_open+0x18/0x3c [first_drv]
    LR is at chrdev_open+0x14c/0x164
    pc : [<bf000018>]    lr : [<c008c888>]    psr:
    a0000013
    3.1 根据PC确定出错位置
    bf000018 属于 insmod的模块
    bf000000 t first_drv_open       [first_drv]

小结:在本文件里面我们做了两件事情,一件事情是定义了一个写函数,当我们在用户空间使用命令:cat
/proc/mymsg的时候,就会调用到这个读函数,这个读函数会将mylog_buf中的数据拷贝到用户空间,那么mylog_buf里面的数据哪里来的呢?这就是我们做的另外一件事情,我们定义了一个打印函数,这个打印函数会将要打印的数据写入一个临时缓冲区,然后又从临时缓冲区里面取出数据放入mylog_buf中。cat
/proc/mymsg的候就会将mylog_buf中的数据拷贝到用户空间,就可以显示出来了!

四. 修改内核来定位系统僵死问题

系统处于僵死状态时,程序将不再运行。但是即便系统僵死,系统时钟中断还是以固定的频率发生我们可以进入时钟中断处理函数把当前僵死的进程

  cat /proc/sys/kernel/printk      
//查看默认的打印级别console_loglevel

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/fs.h>
  4 #include <linux/init.h>
  5 #include <linux/delay.h>
  6 #include <asm/uaccess.h>
  7 #include <asm/irq.h>
  8 #include <asm/io.h>
  9 #include <asm/arch/regs-gpio.h>
 10 #include <asm/hardware.h>
 11 #include <linux/proc_fs.h>
 12 
 13 #define MYLOG_BUF_LEN 1024
 14 
 15 struct proc_dir_entry *myentry;
 16 
 17 static char mylog_buf[MYLOG_BUF_LEN];
 18 static char tmp_buf[MYLOG_BUF_LEN];
 19 static int mylog_r = 0;
 20 static int mylog_r_for_read = 0;
 21 static int mylog_w = 0;
 22 
 23 static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
 24 
 25 static int is_mylog_empty(void)
 26 {
 27     return (mylog_r == mylog_w);
 28 }
 29 
 30 static int is_mylog_empty_for_read(void)
 31 {
 32     return (mylog_r_for_read == mylog_w);
 33 }
 34 
 35 static int is_mylog_full(void)
 36 {
 37     return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
 38 }
 39 
 40 static void mylog_putc(char c)
 41 {
 42     if (is_mylog_full())
 43     {
 44         /* 丢弃一个数据 */
 45         mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
 46 
 47         if ((mylog_r_for_read + 1) % MYLOG_BUF_LEN == mylog_r)
 48         {
 49             mylog_r_for_read = mylog_r;
 50         }
 51     }
 52 
 53     mylog_buf[mylog_w] = c;
 54     mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;
 55 
 56     /* 唤醒等待数据的进程 */    
 57     wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */    
 58 }
 59 
 60 static int mylog_getc(char *p)
 61 {
 62     if (is_mylog_empty())
 63     {
 64         return 0;
 65     }
 66     *p = mylog_buf[mylog_r];
 67     mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
 68     return 1;
 69 }
 70 
 71 static int mylog_getc_for_read(char *p)
 72 {
 73     if (is_mylog_empty_for_read())
 74     {
 75         return 0;
 76     }
 77     *p = mylog_buf[mylog_r_for_read];
 78     mylog_r_for_read = (mylog_r_for_read + 1) % MYLOG_BUF_LEN;
 79     return 1;
 80 }
 81 
 82 
 83 int myprintk(const char *fmt, ...)
 84 {
 85     va_list args;
 86     int i;
 87     int j;
 88 
 89     va_start(args, fmt);
 90     i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
 91     va_end(args);
 92     
 93     for (j = 0; j < i; j++)
 94         mylog_putc(tmp_buf[j]);
 95         
 96     return i;
 97 }
 98 
 99 static ssize_t mymsg_read(struct file *file, char __user *buf,
100              size_t count, loff_t *ppos)
101 {
102     int error = 0;
103     int i = 0;
104     char c;
105 
106     /* 把mylog_buf的数据copy_to_user, return */
107     if ((file->f_flags & O_NONBLOCK) && is_mylog_empty_for_read())
108         return -EAGAIN;
109 
110     //printk("%s %d\n", __FUNCTION__, __LINE__);
111     //printk("count = %d\n", count);
112     //printk("mylog_r = %d\n", mylog_r);
113     //printk("mylog_w = %d\n", mylog_w);
114 
115     error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read());
116 
117     //printk("%s %d\n", __FUNCTION__, __LINE__);
118     //printk("count = %d\n", count);
119     //printk("mylog_r = %d\n", mylog_r);
120     //printk("mylog_w = %d\n", mylog_w);
121 
122     /* copy_to_user */
123     while (!error && (mylog_getc_for_read(&c)) && i < count) {
124         error = __put_user(c, buf);
125         buf++;
126         i++;
127     }
128     
129     if (!error)
130         error = i;
131     
132     return error;
133 }
134 
135 static int mymsg_open(struct inode *inode, struct file *file)
136 {
137     mylog_r_for_read = mylog_r;
138     return 0;
139 }
140 
141 const struct file_operations proc_mymsg_operations = {
142     .open = mymsg_open,
143     .read = mymsg_read,
144 };
145 
146 static int mymsg_init(void)
147 {    
148     myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root);
149     if (myentry)
150         myentry->proc_fops = &proc_mymsg_operations;
151     return 0;
152 }
153 
154 static void mymsg_exit(void)
155 {
156     remove_proc_entry("mymsg", &proc_root);
157 }
158 
159 module_init(mymsg_init);
160 module_exit(mymsg_exit);
161 
162 EXPORT_SYMBOL(myprintk);
163 
164 MODULE_LICENSE("GPL");
 1  打印出如下信息:
 2            CPU0
 3  30:      85713         s3c  S3C2410 Timer Tick   //这个就是系统时钟中断
 4  33:          0         s3c  s3c-mci
 5  34:          0         s3c  I2SSDI
 6  35:          0         s3c  I2SSDO
 7  37:         12         s3c  s3c-mci
 8  42:          0         s3c  ohci_hcd:usb1
 9  43:          0         s3c  s3c2440-i2c
10  51:       3509     s3c-ext  eth0
11  60:          0     s3c-ext  s3c-mci
12  70:         96   s3c-uart0  s3c2440-uart
13  71:         92   s3c-uart0  s3c2440-uart
14  83:          0           -  s3c2410-wdt
15 Err:          0
16 
17  30:      85713         s3c  S3C2410 Timer Tick   :这个就是系统时钟中断,我们可以再内核中查找:S3C2410 Timer Tick,会找到这样一个结构体:
18 static struct irqaction s3c2410_timer_irq = {
19 .name= "S3C2410 Timer Tick",
20 .flags= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
21 .handler= s3c2410_timer_interrupt,
22 };
23 
24 其中:s3c2410_timer_interrupt就是中断处理函数!我们在其中加入一些信息:
25 
26 s3c2410_timer_interrupt(int irq, void *dev_id)
27 {
28        static pid_t pre_pid;
29        static int cnt=0;
30 
31        if(pre_pid==current->pid)
32         {
33             cnt++;
34         }
35        else
36         {
37             cnt=0;
38             pre_pid=current->pid;
39         }
40        //如果本进程十秒钟还没有离开的话,就会打印下面的语句
41        if(cnt==10*HZ)
42         {
43             cnt=0;
44             printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
45         }       
46 
47 write_seqlock(&xtime_lock);
48 timer_tick();
49 write_sequnlock(&xtime_lock);
50 return IRQ_HANDLED;
51 }

 

三. 自制工具—寄存器编辑器
当我们调试驱动程序的时候,可能要调整寄存器的设置。按照我们之前的作法就是直接在程序里面修改,然后重新编译程序。但是这种方法比较麻烦,我们可以编写一个工具,可以直接对寄存器进行修改,这就是我们说的寄存器编辑器。

其操作函数如下:

打印到proc虚拟文件

二. 根据内核打印的段错误信息分析
a. 作为模块:


Stack: (0xc3c7be88 to 0xc3c7c000)
be80:                   c3c7bebc c3c7be98 c008d888 bf000010 00000000
c049abc0
bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48
c008d74c
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc
c3c7bee8
bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8
c0089f40
bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101
00000001
bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68
c3c7bf48
bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0
c3c7bf94
bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670
00000005
bf80: c002c044 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000
c3c7bfa8
bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0
00000001
bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c
bec1fea8
bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000
00000000

 

驱动程序的调试
一. 打印: printk, 自制proc文件
UBOOT传入console=ttySAC0(串口) console=tty1(LCD)

1 const struct file_operations proc_kmsg_operations = {
2     .read        = kmsg_read,
3     .poll        = kmsg_poll,
4     .open        = kmsg_open,
5     .release    = kmsg_release,
6 };
  • start);
  1. 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?
    先看看加载的驱动程序的函数的地址范围
    cat /proc/kallsyms  (内核函数、加载的函数的地址)
    从这些信息里找到一个相近的地址, 这个地址<=0xbf000018
    比如找到了:
    bf000000 t first_drv_open    [first_drv]

  2. 找到了first_drv.ko
    在PC上反汇编它: arm-linux-objdump -D first_drv.ko > frist_drv.dis
    在dis文件里找到first_drv_open

sp : c3e69e88  ip : c3e69e98  fp : c3e69e94
r10: 00000000  r9 : c3e68000  r8 : c0490620
r7 : 00000000  r6 : 00000000  r5 : c3e320a0  r4 : c06a8300
r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33e78000  DAC: 00000015
Process firstdrvtest (pid: 752, stack limit = 0xc3e68258)
Stack: (0xc3e69e88 to 0xc3e6a000)
9e80:                   c3e69ebc c3e69e98 c008c888 bf000010 00000000
c0490620
                        first_drv_open’sp    lr            
chrdev_open’sp

  set bootargs loglevel=0                      //不打印任何东西

 

信息打印出来

输出信息

 

图片 3图片 4

 1 关于我们加入的代码有两点需要说一下:
 2 第一:每一个进程都需要用一个结构体来表示:task_struct,这里面保存着与进程的一些状态信息,而current是一个宏,它代表当前的进程,也是一个task_struct结构体。所以current->pid就代表本进程的id,而current->comm就代表本进程的名字!
 3 第二:HZ是一个宏定义,它表示1秒钟发生多少次中断,10*HZ就代表10秒钟发生多少次中断!
 4 
 5 下面我们测试一下:
 6 我们可以某个驱动程序里面放入语句:while(1);这样的话,当程序执行到这里的时候就会僵死掉,在之前没有加入上述信息之前,没有任何打印信息,我们根本不知道是哪一个进程发生了僵死,现在的话,没过10秒就会打印相关信息,告诉我们现在是什么进程正在发生僵死!我的测试打印信息如下:
 7 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
 8 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
 9 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
10 
11 不过这样还不够详细,有没有办法知道是在哪里发生了僵死呢?这也是有办法的!
12 先来说一下原理:在应用程序的执行的时候,会一直以固定的频率发生时钟中断,那么发生中断的时候肯定会保存现场吧,那么这个保存现场的时候就要保存发生中断处的PC值,这样才能返回,那么如果把这个PC值打印出来不就知道在哪里发生中断了吗!
13 
14 我们之前分析过,发生中断的时候经过一些前期处理之后会调用:asm_do_IRQ这个函数
15 在这个函数里面我们发现了一个结构体:struct pt_regs,这个结构体就用来保存发生中断时的现场,其中PC值就是:ARM_pc
16 我们将上面在:s3c2410_timer_interrupt里面加入的信息都删除,并在:asm_do_IRQ函数里面加入如下信息:

 

9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48
c008c74c
                                                            lr    
                                                                               
 
9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc
c3e69ee8
      __dentry_open’sp

./firstdrvtest on
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name =
firstdrvtest
pc = bf000084
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name =
firstdrvtest
pc = bf000084   // 对于中断, pc-4才是发生中断瞬间的地址

测试程序

  1. 根据pc值确定该指令属于内核还是外加的模块
    pc=c014e6c0 属于内核(看System.map)

  2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis
    在dis文件里搜c014e6c0
    c014e6a8 <first_drv_open>:
    c014e6a8:       e1a0c00d        mov     ip, sp
    c014e6ac:       e92dd800        stmdb   sp!, {fp, ip, lr, pc}
    c014e6b0:       e24cb004        sub     fp, ip, #4      ; 0x4
    c014e6b4:       e59f1024        ldr     r1, [pc, #36]   ; c014e6e0
    <.text+0x1276e0>
    c014e6b8:       e3a00000        mov     r0, #0  ; 0x0
    c014e6bc:       e5912000        ldr     r2, [r1]
    c014e6c0:       e5923000        ldr     r3, [r2] // 在此出错
    r2=56000050

图片 5图片 6

9f20: 00000000 c3e68000 c04c1468 c04c1460 ffffffe8 c06e0000 c3e69f68
c3e69f48
9f40: c008916c c009ec70 00000003 00000000 c0490620 00000002 be94eee0
c3e69f94
9f60: c3e69f6c c00892f4 c0088f88 00008520 be94eed4 0000860c 00008670
00000005
               lr                do_sys_open’sp

 

图片 7图片 8

创建一个proc入口,并设置操作函数

在系统运行的过程中可能出现一种状况:系统僵死。

9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0
00000001
      lr                ret_fast_syscall’sp
                        
9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c
be94eea8
9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000
00000000

LR is at chrdev_open+0x14c/0x164
LR寄存器的值

图片 9图片 10

  echo “8 4 17”>/proc/sys/kernel/printk           
//修改默认的打印级别console_loglevel

9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8
c0088f40
      lr                nameidata_to_filp’sp                lr
      
9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101
00000001
      do_filp_open’sp

1 #ifdef CONFIG_PRINTK
2     {
3         struct proc_dir_entry *entry;
4         entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
5         if (entry)
6             entry->proc_fops = &proc_kmsg_operations;
7     }
8 #endif

Backtrace: (回溯)
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from
[<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>]
(__dentry_open+0x100/0x1e8)
 r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
[<c0089d48>] (__dentry_open+0x0/0x1e8) from
[<c0089f64>] (nameidata_to_filp+0x34/0x48)
[<c0089f30>] (nameidata_to_filp+0x0/0x48) from
[<c0089fb8>] (do_filp_open+0x40/0x48)
 r4:00000002
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>]
(do_sys_open+0x54/0xe4)
 r5:bec1fee0 r4:00000002
[<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>]
(sys_open+0x24/0x28)
[<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>]
(ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault
#

  1. 根据pc值确定该指令属于内核还是外加的模块
    pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?
    先判断是否属于内核的地址:
    看System.map确定内核的函数的地址范围:c0004000~c03265a4

图片 11图片 12

admin

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注