oops && ksymoops && objdump
Wednesday, October 28, 2009 by JSDN
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 < oops.info
>>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 <_EIP>:
Code; d006408a <=====
0: f6 7d fb idivb 0xfffffffb(%ebp) <=====
Code; d006408d
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 <=====
0: f6 7d fb idivb 0xfffffffb(%ebp) <=====
Code; d006408d
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 源碼:
/*file: hello.c*/
#ifndef MODULE
#define MODULE
#endif
#include
#include "hello.h"
MODULE_AUTHOR("BUROC") ;
MODULE_DESCRIPTION("The test module") ;
MODULE_SUPPORTED_DEVICE("no_dev") ;
void print(char *str)
{
int i;
for(i = 0; i < 100; i++)
printk("%d\n",str[i]/(str[i]-str[i]));
}
static int __init hello_init(void){
print("Hello, I am coming.\n");
return 0;
}
static void __exit hello_exit(void){
print("Bye, I am leaving.\n");
}
module_init(hello_init);
module_exit(hello_exit);
什麼是OOPS呢? 如果寫過linux模塊或者linux驅動,對於OOPS並不陌生, 當模塊程序出現錯誤時, 終端會打印出一些讓人頭疼的寄存器和數據, 例如:
divide error: 0000
CPU: 0
EIP: 0010:[
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:[
2. Ksymoops
為了讓程序員明白它們的含義, 以及更好的使用這些」寶貴」的數據, 開發人員設計了ksymoops工具, 它就是講晦澀難懂的oops消息, 轉換成我們可以直接理解的信息.
這裡還是以上面數據為例, 首先要把數據存儲到一個文件中, 作為ksmoops的輸入數據. 這裡我把上面的數據放入文件oops.info中, 然後執行ksmoops數據, 看看有什麼結果.
#ksmoops < oops.info
>>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 <_EIP>:
Code; d006408a
0: f6 7d fb idivb 0xfffffffb(%ebp) <=====
Code; d006408d
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
0: f6 7d fb idivb 0xfffffffb(%ebp) <=====
Code; d006408d
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 源碼:
/*file: hello.c*/
#ifndef MODULE
#define MODULE
#endif
#include
#include "hello.h"
MODULE_AUTHOR("BUROC") ;
MODULE_DESCRIPTION("The test module") ;
MODULE_SUPPORTED_DEVICE("no_dev") ;
void print(char *str)
{
int i;
for(i = 0; i < 100; i++)
printk("%d\n",str[i]/(str[i]-str[i]));
}
static int __init hello_init(void){
print("Hello, I am coming.\n");
return 0;
}
static void __exit hello_exit(void){
print("Bye, I am leaving.\n");
}
module_init(hello_init);
module_exit(hello_exit);