Kdump是一种基于kexec的Linux内核崩溃捕获机制,kexec的全称是Kernel execution,即立即执行,它可以跳过BIOS或者bootloader等引导程序,快速启动一个新的内核。这样第一个内核的内存就得以保留。在第二个内核中可以对第一个内核产生的崩溃数据进行分析。
Crash是由Red Hat Enterprise Linux 项目师开发的,和Kdump配套使用的一个用于分析内核转储文件的工具。
Kdump+Crash分析宕机问题的流程并不复杂,首先Kdump会在内存中保留一块区域,这个区域用来存放捕获内核。当生产内核在运行过程中遇到崩溃等情况。Kdump会通过kexec机制自动启动捕获内核,跳过BIOS,以免破坏了生产内核的内存,然后把生产内核的完整信息(包括CPU寄存器,栈数据等)转储到指定文件中。接着使用Crash工具来分析这个转储文件,以快速定位宕机问题。
目前大部分服务器基于RHEL或者其开源版本Centos部署,本节介绍如何在Centos中配置和安装Kdump和Crash工具。
yum install kexec-tools
GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=256M"
修改crashkernel的大小,我的系统内存是3G,保留了256M,注意预留内存大小,过小会导致生成coredump文件失败(不知道设置多少时,可以尝试每次增加128M)
修改后还需重新生成grub配置文件,重启系统才能生效
[vagrant@localhost ~]$ grub2-mkconfig -o /boot/grub2/grub.cfg
[vagrant@localhost ~]$ reboot
[root@localhost ~]$ cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=256M rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
centos7 默认已安装kdump,根据需要修改默认配置
[root@localhost ~]$ vi /etc/kdump.conf
path /var/crash #指定coredump文件存储位置
core_collector makedumpfile -c -l --message-level 1 -d 31 #增加-c参数,代表压缩coredump文件
default reboot #生成coredump后,重启系统
systemctl start kdump.service //启动kdump
systemctl enable kdump.service //设置开机启动
[root@localhost ~]# service kdump status
Redirecting to /bin/systemctl status kdump.service
● kdump.service - Crash recovery kernel arming
Loaded: loaded (/usr/lib/systemd/system/kdump.service; enabled; vendor preset: enabled)
Active: active (exited) since Wed 2022-11-16 10:48:09 CST; 18s ago
Process: 1342 ExecStart=/usr/bin/kdumpctl start (code=exited, status=0/SUCCESS)
Main PID: 1342 (code=exited, status=0/SUCCESS)
CGroup: /system.slice/kdump.service
...
Jan 05 11:07:03 localhost.localdomain kdumpctl[1082]: Starting kdump: [OK]
...
如果看到”Starting kdump: [OK]“字样,说明Kdump服务已经成功配置。
[root@cloud ~]# echo 1 > /proc/sys/kernel/sysrq ; echo c > /proc/sysrq-trigger
如果Kdump配置正确,上述命令会让系统快速重启并且启动捕获内核以进行转储。
[root@localhost ~]# ls /var/crash/
127.0.0.1-2022-11-16-11:11:43
转储完成之后会自动切换回生产内核。在/var/crash
目录下存放了对应的coredump目录。(在2.3节中设置了将coredump目录存放在/var/crash中)。该目录是以IP地址和日期来命令的,目录里面包含了vmcore
和vmcore-dmesg.txt
两个文件,其中vmcore
是捕获的内核转储文件,vmcore-dmesg.txt
是生产内核发生崩溃时生成的内核日志信息。
在使用Crash工具进行分析之前,需要安装生产内核对应的调试信息的内核符号表。
sudo yum update -y
sudo yum install -y kernel-debuginfo-$(uname -r)
安装完成之后,带调试符号信息的内核在/usr/lib/debug/lib/modules/
目录下面
crash vmcore /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
这里vmlinux提供内核符号表,里面存了大量的全局变量地址和数据结构原理和代码等。 crash 根据你输入的命令去vmlinux中查找符号和地址,再去vmcore里面对应的位置获取真实数据,这样解析就完成了。
crash> bt
PID: 53022 TASK: ffff9dbe1ebc1040 CPU: 0 COMMAND: "bash"
#0 [ffff9dbe2060bae0] machine_kexec at ffffffffb7e63674
#1 [ffff9dbe2060bb40] __crash_kexec at ffffffffb7f1ce12
#2 [ffff9dbe2060bc10] crash_kexec at ffffffffb7f1cf00
#3 [ffff9dbe2060bc28] oops_end at ffffffffb856c758
#4 [ffff9dbe2060bc50] no_context at ffffffffb855aa7e
#5 [ffff9dbe2060bca0] __bad_area_nosemaphore at ffffffffb855ab15
#6 [ffff9dbe2060bcf0] bad_area_nosemaphore at ffffffffb855ac86
#7 [ffff9dbe2060bd00] __do_page_fault at ffffffffb856f6b0
#8 [ffff9dbe2060bd70] do_page_fault at ffffffffb856f915
#9 [ffff9dbe2060bda0] page_fault at ffffffffb856b758
[exception RIP: sysrq_handle_crash+22]
RIP: ffffffffb8261bf6 RSP: ffff9dbe2060be58 RFLAGS: 00010246
RAX: ffffffffb8261be0 RBX: ffffffffb8ae4c60 RCX: 0000000000000000
RDX: 0000000000000000 RSI: ffff9dbe3a613898 RDI: 0000000000000063
RBP: ffff9dbe2060be58 R8: ffffffffb8de38bc R9: 6873617263206120
R10: 000000000000073a R11: 0000000000000739 R12: 0000000000000063
R13: 0000000000000000 R14: 0000000000000004 R15: 0000000000000000
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
#10 [ffff9dbe2060be60] __handle_sysrq at ffffffffb826241d
#11 [ffff9dbe2060be90] write_sysrq_trigger at ffffffffb8262888
#12 [ffff9dbe2060bea8] proc_reg_write at ffffffffb80b7f30
#13 [ffff9dbe2060bec8] vfs_write at ffffffffb80410a0
#14 [ffff9dbe2060bf08] sys_write at ffffffffb8041ebf
#15 [ffff9dbe2060bf50] system_call_fastpath at ffffffffb8574ddb
RIP: 00007f8feee9aba0 RSP: 00007ffcde15a468 RFLAGS: 00000246
RAX: 0000000000000001 RBX: 0000000000000002 RCX: ffffffffffffffff
RDX: 0000000000000002 RSI: 00007f8fef7c6000 RDI: 0000000000000001
RBP: 00007f8fef7c6000 R8: 000000000000000a R9: 00007f8fef7ae740
R10: 00007f8fef7ae740 R11: 0000000000000246 R12: 00007f8fef173400
R13: 0000000000000002 R14: 0000000000000001 R15: 0000000000000000
ORIG_RAX: 0000000000000001 CS: 0033 SS: 002b
其中第一行显示了进程的pid,task_struct结构体地址,进程所在CPU和运行进程的命令。
后面的栈帧列出了该进程在内核态的函数调用关系,执行顺序是从下往上。#15是最开始执行的系统调用,一般#0是切换到crashkernel的执行。重点关注#9这个位置,打印出很多寄存器的地址, 标准的信息是 exception RIP表示出问题时候执行的指令。
dis命令可以用来输出反汇编结果。如显示10行崩溃点的反汇编代码。
crash> dis sysrq_handle_crash+22 10
0xffffffffb8261bf6 <sysrq_handle_crash+22>: movb $0x1,0x0
0xffffffffb8261bfe <sysrq_handle_crash+30>: pop %rbp
0xffffffffb8261bff <sysrq_handle_crash+31>: retq
0xffffffffb8261c00 <sysrq_handle_loglevel>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffb8261c05 <sysrq_handle_loglevel+5>: push %rbp
0xffffffffb8261c06 <sysrq_handle_loglevel+6>: xor %eax,%eax
0xffffffffb8261c08 <sysrq_handle_loglevel+8>: movl $0x7,0x7e599e(%rip) # 0xffffffffb8a475b0
0xffffffffb8261c12 <sysrq_handle_loglevel+18>: mov %rsp,%rbp
0xffffffffb8261c15 <sysrq_handle_loglevel+21>: push %rbx
0xffffffffb8261c16 <sysrq_handle_loglevel+22>: lea -0x30(%rdi),%ebx
其中 movb $0x1,0x0
表示把数字1赋值到地址0,地址0系统会判断为空指针,所以产生了panic,除此之外查看bt的打印,上面有write_sysrq_trigger函数,说明是我们手动echo c写sysrq-trigger触发的。
使用help命令可以查看其他更多命令
crash> help
* extend log rd task
alias files mach repeat timer
ascii foreach mod runq tree
bpf fuser mount search union
bt gdb net set vm
btop help p sig vtop
dev ipcs ps struct waitq
dis irq pte swap whatis
eval kmem ptob sym wr
exit list ptov sys q
也可以使用help命令具体查看某一个子命令的帮助说明,如查看struct命令的帮助说明。
crash> help struct
NAME
struct - structure contents
SYNOPSIS
struct struct_name[.member[,member]][-o][-l offset][-rfuxdp]
[address | symbol][:cpuspec] [count | -c count]
DESCRIPTION
This command displays either a structure definition, or a formatted display
of the contents of a structure at a specified address. When no address is
specified, the structure definition is shown along with the structure size.
A structure member may be appended to the structure name in order to limit
the scope of the data displayed to that particular member; when no address
is specified, the member's offset and definition are shown.
struct_name name of a C-code structure used by the kernel.