如何使用crash工具分析Linux内核崩溃转储文件
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用crash工具分析Linux内核崩溃转储文件相关的知识,希望对你有一定的参考价值。
参考技术A 由于 crash 用于调试内核崩溃的转储文件,因此使用 crash 需要依赖如下条件:1. kernel 映像文件 vmlinux 在编译的时候必须指定了 -g 参数,即带有调试信息。
2. 需要有一个内存崩溃转储文件(例如 vmcore),或者可以通过 /dev/mem 或 /dev/crash 访问的实时系统内存。如果 crash 命令行没有指定转储文件,则 crash 默认使用实时系统内存,这时需要 root 权限。
3. crash 支持的平台处理器包括:x86, x86_64, ia64, ppc64, arm, s390, s390x ( 也有部分 crash 版本支持 Alpha 和 32-bit PowerPC,但是对于这两种平台的支持不保证长期维护 )。
4. crash 支持 2.2.5-15(含)以后的 Linux 内核版本。随着 Linux 内核的更新,crash 也在不断升级以适应新的内核。
启动参数说明
使用 crash 调试转储文件,需要在命令行输入两个参数:debug kernel 和 dump file,其中 dump file 是内核转储文件的名称,debug kernel 是由内核调试信息包安装的,不同的发行版名称略有不同,以 RHEL 和 SLES 为例:
RHEL6.2:/usr/lib/debug/lib/modules/2.6.32-220.el6.ppc64/vmlinux
SLES11SP2:/usr/lib/debug/boot/vmlinux-3.0.13-0.27-ppc64.debug
使用 crash -h 或 man crash 可以查看 crash 支持的一系列选项,这里仅以常用的选项为例说明如下:
-h:打印帮助信息
-d:设置调试级别
-S:使用 /boot/System.map 作为默认的映射文件
-s:不显示版本、初始调试信息等,直接进入命令行
-i file:启动之后自动运行 file 中的命令,再接受用户输入
linux 使用kdump和crash工具调试内核
目录
原理
kdump是内核转存工具
生产内核在崩溃时,启动kexec捕获内核,快速切换到备份的内核
crash使用方式
crash [vmcore] [vmlinux]
vmcore:转储的内核文件
vmlinux:带调试内核符号信息的内核映像,make编译内核源码时会生成
启动调试信息
# crash dump.202212030803 /mnt/vmlinux
WARNING: kernel version inconsistency between vmlinux and dumpfile
KERNEL: /mnt/vmlinux
DUMPFILE: dump.202212030803 [PARTIAL DUMP]
CPUS: 4
DATE: Sat Dec 3 08:03:00 2022
UPTIME: 00:56:21
LOAD AVERAGE: 0.00, 0.00, 0.03
TASKS: 84
NODENAME: benshushu
RELEASE: 5.0.0+
VERSION: #13 SMP Sat Dec 3 12:44:47 CST 2022
MACHINE: aarch64 (unknown Mhz)
MEMORY: 1 GB
PANIC: "Unable to handle kernel NULL pointer dereference at virtual address 0000000000000050"
PID: 709
COMMAND: "insmod"
TASK: ffff80001f531c00 [THREAD_INFO: ffff80001f531c00]
CPU: 1
STATE: TASK_RUNNING (PANIC)
- PANIC 提示崩溃的原因
- PID 崩溃的进程
- COMMAND 崩溃时的命令
- TASK 崩溃时进程的task_struct数据结构的地址
- STATE 发送崩溃时的进程状态
crash 常用命令
bt 输出一个进程的内核栈的函数调用关系
dis 用来输出反汇编结果
mod 显示当前系统加载的内核模块信息;加载某个内核块的符号信息和调试信息;
sym 解析内核符号信息 如sym -m oops; sym -m create_oops
rd 用来读取内存地址中的值,rd addr len
struct 用来显示内核中数据结构的定义或者值 struct vm_area_struct
p 用来输出内核变量,表达式或者符号信息,如p jiffies
irq 显示中断相关信息
task 用来显示task_struct 数据结构和thread_info数据结构的内容
vm 显示进程的地址空间的相关信息,如 vm -p pid
kmem 显示系统内存信息,如 kmem -i
list 变量链表,可以输出链表成员的值,如list -s
错误分析
bt 查看崩溃时内核函数的调用关系
crash> bt
PID: 709 TASK: ffff80001f531c00 CPU: 1 COMMAND: "insmod"
#0 [ffff80001f51ecb0] machine_kexec at ffff0000100dc0c8
#1 [ffff80001f51edd0] __crash_kexec at ffff00001037c9c0
#2 [ffff80001f51ef60] crash_kexec at ffff00001037cc10
#3 [ffff80001f51f030] die at ffff0000100adb8c
#4 [ffff80001f51f170] die_kernel_fault at ffff0000100e6684
#5 [ffff80001f51f1a0] __do_kernel_fault at ffff0000100e68a8
#6 [ffff80001f51f220] do_page_fault at ffff0000119081dc
#7 [ffff80001f51f490] do_translation_fault at ffff0000119082ec
#8 [ffff80001f51f4f0] do_mem_abort at ffff000010081280
#9 [ffff80001f51f660] el1_ia at ffff0000100858cc
PC: ffff00000991001c [create_oops+28]
LR: ffff0000099150a0 [_MODULE_INIT_START_oops+160]
SP: ffff80001f51f670 PSTATE: 80000005
X29: ffff80001f51f670 X28: ffff80001f531c00 X27: 0000000000000000
X26: 0000000000000000 X25: 0000000056000000 X24: 0000000000000015
X23: 0000000040001000 X22: 0000ffff95239ec4 X21: 00000000ffffffff
X20: 000080001df80000 X19: 0000000000000000 X18: 0000000000000000
X17: 0000000000000000 X16: 0000000000000000 X15: 5400160b13131717
X14: ff00000000000000 X13: 0000000000000000 X12: 0000000000000020
X11: 0101010101010101 X10: 7f7f7f7f7f7f7f7f X9: ffff0000099150a0
X8: ffff80002fdabd40 X7: ffff80002fdabd40 X6: ffff80001f51f6de
X5: ffff80002887c800 X4: ffff80002fdabd40 X3: 0000000000000075
X2: 000000000000000a X1: ffff80001f51f6d4 X0: 0000000000000000
#10 [ffff80001f51f670] create_oops at ffff000009910018 [oops]
#11 [ffff80001f51f6a0] _MODULE_INIT_START_oops at ffff00000991509c [oops]
#12 [ffff80001f51f720] do_one_initcall at ffff000010087f34
#13 [ffff80001f51f970] do_init_module at ffff0000103715b0
#14 [ffff80001f51f9d0] load_module at ffff0000103725a4
#15 [ffff80001f51fba0] __se_sys_finit_module at ffff000010372bd8
#16 [ffff80001f51fc80] __arm64_sys_finit_module at ffff000010372a88
#17 [ffff80001f51fca0] __invoke_syscall at ffff0000100c14e4
#18 [ffff80001f51fcc0] invoke_syscall at ffff0000100c1590
#19 [ffff80001f51fd30] el0_svc_common at ffff0000100c16c4
#20 [ffff80001f51fdd0] el0_svc_handler at ffff0000100c1bc0
#21 [ffff80001f51fff0] el0_svc at ffff000010086784
最前面一行信息提示造成内核崩溃的进程,此处为insmod 加载内核模块
造成内核崩溃的指令 PC指针 PC: ffff00000991001c [create_oops+28]
已知寄存器现场
SP寄存器;X29寄存器,FP寄存器
X0 参数传递1的寄存器
X1 参数传递2的寄存器
#10行后是函数调用栈
在使用bt打印系统crash时从哪里进行分析?
PC指针,也就是内核崩溃发生的地方
PC: ffff00000991001c [create_oops+28]
反汇编pc指针的地址
crash> dis ffff00000991001c
0xffff00000991001c <create_oops+28>: ldr x0, [x0,#80]
#0x80 表示是基于x0寄存器的偏移量
x0是参数,参数是vm_area_struct,所以要查看其结构体的偏移
crash> struct -o vm_area_struct
struct vm_area_struct
[0] unsigned long vm_start;
[8] unsigned long vm_end;
...
[80] unsigned long vm_flags;
struct
struct rb_node rb;
unsigned long rb_subtree_last;
[88] shared;
表示访问vm->vm_flags,把值存放到x0寄存器中。查看x0寄存器中的值
crash> struct vm_area_struct 0x0
struct: invalid kernel virtual address: 0x0
无效的值为0x0
恢复函数调用栈
1、求解函数栈空间的FP
create_oops函数栈空间的FP为 ffff80001f51f670
使用rd + addr 可以得到上一级函数的FP
crash> rd ffff80001f51f670
ffff80001f51f670: ffff80001f51f6a0 ..Q.....
2、找出每个函数的名称
发生崩溃时,create_oops函数栈空间的FP放在 ffff80001f51f670 处,那么LR在其高8字节的地址上,因此LR放在ffff80001f51f678处
PC_f = *LR_c - 4 = *(FP_c + 8) - 4
PC_f 指的是父函数调用子函数时的PC值
LR_c 表示子函数栈空间的LR,也称为P_LR
FP_c 值的是子函数栈空间的FP
由于LR存放了子函数返回的下一条指令,再减去4字节,就是其父函数调用该函数的PC值
crash> rd ffff80001f51f678
ffff80001f51f678: ffff0000099150a0 .P......
crash> dis ffff00000991509c
0xffff00000991509c <_MODULE_INIT_START_oops+156>: bl 0xffff000009910000 <create_oops>
找到create_oops的父函数 _MODULE_INIT_START_oops
求解_MODULE_INIT_START_oops的父函数
根据bt的提示#11行
#11 [ffff80001f51f6a0] _MODULE_INIT_START_oops at ffff00000991509c [oops]
其栈空间的FP存储在 ffff80001f51f6a0 处,因此LR存放在 ffff80001f51f6a8处
PC值 = *(ffff80001f51f6a8)-4
计算其父函数调用该函数的PC值
crash> rd ffff80001f51f6a8
ffff80001f51f6a8: ffff000010087f38 8.......
crash> dis ffff000010087f34
0xffff000010087f34 <do_one_initcall+1168>: blr x0
整个函数的栈手动可以恢复。
推导参数值
查找进程pid
crash> ps | grep insmod
> 709 602 1 ffff80001f531c00 RU 0.1 5408 2440 insmod
函数调用关系
do_one_initcall->_MODULE_INIT_START_oops->create_oops
函数的SP指针(前面的地址)与PC指针(后面的地址)
#10 [ffff80001f51f670] create_oops at ffff000009910018 [oops]
#11 [ffff80001f51f6a0] _MODULE_INIT_START_oops at ffff00000991509c [oops]
#12 [ffff80001f51f720] do_one_initcall at ffff000010087f34
x0 寄存器存放参数1,x1寄存器存放参数2, x3寄存器存放参数3
反汇编creat_oops
crash> dis -f create_oops
0xffff000009910000 <create_oops>: mov x9, x30
0xffff000009910004 <create_oops+4>: nop
0xffff000009910008 <create_oops+8>: stp x29, x30, [sp,#-48]!
0xffff00000991000c <create_oops+12>: mov x29, sp
0xffff000009910010 <create_oops+16>: str x0, [sp,#24]
0xffff000009910014 <create_oops+20>: str x1, [sp,#16]
0xffff000009910018 <create_oops+24>: ldr x0, [sp,#24]
0xffff00000991001c <create_oops+28>: ldr x0, [x0,#80]
0xffff000009910020 <create_oops+32>: str x0, [sp,#40]
0xffff000009910024 <create_oops+36>: ldr x0, [sp,#16]
0xffff000009910028 <create_oops+40>: mov x2, x0
0xffff00000991002c <create_oops+44>: ldr x1, [sp,#40]
0xffff000009910030 <create_oops+48>: adrp x0, 0xffff000009911000
0xffff000009910034 <create_oops+52>: add x0, x0, #0x48
0xffff000009910038 <create_oops+56>: bl 0xffff000010298638 <printk>
0xffff00000991003c <create_oops+60>: mov w0, #0x0 // #0
0xffff000009910040 <create_oops+64>: ldp x29, x30, [sp],#48
0xffff000009910044 <create_oops+68>: ret
0xffff000009910048 <my_oops_exit>: stp x29, x30, [sp,#-16]!
0xffff00000991004c <cleanup_module+4>: mov x29, sp
0xffff000009910050 <cleanup_module+8>: adrp x0, 0xffff000009911000
0xffff000009910054 <cleanup_module+12>: add x0, x0, #0x70
0xffff000009910058 <cleanup_module+16>: bl 0xffff000010298638 <printk>
0xffff00000991005c <cleanup_module+20>: nop
0xffff000009910060 <cleanup_module+24>: ldp x29, x30, [sp],#16
0xffff000009910064 <cleanup_module+28>: ret
0xffff000009910068 <cleanup_module+32>: .inst 0x00000000 ; undefined
0xffff00000991006c <cleanup_module+36>: .inst 0x00000000 ; undefined
0xffff000009910070 <cleanup_module+40>: .inst 0x00000000 ; undefined
crash> rd ffff80001f51f680
ffff80001f51f680: ffff80001f51f6d4 ..Q.....
crash> struct mydev_priv ffff80001f51f6d4
struct: invalid kernel virtual address: ffff80001f51f6d4
加载内核符号表
crash> mod -s oops /home/rlk/rlk/runninglinuxkernel_5.0/kmodules/rlk_lab/rlk_senior/Volume_2/chapter_7/lab01/oops.ko
MODULE NAME SIZE OBJECT FILE
ffff000009912040 oops 16384 /mnt/rlk_lab/rlk_senior/Volume_2/chapter_8/lab03/oops.ko
crash> rd ffff80001f51f680
ffff80001f51f680: ffff80001f51f6d4 ..Q.....
crash> struct mydev_priv ffff80001f51f6d4
struct mydev_priv
name = "benshushu\\000\\377\\377X\\365Q\\037\\000\\200\\377\\377\\200<\\200*\\000\\200\\377\\377\\300-\\000\\000~\\377\\377\\000tK\\037\\000\\200\\377\\377\\000\\000\\000\\000\\000\\000\\000\\000Пk\\020\\000\\000\\377\\377\\000tK\\037",
i = 10,
mm = 0x1f51f970e8b36c88,
sem = 0x103715b4ffff8000
第二个参数的值name ,都正确
第一个参数值
crash> rd ffff80001f51f688
ffff80001f51f688: 0000000000000000 ........
crash> struct vm_area_struct 0000000000000000
struct: invalid kernel virtual address: 0000000000000000
第一个参数值无效
以上是关于如何使用crash工具分析Linux内核崩溃转储文件的主要内容,如果未能解决你的问题,请参考以下文章