Kdump是目前最有效的Linux内存镜像收集机制,在调试Linux系统内核方面有着不可替代的重要作用,广泛应用于各大linux厂商的各种产品中。
如果你运行某个驱动程序时,系统莫名其妙的没有反应了,没有任何的异常信息输出,查看驱动程序的运行日志也看不到有价值的信息,通过系统监控发现,在系统没有反应之前系统的CPU、内存、负载、流量都很正常,但突然没有响应了!驱动程序一般运行在系统的内核态中,排查系统内核态的异常是较为复杂的,这时kdump就有用武之地了,可以导出出问题的驱动程序的内存及异常上下文信息到dump转储文件中,事后我们去分析这个dump文件,可能就能很快定位发生异常的原因了。今天我们就来详细地讲述一下Kdump转储工具的使用。
一、Kdump简介
Kdump出现在 2005年左右,是迄今为止最可靠的内核转存机制,已经被主要的 linux 厂商选用。Kdump是一种先进的基于 kexec 的内核崩溃转储机制,用来捕获kernel crash(内核崩溃)时候产生的crash dump。当内核产生错误(系统崩溃、死锁或者死机)时,kdump会将内存导出为vmcore保存到磁盘或通过 NFS, SSH 等协议转储到不同机器的设备上。
Kdump的实现可以分为两个部分:内核机制和用户工具。内核提供机制,用户工具在这些机制上实现各种转储策略。内核机制对用户工具的接口是一个系统调用:kexec_load(),它被用于加载捕获内核和传递一些相关信息。
Kdump的实现依赖于kexec,下面分别介绍kexec、kdump这两个机制相关的内容。
1、kexec及其实现机制
kexec 是一种快速启动机制,允许在当前运行的内核(生产内核)的上下文中启动一个新的Linux内核(捕获内核),而不需要通过耗时的 BIOS 检测,方便内核开发人员对内核进行调试。
kexec的实现包括两个部分:内核空间和用户空间。
一是内核空间的系统调用:kexec_load(),负责在生产内核(production kernel 或 first kernel)启动时将捕获内核(capture kernel或sencond kernel)加载到指定地址。
kexec 在 kernel 里以一个系统调用 kexec_load() 的形式提供给用户。这个系统调用主要用来把另一个内核和其 ramdisk 加载到当前内核中。在 kdump 中,捕获内核只能使用事先预留的一小段内存。生产内核的内存镜像会被以 /proc/vmcore 的形式提供给用户。这是一个 ELF 格式的文件,它的头是由用户空间工具 kexec(包含在kexec-tools工具集中)生成并传递来的。在系统崩溃时,系统最后会调用 machine_kexec()。这通常是一个硬件相关的函数。它会引导捕获内核,从而完成 kdump 的过程。
二是用户空间的工具kexec-tools,他将捕获内核的地址传递给生产内核,从而在系统崩溃的时候能够找到捕获内核的地址并运行。没有kexec就没有kdump。先有kexec实现了在一个内核中可以启动另一个内核,才让kdump有了用武之地。
kdump 的很大一部分工作都是在用户空间内完成的。与 kexec 相关的集成在一个叫“kexec-tools”的工具中的“kexec”程序中。该程序主要是为调用 kexec_load() 收集各种信息,然后调用之。这些信息主要包括 purgatory 的入口地址,还有一组由 struct kexec_segment 描述的信息。
2、相关概念
生产内核 :第一个运行的内核(正常的系统运行的内核)。
捕获内核:第二个运行的内核(系统异常时,会启动捕获内核,用以对生产内核下的内存进行收集和转储)。
vmcore:这里是指收集到的生产内核产生的内存核心内容。
ELF文件:这里指的是内核分析出内存的使用和分布等情况,然后把这些信息综合起来生成一个ELF头文件保存起来。
ramdisk:这里的ramdisk实际上就是把一段内存假设为一个硬盘驱动器(使用ramdisk作为文件系统可以大幅提高读写速度)。
3、kdump及其实现机制
kdump机制的实现需要两个不同目的的内核,生产内核和捕获内核。捕获内核为生产内核服务。捕获内核会在生产内核崩溃时启动起来,与相应的ramdisk一起组建一个微环境,用以对生产内核下的内存进行收集和转储。注意,在启动时,kdump保留了一定数量的重要的内存,为了计算系统需要的真正最小内存,加上kdump使用的内存数量,以决定真正的最小内存的需求。
生产内核就是系统正常运行的一般内核。生产内核保留了内存的一部分给捕获内核启动用。由于kdump利用kexec启动捕获内核,绕过了 BIOS,所以生产内核的内存得以保留。这是内核崩溃转储的本质。
捕获内核启动后,会像一般内核一样,去运行为它创建的 ramdisk 上的 init 程序。而各种转储机制都可以事先在 init 中实现。为了在生产内核崩溃时能顺利启动捕获内核,捕获内核(以及它的 ramdisk)是事先放到生产内核的内存中的。而捕获内核启动后需要使用的一小部分内存是通过 crashkernel=Y@X 这一内核参数在生产内核中保存的。
生产内核的内存是通过 /proc/vmcore 这个文件交给捕获内核的。为了生成它,用户工具先在生产内核中分析出内存的使用和分布等情况,然后把这些信息综合起来 生成一个 ELF 文件头保存起来。捕获内核被引导时会被同时传递这个 ELF 文件头的地址,通过分析它,捕获内核就可以生成出 /proc/vmcore。有了 /proc/vmcore 这个文件,捕获内核的 ramdisk 中的脚本就可以通过通常的文件读写和网络来实现 各种策略了。同时 kdump 的用户工具还提供了缩减内存镜像尺寸的工具。这就是 Kdump 的基本设计。
4、kdump执行流程
当系统崩溃时,kdump使用kexec启动到第二个内核。第二个内核叫做捕获内核,启动的时候会与相应的ramdisk一起组成一个微环境,用于转储系统内存,导出为vmcore并保存到磁盘或者远程设备上。具体流程如下:

1)First kernel(生产内核)正常运行;
2)运行过程中,系统出现异常(也可以是模拟通过sysrq触发panic);
3)在系统崩溃时,系统最后会调用 machine_kexec(),触发并启动Sencond kernel(捕获内核),传递ELF头文件的地址;
4)捕获内核与相应的ramdisk一起组建一个微环境,获取ELF头文件的地址,并生成出/proc/vmcore文件;
5)捕获内核的ramdisk中的脚本开始执行,将/proc/vmcore文件中的数据通过文件读写和网络来实现对生产内核下的内存进行收集和转储。
6)通过gdb、crash等工具,对收集到的vmcore文件镜像分析。
二、Kdump配置与使用
不同的Linux发行版本对Kdump的配置都是不一样的,本文主要介绍CentOS的Kdump配置。想要实现Kdump功能,主要有以下几个方面需要进行配置:
内核配置
安装kdump(kexec-tools)工具
修改内核启动参数("crashkernel=xxxM quiet")
修改kdump配置文件
启动kdump功能
验证Kdump功能
1、内核配置
通常我们认为X86设备默认内核是已经配置了Kdump功能的。
想要确认当前内核是否支持kexec,可通过查看在/sys/kernel/下有没有kexec等文件去确认。

同时通过查看/sys/kernel/kexec_crash_loaded 的值,判断Kdump功能是否加载(“1”为已经加载,“0”为还未加载)
[]1
如果没有,则需要我们对内核进行配置,具体如下(make menuconfig):
CONFIG_SUSPEND=yPower management options --->[*] Suspend to RAM and standbyCONFIG_KEXEC=yBoot options --->[*] Kexec system call (EXPERIMENTAL)//此参数告诉系统使用Kexec跳过BIOS和引导(新)内核。(提供内核层面的kexec功能支持)CONFIG_CRASH_DUMP=yBoot options --->[*] Build kdump crash kernel (EXPERIMENTAL)//崩溃转储需要启用。没有此选项,Kdump将毫无用处。(提供内核层面的kdump功能支持)CONFIG_SYSFS=yFile systems --->Pseudo filesystems --->[*] Tmpfs virtual memory file system support//启用sysfs文件系统支持CONFIG_PROC_VMCORE=yFile systems --->Pseudo filesystems --->-*- /proc file system support--->[*] /proc/vmcore support//此配置允许Kdump将内存转储保存到/proc/vmcore。CONFIG_DEBUG_INFO=yKernel hacking --->Compile-time checks and compiler options --->[*] Compile the kernel with debug info//此参数表示将使用调试符号构建内核。尽管这将增加内核映像的大小,但是具有可用的符号对于深入分析内核崩溃非常有用,因为它不仅使您可以跟踪导致崩溃的有问题的函数调用问题,而且可以跟踪特定行在相关来源中。
2、安装kdump
通常我们认为X86设备默认内核是已经配置了Kdump功能的。kdump依赖于kexec工具,验证kexec工具是否存在:
[root@compute2 grub2]# kexec -vkexec-tools 2.0.21
如果没有安装kexec工具,安装kexec:
sudo yum updatesudo yum search kexec-toolssudo yum install kexec-tools
3、修改内核启动参数
修改/etc/default/grub内核启动文件,如下:
vi /etc/default/grub
在GRUB_CMDLINE_LINUX行增加如下内容:
GRUB_CMDLINE_LINUX="crashkernel=128M quiet"
参数说明:
crashkernel=xxx #预留内存大小
quiet #表示在启动过程中只有重要信息显示,类似硬件自检的消息不会显示。
crashkernel参数格式是:crashkernel=nn[KMG]@ss[KMG]
nn表示要为crashkernel预留多少内存
ss表示为crashkernel预留内存的起始位置注意如果不加quiet可能会导致生成不了vmcore,原因暂时不明。另外,注意预留内存大小,过大/过小都会导致生成vmcore文件失败(不知道设置多少时,可以尝试每次增加128M)。只要更改了grub文件,都需要更新grub配置。执行完毕后,需要重启才能生效。
修改后的/etc/default/grub内核启动文件内容如下:
[root@compute2 grub2]# cat /etc/default/grubGRUB_TIMEOUT=5GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"GRUB_DEFAULT=savedGRUB_DISABLE_SUBMENU=trueGRUB_TERMINAL_OUTPUT="console"GRUB_CMDLINE_LINUX="crashkernel=512M rhgb quiet intermap=on iommu=pt console=ttyS0,115200 console=ttyS1,115200 intel_iommu=on pci=realloc default_hugepagesz=1G hugepagesz=1G hugepages=4 hugepagesz=2M hugepages=1375"GRUB_DISABLE_RECOVERY="true"GRUB_ENABLE_BLSCFG=true
还可以将保留的内存量设置为可变的,具体取决于安装的内存总量。可变内存预留的语法是crashkernel=<range1>:<size1>,<range2>:<size2>
crashkernel=512M-2G:64M,2G-:128M
如果系统内存总量为 512 MB 或更高且低于 2 GB,则上述示例保留 64 MB 内存。如果内存总量超过 2 GB,则为 kdump 保留 128 MB。
一些系统需要保留具有某个固定偏移量的内存。如果设置了偏移量,则保留的内存从那里开始。要偏移保留的内存,请使用以下语法:
crashkernel=128M@16M
上面的例子意味着 kdump 保留从 16 MB 开始的 128 MB 内存(物理地址 0x01000000)。如果 offset 参数设置为 0 或完全省略,kdump 会自动偏移保留的内存。如上所述设置可变内存预留时也可以使用此语法;在这种情况下,总是最后指定偏移量。
例如:
crashkernel=512M-2G:64M,2G-:128M@16M
更新grub配置,执行完毕后,重启主机:
sudo grub2-mkconfig -o /boot/grub2/grub.cfgreboot
重启成功后,验证是否已成功配置启动参数:
cat /proc/cmdline
说明:
如果在cmdline里找到了crashkernel参数,代表已经将配置写入内核了
4、修改kdump配置文件
vi /etc/kdump.conf
打开kdump.conf文件后,其中需要注意的三行内容:
...
path /var/crash #指定coredump文件放在/var/crash文件夹中
core_collector makedumpfile -l -message-level 1 -d 31
default reboot #生成coredump后,重启系统
...
要更改要保存核心转储的本地目录,请删除行首的井号(“#”)
#path /var/crash,并将该值替换为所需的目录路径,如path /usr/local/cores
使用/etc/kdump.conf中的core_collector指令修改vmcore转储的过滤级别。要从转储中排除某些页面,请使用-d[value]参数,其中[value]是要排除的页面的值之和。对页面使用以下值:
1: zero page
2: cache page
4: cache private
8: user data
16: free page
建议如下排除所有这些页面。将这些值相加(所有值的总和为31),并将总和作为参数提供给-d(转储级别)选项:
core_collector makedumpfile -d 31
仅排除零(1)页和自由(16)页:
core_collector makedumpfile -d 17
makedumpfile工具,负责将故障内核的内存映像copy,压缩,写到指定文件

5、启动kdump
执行命令启动kdump服务,如下:
systemctl start kdump.service #开启kdump服务systemctl enable kdump.service #开机即启动kdump服务
如果您之前配置过kdump服务的,可以使用restart命令重新启动下,否则不一定能用,如:
systemctl restart kdump.service
查看Kdump服务
systemctl status kdump

6、验证kdump
最后,通过模拟系统异常崩溃,验证kdump是否已启用:
echo 1 > /proc/sys/kernel/sysrqecho c > /proc/sysrq-trigger
重启后,查看/var/crash目录。每一次内核崩溃都会在/var/crash目录下创建一个127.0.0.1-xxx(后缀是产生该目录时的具体时间)对应的目录,里面保存有vmcore和vmcore-dmesg.txt文件。

说明:
vmcore-dmesg.txt是生产内核的dmesg信息。
vmcore文件为通过kdump等手段收集的操作系统core dump信息,在不采用压缩的情况下,其相当于整个物理内存的镜像,所以其中包括了最全面、最完整的信息,对于分析定位各种疑难问题有极大的帮助。配置kdump后,在内核panic后,会自动进入kump流程,搜集并转储vmcore。
三、kdump解析
解析kdump捕获的vmcore文件,需要准备如下:
crash工具;
发生崩溃的内核映像文件(vmlinux),包含调试内核所需调试信息;
崩溃转储文件(vmcore);
crash工具,跟gdb很类似,它可以交互的分析运行中的内核,也可以分析由kdump、netdump、diskdump、xendump产生的core dump文件。
1、查看是否安装crash工具
[root@compute2 ~]# rpm -q crashcrash-7.3.2-2.el8.x86_64
若没有安装过,则执行以下命令安装:
yum install crash
2、准备内核映像文件
一般系统在安装后在/boot目录下,也有个内核映像文件,vmlinuxz开头的文件,但是它是压缩过后的,无法完成调试工作。我们需要一个带内核调试信息的vmlinux镜像,它有两种获取方式:手动编译/官网下载。
手动编译vmlinux
手动编译就是正常的编译一个Linux内核,不具体介绍了。
需要注意的是,make menuconfig里配置CONFIG_DEBUG_INFO=y。
官网下载vmlinux
下载带有完整调试信息的内核映像文件,内核调试信息包kernel-debuginfo有两个:
kernel-debuginfokernel-debuginfo-common
对于centos系统,可以在centos官方网站http://debuginfo.centos.org/上下载到各发行版本所需的调试包。对于centos7.x,安装对应内核版本的内核调试包,执行如下即可:
下载完后,安装内核调试包:
rpm -ivh *.rpm
安装完成后,可以在/lib/debug/lib/modules/3.10.0-957.el7.x86_64/目录下看到vmlinux内核映像文件:
[]total 419Mdrwxr-xr-x. 2 root root 119 Mar 26 13:13 vdsodrwxr-xr-x. 12 root root 128 Mar 26 13:13 kernel-rwxr-xr-x. 2 root root 419M Nov 9 2018 vmlinux
3、查看vmcore
每一次内核崩溃都会在/var/crash目录下创建一个127.0.0.1-xxx(后缀是产生该目录时的具体时间)对应的目录,里面保存有vmcore和vmcore-dmesg.txt文件。
Ps: vmcore-dmesg.txt是生产内核的dmesg信息。vmcore文件为通过kdump等手段收集的操作系统core dump信息,在不采用压缩的情况下,其相当于整个物理内存的镜像,所以其中包括了最全面、最完整的信息,对于分析定位各种疑难问题有极大的帮助。配置kdump后,在内核panic后,会自动进入kump流程,搜集并转储vmcore。
/var/crash 目录下的文件分别为:

crash工具即为专门用于分析vmcore文件的工具,其中提供了大量的实用分析命令,极大的提高了vmcore的分析效率。通过执行crash命令,解析vmcore文件,如下:
crash xxx/vmlinux xxx/vmcore
例如:
crash /boot/vmlinux /var/crash/127.0.0.1-2022-8-25-17:20:41/vmcore
上述命令的执行结果如下:
GNU gdb (GDB) 7.6Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-unknown-linux-gnu"...KERNEL: /boot/vmlinuxDUMPFILE: /var/crash/127.0.0.1-2022-8-25-17:20:41/vmcore [PARTIAL DUMP]CPUS: 8DATE: Thu Aug 25 17:20:34 2022UPTIME: 00:34:45LOAD AVERAGE: 3.37, 3.71, 3.38TASKS: 1316NODENAME: compute2RELEASE: 3.10.0+VERSION: #1 SMP Thu Aug 25 16:27:16 CST 2022MACHINE: x86_64 (3408 Mhz)MEMORY: 31.9 GBPANIC: "SysRq : Trigger a crash"PID: 1942COMMAND: "bash"TASK: ffff88068c957300 [THREAD_INFO: ffff88062b8f4000]CPU: 2STATE: TASK_RUNNING (SYSRQ)crash>
其实通过上述的“PANIC”字段信息,已经能大致分析出vmcore产生的原因。产生Panic的原因是sysrq触发了一个crash(Ps:我使用了sysrq模拟内核崩溃“echo c > /proc/sysrq-trigger”)。
通过“bt”命令+上述的“PID”字段,可以打印问题进程的栈信息,如下:
crash> bt 1942PID: 1942 TASK: ffff88068c957300 CPU: 2 COMMAND: "bash"#0 [ffff88062b8f7b48] machine_kexec at ffffffff81051e9b#1 [ffff88062b8f7ba8] crash_kexec at ffffffff810f27e2#2 [ffff88062b8f7c78] oops_end at ffffffff81689948#3 [ffff88062b8f7ca0] no_context at ffffffff816793f1#4 [ffff88062b8f7cf0] __bad_area_nosemaphore at ffffffff81679487#5 [ffff88062b8f7d38] bad_area_nosemaphore at ffffffff816795f1#6 [ffff88062b8f7d48] __do_page_fault at ffffffff8168c6ce#7 [ffff88062b8f7da8] do_page_fault at ffffffff8168c863#8 [ffff88062b8f7dd0] page_fault at ffffffff81688b48[exception RIP: sysrq_handle_crash+22]RIP: ffffffff813baf16 RSP: ffff88062b8f7e80 RFLAGS: 00010046RAX: 000000000000000f RBX: ffffffff81a7b180 RCX: 0000000000000000RDX: 0000000000000000 RSI: ffff88086ec8f6c8 RDI: 0000000000000063RBP: ffff88062b8f7e80 R8: 0000000000000092 R9: 0000000000000e37R10: 0000000000000e36 R11: 0000000000000003 R12: 0000000000000063R13: 0000000000000246 R14: 0000000000000004 R15: 0000000000000000ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018#9 [ffff88062b8f7e88] __handle_sysrq at ffffffff813bb6d2#10 [ffff88062b8f7ec0] write_sysrq_trigger at ffffffff813bbbaf#11 [ffff88062b8f7ed8] proc_reg_write at ffffffff812494bd#12 [ffff88062b8f7ef8] vfs_write at ffffffff811dee9d#13 [ffff88062b8f7f38] sys_write at ffffffff811df93f#14 [ffff88062b8f7f80] system_call_fastpath at ffffffff81691049RIP: 00007fb320bcb500 RSP: 00007ffde533c198 RFLAGS: 00000246RAX: 0000000000000001 RBX: ffffffff81691049 RCX: ffffffffffffffffRDX: 0000000000000002 RSI: 00007fb3214eb000 RDI: 0000000000000001RBP: 00007fb3214eb000 R8: 000000000000000a R9: 00007fb3214d5740R10: 0000000000000001 R11: 0000000000000246 R12: 0000000000000001R13: 0000000000000002 R14: 00007fb320e9f400 R15: 0000000000000002ORIG_RAX: 0000000000000001 CS: 0033 SS: 002b
可以看到最后几步触发了缺页异常,进入crash_kexec的流程,最后调用 machine_kexec()。这通常是一个硬件相关的函数。它会引导启动捕获内核,从而完成 kdump 的过程。代码就是走到了sysrq_handle_crash函数首地址+0x22这段命令的时候,触发的缺页异常。
注意这里,对应x86-64汇编,应用层下来的系统调用对应的6个参数存放的寄存器依次对应:rdi、rsi、rdx、rcx、r8、r9。对于多于6个参数的,仍存储在栈上。通过“bt命令”,我们可以分析异常进程的栈信息,得到关键信息:[exception RIP: sysrq_handle_crash+22],代码就是走到了sysrq_handle_crash函数首地址+0x22这段命令的时候,触发的缺页异常。
能不能再具体一点?当然可以!通过“dis -l (function+offset) 10” 命令,反汇编出指令所在代码,10代表打印该指定位置开始的10行信息。
crash> dis -l sysrq_handle_crash+22 10/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 1380xffffffff813baf16 <sysrq_handle_crash+22>: movb $0x1,0x0/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 1390xffffffff813baf1e <sysrq_handle_crash+30>: pop %rbp0xffffffff813baf1f <sysrq_handle_crash+31>: retq/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 850xffffffff813baf20 <sysrq_handle_loglevel>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]0xffffffff813baf25 <sysrq_handle_loglevel+5>: push %rbp/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 900xffffffff813baf26 <sysrq_handle_loglevel+6>: xor %eax,%eax/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 890xffffffff813baf28 <sysrq_handle_loglevel+8>: movl $0x7,0x6322be(%rip) # 0xffffffff819ed1f0 <console_printk>/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 850xffffffff813baf32 <sysrq_handle_loglevel+18>: mov %rsp,%rbp0xffffffff813baf35 <sysrq_handle_loglevel+21>: push %rbx/home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0 -- MORE -- forward: <SPACE>, <ENTER> or j backward: b or k/drivers/tty/sysrq.c: 880xffffffff813baf36 <sysrq_handle_loglevel+22>: lea -0x30(%rdi),%ebx
解析:代码位置+函数+函数偏移位置+该位置的汇编指令...很容易定义到具体的哪一行代码,更方便我们去分析问题的原因!
通过“dis”命令,我们很容易得到“代码位置+函数+函数偏移位置+该位置的汇编指令...”等等信息应该能很容易定位到具体哪一行代码出问题。
什么?还是分析不出来!?能不能再具体一点?难道想让它给你指出具体问题出在哪一行了?当然也可以!通过“sym 地址”命令,可以看到这个地址上对应的符号表信息,并且具体到源代码的那一行。
sym ffffffff813baf16ffffffff813baf16 (t) sysrq_handle_crash+22 /home/wangjunsource/sdx/sdx_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 138
出问题的位置在“drivers/tty/sysrq.c”源文件中的第138行。
还有其他的命令,就不一一举例了,简单的描述下:
log命令:打印vmcore所在的系统内核dmesg日志信息,可以重定向到文件(log > logfile)
mod命令:查看当时内核加载的所有内核模块信息
ps命令:打印进程信息
files命令:打印指定进程所打开的文件信息
vm命令:打印指定进程当时虚拟内存基本信息
task命令:查看当前进程或指定进程,task_struct和thread_info的信息,它会把task_struct中所有成员的值打印出来
kmem命令:查看当时系统内存使用信息
...
通过help查看其他命令。
四、根据dmesg信息分析
其实也可以不通过vmcore去分析问题的(毕竟vmcore的环境搭建起来不是那么的方便),日常通过有限的dmesg信息,也可以分析出很多内容。分析dmesg信息:
[ 2081.459132] SysRq : Trigger a crash[ 2081.462707] BUG: unable to handle kernel NULL pointer dereference at (null)[ 2081.470616] IP: [<ffffffff813baf16>] sysrq_handle_crash+0x16/0x20[ 2081.476810] PGD 7bf5a1067 PUD 679167067 PMD 0[ 2081.481364] Oops: 0002 [#1] SMP[ 2081.484652] Modules linked in: xfs(E) libcrc32c(E) dccp_diag(E) dccp(E) udp_diag(E) unix_diag(E) af_packet_diag(E) netlink_diag(E) iptable_nat(E) nf_conntrack_ipv4(E) nf_defrag_ipv4(E) nf_nat_ipv4(E) xt_addrtype(E) iptable_filter(E) xt_conntrack(E) nf_nat(E) nf_conntrack(E) bridge(E) stp(E) llc(E) overlay(E) tcp_diag(E) inet_diag(E) fuse(E) swcsm22(OE) intel_powerclamp(E) coretemp(E) intel_rapl(E) kvm_intel(E) kvm(E) crc32_pclmul(E) ghash_clmulni_intel(E) ppdev(E) aesni_intel(E) lrw(E) gf128mul(E) glue_helper(E) ablk_helper(E) cryptd(E) pcspkr(E) parport_pc(E) sg(E) parport(E) shpchp(E) acpi_pad(E) ip_tables(E) ext4(E) mbcache(E) jbd2(E) sd_mod(E) crc_t10dif(E) crct10dif_generic(E) crct10dif_pclmul(E) crct10dif_common(E) crc32c_intel(E) i915(E) ahci(E) igb(E) libahci(E) ptp(E) pps_core(E) drm_kms_helper(E)[ 2081.558510] dca(E) i2c_algo_bit(E) libata(E) drm(E) i2c_hid(E) video(E) dm_mirror(E) dm_region_hash(E) dm_log(E) dm_mod(E) brd(E)[ 2081.569304] CPU: 2 PID: 1942 Comm: bash Tainted: G W OE ------------ T 3.10.0+ #1[ 2081.577913] Hardware name: Default string Default string/SKYBAY, BIOS 5.11 03/13/2018[ 2081.586046] task: ffff88068c957300 ti: ffff88062b8f4000 task.ti: ffff88062b8f4000[ 2081.593740] RIP: 0010:[<ffffffff813baf16>] [<ffffffff813baf16>] sysrq_handle_crash+0x16/0x20[ 2081.602538] RSP: 0018:ffff88062b8f7e80 EFLAGS: 00010046[ 2081.608009] RAX: 000000000000000f RBX: ffffffff81a7b180 RCX: 0000000000000000[ 2081.615354] RDX: 0000000000000000 RSI: ffff88086ec8f6c8 RDI: 0000000000000063[ 2081.622619] RBP: ffff88062b8f7e80 R08: 0000000000000092 R09: 0000000000000e37[ 2081.629861] R10: 0000000000000e36 R11: 0000000000000003 R12: 0000000000000063[ 2081.637186] R13: 0000000000000246 R14: 0000000000000004 R15: 0000000000000000[ 2081.644561] FS: 00007fb3214d5740(0000) GS:ffff88086ec80000(0000) knlGS:0000000000000000[ 2081.652857] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033[ 2081.658767] CR2: 0000000000000000 CR3: 00000005eef57000 CR4: 00000000003407e0[ 2081.666046] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000[ 2081.673388] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400[ 2081.680661] Stack:[ 2081.682775] ffff88062b8f7eb8 ffffffff813bb6d2 0000000000000002 00007fb3214eb000[ 2081.690621] ffff88062b8f7f48 0000000000000002 0000000000000000 ffff88062b8f7ed0[ 2081.698520] ffffffff813bbbaf ffff8807bba10600 ffff88062b8f7ef0 ffffffff812494bd[ 2081.706545] Call Trace:[ 2081.709113] [<ffffffff813bb6d2>] __handle_sysrq+0xa2/0x170[ 2081.715079] [<ffffffff813bbbaf>] write_sysrq_trigger+0x2f/0x40[ 2081.721434] [<ffffffff812494bd>] proc_reg_write+0x3d/0x80[ 2081.727048] [<ffffffff811dee9d>] vfs_write+0xbd/0x1e0[ 2081.732391] [<ffffffff811df93f>] SyS_write+0x7f/0xe0[ 2081.737641] [<ffffffff81691049>] system_call_fastpath+0x16/0x1b[ 2081.743796] Code: eb 9b 45 01 f4 45 39 65 34 75 e5 4c 89 ef e8 e2 f7 ff ff eb db 0f 1f 44 00 00 55 c7 05 40 20 63 00 01 00 00 00 48 89 e5 0f ae f8 <c6> 04 25 00 00 00 00 01 5d c3 0f 1f 44 00 00 55 31 c0 c7 05 be[ 2081.764509] RIP [<ffffffff813baf16>] sysrq_handle_crash+0x16/0x20[ 2081.770853] RSP <ffff88062b8f7e80>[ 2081.774459] CR2: 0000000000000000
dmesg信息中有时候也会打印部分栈信息,具体看到这行:
IP: [<ffffffff813baf16>] sysrq_handle_crash+0x16/0x20
在dmesg信息中,我们得到了出现异常时,PC指针所在的位置,再通过反汇编内核镜像,得到汇编级的代码,一样可以分析出具体出问题在哪一行:
objdump -DS vmlinux > vmlinux.txt
再分析汇编vmlinux.txt文件:
ffffffff813baf00 <sysrq_handle_crash>:static void sysrq_handle_crash(int key){ffffffff813baf10: 48 89 e5 mov %rsp,%rbpchar *killer = NULL;panic_on_oops = 1; /* force panic */wmb();ffffffff813baf13: 0f ae f8 sfence= 1;ffffffff813baf16: c6 04 25 00 00 00 00 movb $0x1,0x0ffffffff813baf1d: 01}
根据sysrq_handle_crash+0x16 对应就是ffffffff813baf00+0x16=ffffffff813baf16或者直接根据上面的地址搜索,ffffffff813baf16 看到对应的汇编指令:c6 04 25 00 00 00 00 movb $0x1,0x0。
PS:“sysrq_handle_crash+0x16/0x20”这段的意思是说,函数的总长度就是sysrq_handle_crash首地址+0x20偏移地址,当前异常的位置是sysrq_handle_crash首地址+0x16偏移地址!
当然在这需要一定的汇编代码功底,才能明白。如果崩溃处对应有c代码的话,排查起来就简单多了。
五、总结
我们有时往往距离掌握Linux内核的秘密只有一步之遥。通过kdump,我们可以在系统崩溃时收集内存及关键信息,对其进行分析。如果想要将kdump利用到有些产品上,过程相对要复杂一些,可能要执行下面一些步骤:
需要预留一部分内存空间(用于存放捕获内核);
需要配置内核(.config文件);
需要修改配置文件(内核启动参数、kdump配置);
需要编译时保留vmliunx,以便在外部发生问题时,通过crash分析(vmlinux必须带debuginfo调试信息);
还需要安装各类工具(kexec、kdump、crash…)。
附件
Crash命令列表
| 命令 | 功能 |
|---|---|
| * | 指针快捷键 |
| alias | 命令快捷键 |
| ascii | ASCII码转换和码表 |
| bpf | eBPF - extended Berkeley Filter |
| bt | 堆栈查看 |
| btop | 地址页表转换 |
| dev | 设备数据查询 |
| dis | 返汇编 |
| eval | 计算器 |
| exit | 退出 |
| extend | 命令扩展 |
| files | 打开的文件查看 |
| foreach | 循环查看 |
| fuser | 文件使用者查看 |
| gdb | 调用gdb执行命令 |
| help | 帮助 |
| ipcs | 查看system V IPC工具 |
| irq | 查看irq数据 |
| kmem | 查看Kernel内存 |
| list | 查看链表 |
| log | 查看系统消息缓存 |
| mach | 查看平台信息 |
| mod | 加载符号表 |
| mount | Mount文件系统数据 |
| net | 网络命令 |
| p | 查看数据结构 |
| ps | 查看进程状态信息 |
| pte | 查看页表 |
| ptob | 页表地址转换 |
| ptov | 物理地址虚拟地址转换 |
| rd | 查看内存 |
| repeat | 重复执行 |
| runq | 查看run queue上的线程 |
| search | 搜索内存 |
| set | 设置线程环境和Crash内部变量 |
| sig | 查询线程消息 |
| struct | 查询结构体 |
| swap | 查看swap信息 |
| sym | 符号和虚拟地址转换 |
| sys | 查看系统信息 |
| task | 查看task_struct和thread_thread信息 |
| timer | 查看timer队列 |
| tree | 查看radix树和rb树 |
| union | 查看union结构体 |
| vm | 查看虚拟内存 |
| vtop | 虚拟地址物理地址转换 |
| waitq | 查看wait queue上的进程 |
| whatis | 符号表查询 |
| wr | 改写内存 |
| q | 退出 |
参考:
详解Linux内核态调试工具kdump - 代码天地
https://www.codetd.com/article/14412465

