Linux内核调试之Oops信息
Oops这个英文单词的意思是“哎呀”,当内核出错时(比如访问非法地址),输出的信息就成为Oops信息,下面用一个例子来介绍一下Oops信息:
例子:为了测试Oops信息,这里我们可以任意选择一个内核文件做测试,我以i2c为例:
1.
修改linux源代码/drivers/i2c/i2c-core.c
在函数static int __init i2c_init(void)的开头添加如下语句:
int * ptest = NULL;
*ptest = 0xabcd;
如下:
Linux内核执行到这里就会出错,产生Oops信息
2.
然后在menuconfig中将i2c编译进内核:
make menuconfig
在Device Drivers中选择I2C support,把I2C编译进内核
3.
重新编译内核,然后用该内核启动
4.
果然出现了Oops信息啦~~如下图:
上图中的从第7行Bug开始到最后就是Oops信息
5.
为了将Oops信息中的机器码地址和源文件相对应,我们还要将编译好的内核镜像反汇编以便对照,但由于我们之前用make bzImage命令来生成bzImage内核压缩镜像,无法反汇编,所以我们运行如下命令:
make vmlinux 表示对生成的内核镜像不压缩
这样生成的内核镜像大约150M!
6.
将第5步生成的无压缩的内核镜像反汇编:
objdump -D vmlinux > vmlinux.dis
生成的vmlinux.dis大约500M!呵呵,如下:
vmlinux: file format elf32-i386
Disassembly of section .text:
c1000000 <_text>:
c1000000: f6 86 11 02 00 00 40 testb $0x40,0x211(%esi)
c1000007: 75 14 jne c100001d <_text+0x1d>
c1000009: 0f 01 15 16 4e 6a 01 lgdtl 0x16a4e16
c1000010: b8 18 00 00 00 mov $0x18,%eax
c1000015: 8e d8 mov %eax,%ds
c1000017: 8e c0 mov %eax,%es
c1000019: 8e e0 mov %eax,%fs
c100001b: 8e e8 mov %eax,%gs
c100001d: fc cld
c100001e: 31 c0 xor %eax,%eax
c1000020: bf 00 90 7a 01 mov $0x17a9000,%edi
c1000025: b9 5c b8 83 01 mov $0x183b85c,%ecx
c100002a: 29 f9 sub %edi,%ecx
c100002c: c1 e9 02 shr $0x2,%ecx
c100002f: f3 ab rep stos %eax,%es:(%edi)
c1000031: bf e0 99 73 01 mov $0x17399e0,%edi
c1000036: b9 00 04 00 00 mov $0x400,%ecx
c100003b: fc cld
c100003c: f3 a5 rep movsl %ds:(%esi),%es:(%edi)
c100003e: 8b 35 08 9c 73 01 mov 0x1739c08,%esi
c1000044: 21 f6 and %esi,%esi
c1000046: 74 0c je c1000054 <_text+0x54>
c1000048: bf 00 73 73 01 mov $0x1737300,%edi
c100004d: b9 00 02 00 00 mov $0x200,%ecx
c1000052: f3 a5 rep movsl %ds:(%esi),%es:(%edi)
c1000054: 66 81 3d e6 9b 73 01 cmpw $0x207,0x1739be6
c100005b: 07 02
c100005d: 72 1c jb c100007b <default_entry>
c100005f: a1 1c 9c 73 01 mov 0x1739c1c,%eax
c1000064: 3d 04 00 00 00 cmp $0x4,%eax
c1000069: 73 0e jae c1000079 <lguest_entry>
c100006b: 8b 04 85 c0 72 73 01 mov 0x17372c0(,%eax,4),%eax
c1000072: 2d 00 00 00 c0 sub $0xc0000000,%eax
c1000077: ff e0 jmp *%eax
..................................................
BUG: unable to handle kernel NULL pointer dereference at (null)
这是Oops信息的字符串说明,说明我们访问了NULL指针
IP: [<c172f60b>] i2c_init+0x9/0x63
<c172f60b>表示内核错误处的ip指针,在vmlinux.dis中搜索“c172f60b”,找到如下:
c172f5fe: 5e pop %esi
c172f5ff: 5f pop %edi
c172f600: 5d pop %ebp
c172f601: c3 ret
c172f602 <i2c_init>:
c172f602: 55 push %ebp
c172f603: b8 20 c0 6d c1 mov $0xc16dc020,%eax
c172f608: 89 e5 mov %esp,%ebp
c172f60a: 53 push %ebx
c172f60b: c7 05 00 00 00 00 cd movl $0xabcd,0x0
c172f612: ab 00 00
c172f615: e8 c6 92 bd ff call c13088e0 <bus_register>
c172f61a: 85 c0 test %eax,%eax
c172f61c: 89 c3 mov %eax,%ebx
c172f61e: 75 40 jne c172f660 <i2c_init+0x5e>
c172f620: b8 67 ee 63 c1 mov $0xc163ee67,%eax
c172f625: e8 36 ab bd ff call c130a160 <class_compat_register>
c172f62a: 85 c0 test %eax,%eax
c172f62c: a3 d4 76 83 c1 mov %eax,0xc18376d4
c172f631: 74 1e je c172f651 <i2c_init+0x4f>
这正好是在i2c_init函数中。
i2c_init+0x9/0x63
表示出错的是在i2c_init函数的起始地址(0xc172f602)偏移0x9的位置,而i2c_init函数的大小为0x63个字节,出错处的ip=c172f60b,正好等于0xc172f602+0x9
Pid: 1
发生错误的进程ID
[ 0.992003] EIP: 0060:[<c172f60b>] EFLAGS: 00010246 CPU: 0
[ 0.992003] EIP is at i2c_init+0x9/0x63
[ 0.992003] EAX: c16dc020 EBX: c1785e34 ECX: 00000000 EDX: 00000000
[ 0.992003] ESI: c16fe164 EDI: 00000000 EBP: df071f98 ESP: df071f94
[ 0.992003] DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068
[ 0.992003] Process swapper (pid: 1, ti=df070000 task=df068000 task.ti=df070)
发生错误时的各个寄存器的值,其中的CPU: 0表示发生错误的CPU编号,对于单处理器系统来说,编号为0
[ 1.240004] Call Trace:
[ 1.240004] [<c1001272>] ? do_one_initcall+0x32/0x1a0
[ 1.240004] [<c172f602>] ? i2c_init+0x0/0x63
[ 1.240004] [<c17018d9>] ? kernel_init+0x12d/0x183
[ 1.240004] [<c17017ac>] ? kernel_init+0x0/0x183
[ 1.240004] [<c1003e67>] ? kernel_thread_helper+0x7/0x10
发生错误时的堆栈调用,最下面的是最上层的调用
Code: 89 18 83 7d ec 00 0f 85 64 ff ff ff 31 db b8 00 c0 6d c1 e8
出错指令附近的指令的机器码
利用上面这些信息,我们就可以很快的找到错误的位置啦~~
完成!