https://blog.csdn.net/Ivan804638781/article/details/111679583
系统产生异常时,触发Kdump机制,启动捕获内核,用以对生产内核下的内存进行收集和转存。
Kdump用于对内存镜像的转储,它不但可以转储内存镜像到本地硬盘,还可以将内存镜像通过NFS,SSH等协议转储到不同机器的设备上。
本文是将生产内核的内存数据转储在/var/crash目录下,下面开始对其进行分析。
分析vmcore的主要目的:
明白内核崩溃的大致原因;
可以对内核崩溃的原因做更细致的分析;
(一)准备Crash工具
crash是一个被广泛应用的内核奔溃转储文件分析工具。
crash工具,跟gdb很类似,它可以交互的分析运行中的内核,也可以分析由kdump、netdump、diskdump、xendump产生的core dump文件。
使用crash工具分析vmcore,需要:
crash工具(crash)
崩溃转储文件(vmcore)
发生崩溃的内核映像文件(vmlinux),包含调试内核所需调试信息。
查看crash是否已安装:
[root@yglocal ~]# rpm -q crash
crash-7.2.3-8.el7.x86_64
1
2
若没有安装过,则执行以下命令安装:
yum install crash
1
(二)准备内核映像文件
一般系统在安装后在/boot目录下,也有个内核映像文件,vmlinuxz开头的文件,但是它是压缩过后的,无法完成调试工作。
我们需要一个带内核调试信息的vmlinux镜像,它有两种获取方式:手动编译/官网下载
手动编译vmlinux
手动编译就是正常的编译一个Linux内核,不具体介绍了。
需要注意的是,make menuconfig里配置CONFIG_DEBUG_INFO=y。
官网下载vmlinux
下载带有完整调试信息的内核映像文件,内核调试信息包kernel-debuginfo有两个:
kernel-debuginfo
kernel-debuginfo-common
1
2
对于centos系统,可以在http://debuginfo.centos.org/上下载到各发行版本所需的调试包。
对于centos7.x,安装对应内核版本的内核调试包,执行如下即可:
# wget http://debuginfo.centos.org/7/x86_64/kernel-debuginfo-common-x86_64-`uname -r`.rpm
# wget http://debuginfo.centos.org/7/x86_64/kernel-debuginfo-`uname -r`.rpm
1
2
下载完后,安装内核调试包:
rpm -ivh *.rpm
1
安装完成后,可以在/lib/debug/lib/modules/3.10.0-957.el7.x86_64/目录下看到vmlinux内核映像文件:
[root@yglocal ~]# ll -th /lib/debug/lib/modules/3.10.0-957.el7.x86_64/
total 419M
drwxr-xr-x. 2 root root 119 Mar 26 13:13 vdso
drwxr-xr-x. 12 root root 128 Mar 26 13:13 kernel
-rwxr-xr-x. 2 root root 419M Nov 9 2018 vmlinux
1
2
3
4
5
(三)查看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/crashcrash 目录下的文件分别为:
ls -l /var/crash/127.0.0.1-xxx
total 1735504
-rw------- 1 root root 1776288176 Dec 24 16:45 vmcore
-rw-r--r-- 1 root root 859872 Dec 24 16:45 vmcore-dmesg.txt
1
2
3
4
5
如果/var/crash/目录下还没有产生vmcore文件,可以手动触发模拟系统异常崩溃:
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
1
2
重启后,查看/var/crash目录
ls /var/crash/
1
(四)解析vmcore
Crash工具即为专门用于分析vmcore文件的工具,其中提供了大量的实用分析命令,极大的提高了vmcore的分析效率。
Ps:
crash解析vmcore文件,还需要vmlinux文件,这其中可能会涉及到很多的问题,专门再开一个文章讲一下:《【调试工具】【Kdump】crash工具报错及相关处理【四】》
通过执行crash命令,解析vmcore文件,如下:
crash xxx/vmlinux xxx/vmcore
例如:
crash /boot/vmlinux /var/crash/127.0.0.1-2020-12-24-17:20:41/vmcore
1
2
3
执行结果如下:
GNU gdb (GDB) 7.6
Copyright (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/vmlinux
DUMPFILE: /var/crash/127.0.0.1-2020-12-24-17:20:41/vmcore [PARTIAL DUMP]
CPUS: 8
DATE: Thu Dec 24 17:20:34 2020
UPTIME: 00:34:45
LOAD AVERAGE: 3.37, 3.71, 3.38
TASKS: 1316
NODENAME: fe0fdb76-b761-11e9-b107-0014101e89e7
RELEASE: 3.10.0+
VERSION: #1 SMP Thu Dec 24 16:27:16 CST 2020
MACHINE: x86_64 (3408 Mhz)
MEMORY: 31.9 GB
PANIC: "SysRq : Trigger a crash"
PID: 1942
COMMAND: "bash"
TASK: ffff88068c957300 [THREAD_INFO: ffff88062b8f4000]
CPU: 2
STATE: TASK_RUNNING (SYSRQ)
crash>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
其实通过上述的“PANIC”字段信息,已经能大致分析出vmcore产生的原因,解析如下:
PANIC: "SysRq : Trigger a crash"
解析:
产生Panic的原因是sysrq触发了一个crash(Ps:我使用了sysrq模拟内核崩溃“echo c > /proc/sysrq-trigger”)
1
2
3
4
bt命令
通过“bt”命令+上述的“PID”字段,可以打印问题进程的栈信息,如下:
crash> bt 1942
PID: 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: 00010046
RAX: 000000000000000f RBX: ffffffff81a7b180 RCX: 0000000000000000
RDX: 0000000000000000 RSI: ffff88086ec8f6c8 RDI: 0000000000000063
RBP: ffff88062b8f7e80 R8: 0000000000000092 R9: 0000000000000e37
R10: 0000000000000e36 R11: 0000000000000003 R12: 0000000000000063
R13: 0000000000000246 R14: 0000000000000004 R15: 0000000000000000
ORIG_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 ffffffff81691049
RIP: 00007fb320bcb500 RSP: 00007ffde533c198 RFLAGS: 00000246
RAX: 0000000000000001 RBX: ffffffff81691049 RCX: ffffffffffffffff
RDX: 0000000000000002 RSI: 00007fb3214eb000 RDI: 0000000000000001
RBP: 00007fb3214eb000 R8: 000000000000000a R9: 00007fb3214d5740
R10: 0000000000000001 R11: 0000000000000246 R12: 0000000000000001
R13: 0000000000000002 R14: 00007fb320e9f400 R15: 0000000000000002
ORIG_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个参数的,仍存储在栈上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
dis命令
通过“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/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 138
0xffffffff813baf16 <sysrq_handle_crash+22>: movb $0x1,0x0
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 139
0xffffffff813baf1e <sysrq_handle_crash+30>: pop %rbp
0xffffffff813baf1f <sysrq_handle_crash+31>: retq
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 85
0xffffffff813baf20 <sysrq_handle_loglevel>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffff813baf25 <sysrq_handle_loglevel+5>: push %rbp
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 90
0xffffffff813baf26 <sysrq_handle_loglevel+6>: xor %eax,%eax
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 89
0xffffffff813baf28 <sysrq_handle_loglevel+8>: movl $0x7,0x6322be(%rip) # 0xffffffff819ed1f0 <console_printk>
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 85
0xffffffff813baf32 <sysrq_handle_loglevel+18>: mov %rsp,%rbp
0xffffffff813baf35 <sysrq_handle_loglevel+21>: push %rbx
/home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0 -- MORE -- forward: <SPACE>, <ENTER> or j backward: b or k
/drivers/tty/sysrq.c: 88
0xffffffff813baf36 <sysrq_handle_loglevel+22>: lea -0x30(%rdi),%ebx
解析:
代码位置+函数+函数偏移位置+该位置的汇编指令...
很容易定义到具体的哪一行代码,更方便我们去分析问题的原因!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sym命令
通过“dis”命令,我们很容易得到“代码位置+函数+函数偏移位置+该位置的汇编指令…”等等信息
应该能很容易定位到具体哪一行代码出问题。
什么?还是分析不出来!?能不能再具体一点?难道还想让它给你指出具体问题出在哪一行了?
当然也可以!
“sym 内存地址”可以看到这个地址上对应的符号表信息,并且具体到源代码的那一行!!!
crash> sym ffffffff813baf16
ffffffff813baf16 (t) sysrq_handle_crash+22 /home/zhugeyifan/source/jd4000/jd4000_x86/packages/linux_lsp/kernel/linux-3.10.0/drivers/tty/sysrq.c: 138
解析:
出问题的位置在“drivers/tty/sysrq.c”源文件中的第138行。
1
2
3
4
5
其他crash命令
通过help查看其他命令。
log命令:打印vmcore所在的系统内核日志信息3
mod命令:查看当时内核加载的所有内核模块信息
ps命令:打印进程信息
files命令:打印指定进程所打开的文件信息
vm命令:打印指定进程当时虚拟内存基本信息
task命令:查看当前进程或指定进程task_struct和thread_info的信息。(它会把task_struct中所有成员的值打印出来)
kmem命令:查看当时系统内存使用信息
...
1
2
3
4
5
6
7
8
9
写在最后
其实也可以不通过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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
在dmesg信息中,我们得到了出现异常时,PC指针所在的位置,再通过反汇编内核镜像,得到汇编级的代码,一样可以分析出具体出问题在哪一行
objdump -DS vmlinux > vmlinux.txt
1
再分析汇编vmlinux.txt文件:
ffffffff813baf00 <sysrq_handle_crash>:
static void sysrq_handle_crash(int key)
{
ffffffff813baf10: 48 89 e5 mov %rsp,%rbp
char *killer = NULL;
panic_on_oops = 1; /* force panic */
wmb();
ffffffff813baf13: 0f ae f8 sfence
*killer = 1;
ffffffff813baf16: c6 04 25 00 00 00 00 movb $0x1,0x0
ffffffff813baf1d: 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偏移地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
当然在这需要一定的汇编代码功底,才能明白。如果崩溃处对应有c代码的话,排查起来就简单多了。
参考:
Linux内核调试之kdump
kdump 简介 & 配置、触发等
移植Kdump至嵌入式ARM64环境
Kdump 原理探秘
kexec
kdump和crash的配置方法与内核故障原因分析(一)
使用crash分析linux内核崩溃转储文件vmcore
kdump在Debian / Fedora / CentOS下的配置
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Ivan804638781/article/details/111679583