本文介绍在Fedora上对Linux内核的vmlinuz进行反汇编。如果内核是debug版本,可以用来查看某个函数的源代码。
1. 安装kernel-devel软件包
dnf -y install kernel-devel
2. 提取vmlinux
- vmlinux是一个包括Linux kernel的静态链接的可运行文件。
- vmlinuz是vmlinux经过gzip和objcopy制作出来的压缩文件。
/usr/src/kernels/$(uname -r)/scripts/extract-vmlinux /boot/vmlinuz-$(uname -r) > vmlinux
3. 反汇编vmlinux
objdump -D vmlinux > vmlinux.out
4. 查看
vmlinux里的函数
这里以函数tcp4_proc_init为例。/proc/kallsyms存储了所有的内核符号表,/boot/System.map则存储了静态的内核符号表。有关System.map,请阅读这里。
e.g.
root# grep tcp4_proc_init /proc/kallsyms
ffffffffa37dd330 t tcp4_proc_init_net
ffffffffa479d258 T tcp4_proc_init
root# grep tcp4_proc_init /boot/System.map-$(uname -r)
ffffffff817dd330 t tcp4_proc_init_net
ffffffff8279d258 T tcp4_proc_init
root# egrep -in ffffffff8279d258 vmlinux.out
8960512:ffffffff8279d258: e8 93 47 26 ff callq 0xffffffff81a019f0
8960912:ffffffff8279d96d: e8 e6 f8 ff ff callq 0xffffffff8279d258
root# N=8960512
root# sed -n "$((N-5)),$((N+5))"p vmlinux.out
ffffffff8279d24e: e8 7d 14 91 fe callq 0xffffffff810ae6d0
ffffffff8279d253: eb bd jmp 0xffffffff8279d212
ffffffff8279d255: 5b pop %rbx
ffffffff8279d256: 5d pop %rbp
ffffffff8279d257: c3 retq
ffffffff8279d258: e8 93 47 26 ff callq 0xffffffff81a019f0
ffffffff8279d25d: 48 c7 c7 40 ca 31 82 mov $0xffffffff8231ca40,%rdi
ffffffff8279d264: e9 27 d3 fb fe jmpq 0xffffffff8175a590
ffffffff8279d269: e8 82 47 26 ff callq 0xffffffff81a019f0
ffffffff8279d26e: 48 c7 c7 60 c8 31 82 mov $0xffffffff8231c860,%rdi
ffffffff8279d275: e8 16 d3 fb fe callq 0xffffffff8175a590
函数tcp4_proc_init()的源代码如下:
/* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/ipv4/tcp_ipv4.c?h=v4.16.9#n2392 */
2377 static int __net_init tcp4_proc_init_net(struct net *net)
2378 {
2379 return tcp_proc_register(net, &tcp4_seq_afinfo);
2380 }
2381
2382 static void __net_exit tcp4_proc_exit_net(struct net *net)
2383 {
2384 tcp_proc_unregister(net, &tcp4_seq_afinfo);
2385 }
2386
2387 static struct pernet_operations tcp4_net_ops = {
2388 .init = tcp4_proc_init_net,
2389 .exit = tcp4_proc_exit_net,
2390 };
2391
2392 int __init tcp4_proc_init(void)
2393 {
2394 return register_pernet_subsys(&tcp4_net_ops);
2395 }
2396
2397 void tcp4_proc_exit(void)
2398 {
2399 unregister_pernet_subsys(&tcp4_net_ops);
2400 }
从L2394,我们可以看出tcp4_proc_init()调用了函数register_pernet_subsys(), 重新查看vmlinux.out验证一下。
root# egrep 'T register_pernet_subsys' /boot/System.map-$(uname -r)
ffffffff8175a590 T register_pernet_subsys
root# sed -n "$N, $((N+20))"p vmlinux.out > /tmp/1
root# egrep -in retq /tmp/1
11:ffffffff8279d28a: c3 retq
root# sed -n '1,11'p /tmp/1 | cat -n
1 ffffffff8279d258: e8 93 47 26 ff callq 0xffffffff81a019f0
2 ffffffff8279d25d: 48 c7 c7 40 ca 31 82 mov $0xffffffff8231ca40,%rdi
3 ffffffff8279d264: e9 27 d3 fb fe jmpq 0xffffffff8175a590
4 ffffffff8279d269: e8 82 47 26 ff callq 0xffffffff81a019f0
5 ffffffff8279d26e: 48 c7 c7 60 c8 31 82 mov $0xffffffff8231c860,%rdi
6 ffffffff8279d275: e8 16 d3 fb fe callq 0xffffffff8175a590
7 ffffffff8279d27a: 85 c0 test %eax,%eax
8 ffffffff8279d27c: 74 0c je 0xffffffff8279d28a
9 ffffffff8279d27e: 48 c7 c7 c0 d0 14 82 mov $0xffffffff8214d0c0,%rdi
10 ffffffff8279d285: e8 b4 c1 90 fe callq 0xffffffff810a943e
11 ffffffff8279d28a: c3 retq
root# sed -n '1,11'p /tmp/1 | cat -n | egrep ffffffff8175a590
3 ffffffff8279d264: e9 27 d3 fb fe jmpq 0xffffffff8175a590
6 ffffffff8279d275: e8 16 d3 fb fe callq 0xffffffff8175a590
参考资料:
- How to extract and disassemble a Linux kernel image (vmlinuz)