1. 從vmlinux獲取具體的代碼行
文章中albcamus版主也提到了,需要有自己編譯的vmlinux,而且編譯時打開compile with debug info. 這個選項打開之後會使vmlinux文件比不加調試信息大一些。我這裡代調試信息的是49M。建議如果學習的時候,想使用gdb的方式獲取出錯代碼行的 話,就加上這個編譯條件。
然後就可以按照具體的方法去操作,可以定位到具體的C 代碼行。
2. 從自己編譯的內核模塊出錯信息中獲取代碼行以ldd3中提供的misc-modules/faulty.c為例。主要以faulty_write函數作分析。
(1)由於作者提供的函數代碼就一樣,過於簡單,我這裡簡單加上一些代碼(也就是判斷和賦值),如下:
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;
}
(2)編譯該模塊,並且mknod /dev/faulty(3)向該模塊寫入數據:echo 1 > /dev/faulty, 內核OOPS,信息如下:
<1>BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000
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)
EIP is at faulty_write+0xe/0x19 [faulty]
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
其中,我們應該關注的信息是第一行紅色標出部分:告訴我們操作了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文件。由於我們需要關注的部分正好在文件的前面,因此我這裡只貼出文件的前面一部分內容:
faulty.o: file format elf32-i386
Disassembly of section .text:
00000000 <faulty_write>:
0: 81 f9 00 01 00 00 cmp {GetProperty(Content)}x100,%ecx 6: b8 00 01 00 00 mov {GetProperty(Content)}x100,%eax b: 0f 46 c1 cmovbe %ecx,%eax e: c7 05 00 00 00 00 00 movl {GetProperty(Content)}x0,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 {GetProperty(Content)}x0,%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 {GetProperty(Content)}x0,%ecx 32: ba 00 00 00 00 mov {GetProperty(Content)}x0,%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
{GetProperty(Content)}x0,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
由以上匯編代碼可以看出,faulty_write函數的大小確實是0x18 -0x00 +1 = 0x19. 那麼EIP指針出問題的地方是0x0e處,代碼為:
e: c7 05 00 00 00 00 00 movl {GetProperty(Content)}x0,0x0
這行匯編代碼就是將0值保存到0地址的位置。那麼很顯然是非法的。這一行對應的C 代碼應該就是:
*(int *)0 = 0;
(5)以上是對模塊出錯信息的分析。不過也有一定的局限。
首先就是EIP出錯的位置正好在本模塊內部,這樣可以在本模塊定位問題;其次,要求一定的匯編基礎,特別是當一個函數的代碼比較多時,對應的匯編代碼也比較大,如何准確定位到C代碼行需要一定的經驗和時間。
實際運用中,可以將內核代碼行的定位和可動態加載的內核模塊代碼行的定位結合起來使用,應該可以較快的定位問題。
分析中有纰漏或者不妥的地方希望大家指出,也希望有網友分享更有效的方法