如何使用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工具调试内核

目录

原理

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内核崩溃转储文件的主要内容,如果未能解决你的问题,请参考以下文章

linux 使用kdump和crash工具调试内核

linux 使用kdump和crash工具调试内核

Linux内核调试:kdumpvmcorecrashkernel-debuginfo

Linux下常用调试工具总结

使用Crash工具分析 Linux dump文件

ARMv8 Linux内核错误处理过程分析怎么解决