首先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 }
完。