如何解决一个内核崩溃问题¶
通过 kdump 获取内核崩溃现场¶
内核崩溃的时候如果不能通过管理控制台获取最后的现场信息,可以通过 kdump 工具来转储崩溃现场。
kdump is a feature of the Linux kernel that creates crash dumps in the event of a kernel crash. When triggered, kdump exports a memory image (also known as vmcore) that can be analyzed for the purposes of debugging and determining the cause of a crash.
CentOS 系统一般预装了 kdump ,没有的话可以通过以下命令安装。
yum install kexec-tools
修改内核启动参数添加 kdump 配置。
# 修改 /etc/default/grub 中的内核启动参数,添加下面 crashkernel 参数
GRUB_CMDLINE_LINUX="... crashkernel=auto"
重新生成 grub2 配置:
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
重启确认配置是否成功(成功后内核参数会多出上面新加的 crashkernel 参数)。
# cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.19.113-88.8bs.el7.x86_64 ... crashkernel=auto ...
确认 kdump 服务是否正常
# systemctl status kdump
● kdump.service - Crash recovery kernel arming
Loaded: loaded (/usr/lib/systemd/system/kdump.service; enabled; vendor preset: enabled)
Active: active (exited) since Tue 2024-05-14 10:36:56 CST; 1h 15min ago
...
有些时候 crashkernel=auto
参数会失败,这个时候可以尝试将 auto
改成具体的内存大小。
一个真实的崩溃现场¶
启用了 kdump 之后,每次内核崩溃,kdump 会在 /var/crash
下创建一个新的目录用来保存崩溃的现场信息,每个现场一般两个文件,一个 core 文件 vmcore
,一个崩溃时的 dmesg 文件。
# ll /var/crash/127.0.0.1-2023-12-20-10\:51\:07/
total 160112
-rw------- 1 root root 81906871 Dec 20 10:51 vmcore
-rw-r--r-- 1 root root 137943 Dec 20 10:51 vmcore-dmesg.txt
首先查看 dmesg 文件,一般崩溃通过 dmesg
就可以定位。
$ cat /var/crash/127.0.0.1-2023-12-20-10\:51\:07/vmcore-dmesg.txt
...
[ 1823.251269] RIP: 0010:iptunnel_xmit+0x17d/0x1c0
[ 1823.253371] Code: ea 4c 89 e7 e8 d4 12 fb ff 48 85 ed 74 25 83 e0 fd 75 31 83 fb 00 7e 2a 48 8b 85 f0 04 00 00 65 48 03 05 ae 63 77 46 48 63 db <48> 83 40 10 01 48 01 58 18 48 83 c4 20 5b 5d 41 5c 41 5d 41 5e 41
...
[ 1823.280234] Call Trace:
[ 1823.281826] <IRQ>
[ 1823.283814] send4+0xf2/0x1b0 [XxxWan]
[ 1823.285593] hfunc_out6+0x2f7/0x510 [XxxWan]
[ 1823.289267] ? ipt_do_table+0x351/0x680
[ 1823.291741] nf_hook_slow+0x3d/0xb0
[ 1823.293875] ip6_xmit+0x332/0x5e0
[ 1823.295120] ? neigh_key_eq128+0x30/0x30
[ 1823.296623] ? inet6_csk_route_socket+0x158/0x250
[ 1823.298286] ? __kmalloc_node_track_caller+0x5d/0x280
[ 1823.299848] inet6_csk_xmit+0x91/0xe0
[ 1823.301455] __tcp_transmit_skb+0x536/0x9f0
...
[ 1823.367165] </IRQ>
...
能从 dmesg 中获取到的有用信息:
RIP
是程序指令指针(Instruction Pointer Relative),指向下一条即将被执行的指令,在上面 dmesg 中,RIP 指向iptunnel_xmit
函数的 0x17d 偏移处。Code
行是内核崩溃时正在执行前后的指令 dump,其中<48>
为当前 RIP 指向的指令处。最后
Call Trace
后面是内核崩溃时的函数调用栈,从调用栈可以看出内核是崩溃在了XxxWan
模块的send4
函数中,调用栈缺少send4
函数到iptunnel_xmit
的调用过程,并不完全。
反编译 vmlinux 获取崩溃的代码行¶
系统日常启动加载的内核镜像(也就是内核的可执行文件)位于 /boot/
目录下,比如 /boot/vmlinuz-4.19.113-88.8bs.el7.x86_64
,这个内核镜像是去除了调试信息并压缩了的。各种内核调试工具需要用到是未压缩的原始
vmlinux 文件,这个文件一般在内核的 debuginfo
包中,安装。
对比可以看出未压缩的 vmlinux 镜像要比压缩后的 vmlinuz 镜像大两个数量级。
# ll -h /boot/vmlinuz-4.19.113-88.8bs.el7.x86_64
-rwxr-xr-x 1 root root 8.4M Nov 4 2022 /boot/vmlinuz-4.19.113-88.8bs.el7.x86_64
# ll -h /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
-rw-r--r-- 1 root root 495M Dec 22 14:59 /usr/lib/debug/lib/modules/4.19.113-88.8bs.el7.x86_64/vmlinux
有了 vmlinux,就可以通过 objdump -d
反编译内核,根据前面 dmesg 信息,内核崩溃点在 iptunnel_xmit
函数中,所以先获取 iptunnel_xmit
的起始地址( objdump
不支持直接反编译某一个函数),然后从这个起始地址开始反编译内核并打印出汇编代码对应的 c 代码行号。
$ objdump -d /usr/lib/debug/lib/modules/$(uname -r)/vmlinux | grep iptunnel_xmit
ffffffff81898c00 <iptunnel_xmit>:
...
$ objdump -d --start-address=0xffffffff81898c00 --line-numbers /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
/usr/lib/debug/lib/modules/4.19.113-88.8bs.el7.x86_64/vmlinux: file format elf64-x86-64
Disassembly of section .text:
...
ffffffff81898c00 <iptunnel_xmit>:
iptunnel_xmit():
...
/usr/src/debug/kernel-alt-4.19.113/linux-4.19.113-88.8bs.el7.x86_64/include/net/ip_tunnels.h:458
ffffffff81898d6b: 48 8b 85 f0 04 00 00 mov 0x4f0(%rbp),%rax
ffffffff81898d72: 65 48 03 05 ae 63 77 add %gs:0x7e7763ae(%rip),%rax # f128 <this_cpu_off>
ffffffff81898d79: 7e
/usr/src/debug/kernel-alt-4.19.113/linux-4.19.113-88.8bs.el7.x86_64/include/net/ip_tunnels.h:461
ffffffff81898d7a: 48 63 db movslq %ebx,%rbx
/usr/src/debug/kernel-alt-4.19.113/linux-4.19.113-88.8bs.el7.x86_64/include/net/ip_tunnels.h:462
ffffffff81898d7d: 48 83 40 10 01 addq $0x1,0x10(%rax)
👆 RIP
...
iptunnel_xmit
函数 0x17d 偏移处为地址 ffffffff81898d7d
。
>>> hex(0xffffffff81898c00+0x17d)
0xffffffff81898d7dL
结合反编译内核得到的信息,可以获取到正在执行的代码位于 ip_tunnels.h
文件的 461 行。对应的 c 代码如下:
// ip_tunnels.h
455 static inline void iptunnel_xmit_stats(struct net_device *dev, int pkt_len)
456 {
457 if (pkt_len > 0) {
458 struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats);
459
460 u64_stats_update_begin(&tstats->syncp);
461 tstats->tx_bytes += pkt_len;
...
475 }
自此分析结束,后面就是结合内核以及 XxxWan
的代码来看为什么这个地方会崩溃。
如果问题更复杂,还可以使用 crash
工具(类似 gdb)来从 core 文件中获取更多现场信息。
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/.../vmcore
crash
比较耗内存,在本次崩溃的小虚拟机上跑不起来报 crash: cannot allocate any more memory!
错误,以后有机会再说,详细可以参见: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-crash-running-the-utility 。