关于oops的文章,为了以后察看转载了。感谢原文作者。
1. OOPS
什么是OOPS呢? 假如写过linux模块或linux驱动,对于OOPS并不陌生, 当模块程式出现错误时, 终端会打印出一些让人头疼的寄存器和数据, 例如:
divide error: 0000
CPU: 0
EIP: 0010:[] Tainted: P
EFLAGS: 00010286
eax: c10b0048 ebx: d0064000 ecx: 00005ae5 edx: c10b0048
esi: 00000000 edi: 00000000 ebp: c770defc esp: c770deec
ds: 0018 es: 0018 ss: 0018
Process insmod.old (pid: 1160, stackpage=c770d000)
Stack: c0101d04 0f76a067 00067000 00000000 c770df1c d00640be d00640e4 00000212
00000060 d0064000 00000000 00000000 ffffffea c01165e1 00000000 08085a05
0000010d 00000060 00000060 00000005 c2ea95a0 c4145000 cc5ca000 d0066000
Call Trace: [] [] [] [] []
Code: f6 7d fb 88 45 fb 0f be 45 fb 50 68 e0 40 06 d0 e8 c1 16 0b
这些数据就是我们这里要讲的OOPS消息, 这些消息包含了出错时的寄存器信息连同内存信息, 例如, EIP(0010:[]), 这就告诉了我们出错时EIP的相对值是0010, 在运用objdump工具对源代码进行反汇编, 就能够轻而易举的找到错误点. 因此这些数据对于代码错误分析相当重要. 但是, 对于这些只有机器才能明白的数据, 程式员恐怕很不喜欢.
2. Ksymoops
为了让程式员明白他们的含义, 连同更好的使用这些”宝贵”的数据, 研发人员设计了ksymoops工具, 他就是讲晦涩难懂的oops消息, 转换成我们能够直接理解的信息.
这里还是以上面数据为例, 首先要把数据存储到一个文档中, 作为ksmoops的输入数据. 这里我把上面的数据放入文档oops.info中, 然后执行ksmoops数据, 看看有什么结果.
#ksmoops >EIP; d006408a >eax; c10b0048
>>ebx; d0064000
>>edx; c10b0048
>>ebp; c770defc
>>esp; c770deec
Trace; d00640be
Trace; d00640e4
Trace; c01165e1
Trace; d0064060
Trace; c0108983
Code; d006408a
00000000 :
Code; d006408a
3: 88 45 fb mov %al,0xfffffffb(%ebp)
Code; d0064090
6: 0f be 45 fb movsbl 0xfffffffb(%ebp),%eax
Code; d0064094
a: 50 push %eax
Code; d0064095
b: 68 e0 40 06 d0 push $0xd00640e0
Code; d006409a
2.1 Trace
很显然, trace是模块执行过程中对应的函数地址, 由于ksymoops 运行时的默认寻找模块在/lib/modules的下面, 因为我所运行的模块不在那个目录下,所以结果是pg0+ … 一类的数据.
2.2 Code
Code行对应的是相应的错误发生时对应的执行代码, 通过ksymoops的处理, 就变成了我们熟悉的汇编代码:
Code; d006408a
3: 88 45 fb mov %al,0xfffffffb(%ebp)
Code; d0064090
6: 0f be 45 fb movsbl 0xfffffffb(%ebp),%eax
Code; d0064094
a: 50 push %eax
Code; d0064095
b: 68 e0 40 06 d0 push $0xd00640e0
Code; d006409a
第二行就是错误发生的地方, 以下部分是将要执行的代码.
3 objdump
Ksymoops只是给出了错误点的信息, 但是对于庞大的系统模块, 我们必须准确的定位到那个错误点, 为此, 我们还能够利用另一个反汇编工具objdump继续分析错误. 我的模块名称时hello.o, 为了了解他的源码, 我们就能够使用该工具.
#objdump –d hello.o
hello.o: file format elf32-i386
Disassembly of section .text:
00000000 :
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
d: 83 7d fc 63 cmpl $0x63,0xfffffffc(%ebp)
11: 7e 02 jle 15
13: eb 34 jmp 49
15: 83 ec 08 sub $0x8,%esp
18: 8b 45 fc mov 0xfffffffc(%ebp),%eax
1b: 03 45 08 add 0x8(%ebp),%eax
1e: 8a 00 mov (%eax),%al
20: 66 0f be d0 movsbw %al,%dx
24: c6 45 fb 00 movb $0x0,0xfffffffb(%ebp)
28: 89 d0 mov %edx,%eax
2a: f6 7d fb idivb 0xfffffffb(%ebp)
2d: 88 45 fb mov %al,0xfffffffb(%ebp)
30: 0f be 45 fb movsbl 0xfffffffb(%ebp),%eax
34: 50 push %eax
35: 68 00 00 00 00 push $0x0
3a: e8 fc ff ff ff call 3b
3f: 83 c4 10 add $0x10,%esp
42: 8d 45 fc lea 0xfffffffc(%ebp),%eax
45: ff 00 incl (%eax)
47: eb c4 jmp d
49: c9 leave
4a: c3 ret
0000004b :
4b: 55 push %ebp
4c: 89 e5 mov %esp,%ebp
4e: 83 ec 08 sub $0x8,%esp
51: 83 ec 0c sub $0xc,%esp
54: 68 04 00 00 00 push $0x4
59: e8 fc ff ff ff call 5a
5e: 83 c4 10 add $0x10,%esp
61: b8 00 00 00 00 mov $0x0,%eax
66: c9 leave
67: c3 ret
00000068 :
68: 55 push %ebp
69: 89 e5 mov %esp,%ebp
6b: 83 ec 08 sub $0x8,%esp
6e: 83 ec 0c sub $0xc,%esp
71: 68 19 00 00 00 push $0x19
76: e8 fc ff ff ff call 77
7b: 83 c4 10 add $0x10,%esp
7e: c9 leave
7f: c3 ret
这样通过ksmoops的结果和objdump的数据就能够轻而易举的找到发生错误的函数连同在函数内部的具体位置了:
2a: f6 7d fb idivb 0xfffffffb(%ebp)
4 总结
我给出的oops信息是在Linux内核版本为2.4.8的系统里面执行的结果, 在2.6.*版本的内核中, oops信息中已给出了调用函数的名称. Kysmoops 一些具体的参数这里也没有介绍如何使用, 想具体了解他们, 能够参考文档: /usr/src/linux/Documentation/oops-tracing.txt或ksymoops手册.
5 附件
Hello.c 源码:
05 | #include <linux/module.h> |
09 | MODULE_AUTHOR( "BUROC" ) ; |
10 | MODULE_DESCRIPTION( "The test module" ) ; |
11 | MODULE_SUPPORTED_DEVICE( "no_dev" ) ; |
16 | for (i = 0; i < 100; i++) |
17 | printk( "%d\n" ,str[i]/(str[i]-str[i])); |
20 | static int __init hello_init( void ){ |
21 | print( "Hello, I am coming.\n" ); |
26 | static void __exit hello_exit( void ){ |
27 | print( "Bye, I am leaving.\n" ); |
29 | module_init(hello_init); |
30 | module_exit(hello_exit); |