首先Linux內核需要打開編譯的調試選項CONFIG_DEBUG_INFO,如下:
Kernel hacking --->
[*] Kernel debugging
[*] Compile the kernel with debug info
這樣,內核在編譯之後的目標文件帶有調試信息。檢查內核的Makefile文件,打開CONFIG_DEBUG_INFO選項之後,內核爲KBUILD_CFLAGS變量添加了-g的調試選項。
ifdef CONFIG_DEBUG_INFO
KBUILD_CFLAGS += -g
KBUILD_AFLAGS += -gdwarf-2
endif
比如有如下的panic信息,指令指針IP的絕對位置在ffffffff8196f70c,相對位置在函數ip_local_deliver_finish開始的位置偏移0x1c,另外0x100爲此函數的結束位置。
[ 266.469971] BUG: unable to handle kernel NULL pointer dereference at 00000000000004a0
[ 266.470020] IP: [<ffffffff8196f70c>] ip_local_deliver_finish+0x1c/0x100
[ 266.470020] PGD ad37a067 PUD ad37b067 PMD 0
[ 266.470020] Oops: 0000 [#1] SMP
[ 266.470020] CPU: 1 PID: 238 Comm: initXXXXXXXXXXX Not tainted 3.10.0 #1
[ 266.470020] Hardware name: To Be Filled By O.E.M. To Be Filled By O.E.M./G6LD525 VER2, BIOS 080016 07/24/2015
[ 266.470020] task: ffff88007f8b3b80 ti: ffff88009dae4000 task.ti: ffff88009dae4000
[ 266.470020] RIP: 0010:[<ffffffff8196f70c>] [<ffffffff8196f70c>] ip_local_deliver_finish+0x1c/0x100
[ 266.470020] RSP: 0018:ffff88009dae7c98 EFLAGS: 00010206
查看內核代碼可知,ip_local_deliver_finish函數位於文件net/ipv4/ip_input.c中。使用objdump工具反編譯此文件的目標文件:
# objdump -DSl net/ipv4/ip_input.o
如下爲反編譯內容,此處使用panic信息中的相對位置定位出問題的代碼位置:
ip_input.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <ip_local_deliver_finish>:
ip_local_deliver_finish():
/linux-3.10.0/net/ipv4/ip_input.c:198
0: e8 00 00 00 00 callq 5 <ip_local_deliver_finish+0x5>
5: 55 push %rbp
6: 48 89 e5 mov %rsp,%rbp
9: 41 55 push %r13
b: 41 54 push %r12
d: 49 89 f4 mov %rsi,%r12
10: 53 push %rbx
/linux-3.10.0/include/linux/netdevice.h:2058
11: 48 8b 46 20 mov 0x20(%rsi),%rax
skb_network_header_len():
/linux-3.10.0/include/linux/skbuff.h:2095
15: 0f b7 96 4e 01 00 00 movzwl 0x14e(%rsi),%edx
ip_local_deliver_finish():
/linux-3.10.0/include/linux/netdevice.h:2058
1c: 4c 8b a8 a0 04 00 00 mov 0x4a0(%rax),%r13
skb_network_header_len():
fd: 00 00 00
0000000000000100 <ip_rcv_finish>:
函數ip_local_deliver_finish的起始位置爲0000000000000000,加上偏移0x1c,可知出錯位置在文件include/linux/netdevice.h的2058行,而此netdevice.h中的函數有net/ipv4/ip_input.c的198行調用而來,看以下代碼:
net/ipv4/ip_input.c:
196 static int ip_local_deliver_finish(struct sock *sk, struct sk_buff *skb)
197 {
198 struct net *net = dev_net(skb->dev);
include/linux/netdevice.h:
2055 static inline
2056 struct net *dev_net(const struct net_device *dev)
2057 {
2058 return read_pnet(&dev->nd_net);
2059 }
可知,問題出在dev_net函數中,讀取的dev的成員nd_net爲非法值。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
有一個問題時,以上objdump反彙編的代碼沒有插進去c代碼。於是,寫了一個hello world的測試程序,使用gcc -g編譯後,再用objdump反彙編,還是可以看到插入的C代碼的。內核只能看到行號,感覺應該是-O2優化的影響導致,還不確定。
# gcc -g main.c
# objdump -DSl a.out
結果如下:
320 int main()
321 {
322 40052d: 55 push %rbp
323 40052e: 48 89 e5 mov %rsp,%rbp
324 main.c:8
325 printf("hello world!\n");
326 400531: bf d4 05 40 00 mov $0x4005d4,%edi
327 400536: e8 d5 fe ff ff callq 400410 <puts@plt>
328 /home/kzhang/firewall_tc_2.0.0/root/1ksa0b/linux-3.10.0/main.c:10
329 }
完。