http://linux.chinaunix.net/bbs/archiver/?tid-1097586.html
定位可动态加载的内核模块的OOPS代码行
最近又仔细学习了albcamus版主提供的《定位Oops的具体代码行》(链接:[url]http://linux.chinaunix.net/bbs/viewthread.php?tid=1008573[/url]),并且进行了实践。因此这里简单总结一下,并且以实例的方式给出定位可动态加载模块Oops信息的方法。
本文欢迎自由转载,但请标明出处,并保证本文的完整性。
Godbach
Apr 19, 2009
1. 从vmlinux获取具体的代码行
文章中albcamus版主也提到了,需要有自己编译的vmlinux,而且编译时打开compile with debug info. 这个选项打开之后会使vmlinux文件比不加调试信息大一些。我这里代调试信息的是49M。建议如果学习的时候,想使用gdb的方式获取出错代码行的话,就加上这个编译条件。
然后就可以按照具体的方法去操作,可以定位到具体的C 代码行。
2. 从自己编译的内核模块出错信息中获取代码行
以ldd3中提供的misc-modules/faulty.c为例。主要以faulty_write函数作分析。
(1)由于作者提供的函数代码就一样,过于简单,我这里简单加上一些代码(也就是判断和赋值),如下:
[code]ssize_t faulty_write (struct file *filp, const char __user *buf, size_t count,
loff_t *pos)
{
/* make a simple fault by dereferencing a NULL pointer */
if(count > 0x100)
count = 0x100;
*(int *)0 = 0;
return count;
}[/code]
(2)编译该模块,并且mknod /dev/faulty
(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:
[quote]<1>[color=Red]BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000[/color]
printing eip:
f8e8000e
*pde = 00000000
Oops: 0002 [#3]
SMP
Modules linked in: faulty autofs4 hidp rfcomm l2cap ...... //此处省略若干字符
CPU: 1
EIP: 0060:[<f8e8000e>] Not tainted VLI
EFLAGS: 00010283 (2.6.18.3 #2)
[color=Red]EIP is at faulty_write+0xe/0x19 [faulty][/color]
eax: 00000001 ebx: f4f6ca40 ecx: 00000001 edx: b7c2d000
esi: f8e80000 edi: b7c2d000 ebp: 00000001 esp: f4dc5f84
ds: 007b es: 007b ss: 0068
Process bash (pid: 6084, ti=f4dc5000 task=f7c8d4d0 task.ti=f4dc5000)
Stack: c1065914 f4dc5fa4 f4f6ca40 fffffff7 b7c2d000 f4dc5000 c1065f06 f4dc5fa4
00000000 00000000 00000000 00000001 00000001 c1003d0b 00000001 b7c2d000
00000001 00000001 b7c2d000 bfd40aa8 ffffffda 0000007b c100007b 00000004
Call Trace:
[<c1065914>] vfs_write+0xa1/0x143
[<c1065f06>] sys_write+0x3c/0x63
[<c1003d0b>] syscall_call+0x7/0xb
Code: Bad EIP value.
EIP: [<f8e8000e>] faulty_write+0xe/0x19 [faulty] SS:ESP 0068:f4dc5f84[/quote]
其中,我们应该关注的信息是第一行红色标出部分:告诉我们操作了NULL指针。其次,就是第二行红色部分:EIP is at faulty_write+0xe/0x19。这个出错信息告诉我们EIP指针出现问题的地方时faulty_write函数,而且指出了是faulty模块。
同时,faulty_write+0xe/0x19的后半部分0xe/0x19,说明该函数的大小时0x019,出错位置是在0x0e。这两个值应该值得都是汇编代码的值。
(4)将faulty模块反汇编出汇编代码:
objdump -d faulty.ko > faulty.s
或
objdump -d faulty.o > faulty.s
然后,我们打开faulty.s文件。由于我们需要关注的部分正好在文件的前面,因此我这里只贴出文件的前面一部分内容:
[code]faulty.o: file format elf32-i386
Disassembly of section .text:
00000000 <faulty_write>:
0: 81 f9 00 01 00 00 cmp $0x100,%ecx
6: b8 00 01 00 00 mov $0x100,%eax
b: 0f 46 c1 cmovbe %ecx,%eax
e: c7 05 00 00 00 00 00 movl $0x0,0x0
15: 00 00 00
18: c3 ret
00000019 <cleanup_module>:
19: a1 00 00 00 00 mov 0x0,%eax
1e: ba 00 00 00 00 mov $0x0,%edx
23: e9 fc ff ff ff jmp 24 <cleanup_module+0xb>
00000028 <faulty_init>:
28: a1 00 00 00 00 mov 0x0,%eax
2d: b9 00 00 00 00 mov $0x0,%ecx
32: ba 00 00 00 00 mov $0x0,%edx
37: e8 fc ff ff ff call 38 <faulty_init+0x10>
3c: 85 c0 test %eax,%eax
3e: 78 13 js 53 <faulty_init+0x2b>
40: 83 3d 00 00 00 00 00 cmpl $0x0,0x0
47: 74 03 je 4c <faulty_init+0x24>
49: 31 c0 xor %eax,%eax
4b: c3 ret
4c: a3 00 00 00 00 mov %eax,0x0
51: 31 c0 xor %eax,%eax
53: c3 ret [/code]
由以上汇编代码可以看出,faulty_write函数的大小确实是0x18 -0x00 +1 = 0x19. 那么EIP指针出问题的地方是0x0e处,代码为:
[code] e: c7 05 00 00 00 00 00 movl $0x0,0x0[/code]
这行汇编代码就是将0值保存到0地址的位置。那么很显然是非法的。这一行对应的C 代码应该就是:
*(int *)0 = 0;
(5)以上是对模块出错信息的分析。不过也有一定的局限。
首先就是EIP出错的位置正好在本模块内部,这样可以在本模块定位问题;
其次,要求一定的汇编基础,特别是当一个函数的代码比较多时,对应的汇编代码也比较大,如何准确定位到C代码行需要一定的经验和时间。
实际运用中,可以将内核代码行的定位和可动态加载的内核模块代码行的定位结合起来使用,应该可以较快的定位问题。
分析中有纰漏或者不妥的地方希望大家指出,也希望有网友分享更有效的方法。