Kdump 文档 - 基于 kexec 的崩溃转储解决方案

本文档包括概述、设置、安装和分析信息。

概述

Kdump 使用 kexec 快速引导到转储捕获内核,以便在需要获取系统内核内存转储时(例如,当系统发生恐慌时)进行转储。系统内核的内存映像在重启后会保留下来,并且可以供转储捕获内核访问。

您可以使用通用命令,例如 cp、scp 或 makedumpfile 将内存映像复制到本地磁盘上的转储文件,或者通过网络复制到远程系统。

Kdump 和 kexec 当前支持 x86、x86_64、ppc64、s390x、arm 和 arm64 架构。

当系统内核启动时,它会为转储捕获内核保留一小部分内存。这确保了系统内核正在进行的直接内存访问(DMA)不会损坏转储捕获内核。kexec -p 命令将转储捕获内核加载到这部分保留内存中。

在 x86 机器上,无论内核加载到何处,引导都需要前 640 KB 的物理内存。为了简化处理,整个低 1M 内存被保留,以避免后续内核或设备驱动程序向该区域写入数据。这样,低 1M 内存可以被 kdump 内核作为系统 RAM 重用,无需额外处理。

在 PPC64 机器上,无论内核加载到何处,引导都需要前 32KB 的物理内存;为了支持 64KB 页面大小,kexec 会备份前 64KB 内存。

对于 s390x,当 Kdump 触发时,crashkernel 区域会与 [0, crashkernel region size] 区域进行交换,然后 Kdump 内核在 [0, crashkernel region size] 中运行。因此,s390x 不需要可重定位内核。

关于系统内核核心映像的所有必要信息都以 ELF 格式编码,并在崩溃前存储在内存的保留区域中。ELF 头部的起始物理地址通过 elfcorehdr= 引导参数传递给转储捕获内核。在使用 elfcorehdr=[size[KMG]@]offset[KMG] 语法时,也可以选择性地传递 ELF 头部的大小。

通过转储捕获内核,您可以通过 /proc/vmcore 访问内存映像。这会将转储作为 ELF 格式文件导出,您可以使用 cp 或 scp 等文件复制命令将其写出。您还可以使用 makedumpfile 工具分析并带选项写出过滤后的内容,例如使用 '-d 31' 它将只写出内核数据。此外,您可以使用 GNU 调试器 (GDB) 和 Crash 工具等分析工具来调试转储文件。此方法可确保转储页面正确排序。

设置与安装

安装 kexec-tools

  1. 以 root 用户身份登录。

  2. 从以下 URL 下载 kexec-tools 用户空间软件包

https://linuxkernel.org.cn/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz

这是指向最新版本的符号链接。

最新的 kexec-tools Git 仓库位于

此外,还可以通过 GitWeb 界面访问:https://linuxkernel.org.cn/git/?p=utils/kernel/kexec/kexec-tools.git

有关 kexec-tools 的更多信息可在以下网址找到:http://horms.net/projects/kexec/

  1. 使用 tar 命令解压 tarball,如下所示

    tar xvpzf kexec-tools.tar.gz
    
  2. 切换到 kexec-tools 目录,如下所示

    cd kexec-tools-VERSION
    
  3. 配置软件包,如下所示

    ./configure
    
  4. 编译软件包,如下所示

    make
    
  5. 安装软件包,如下所示

    make install
    

构建系统内核和转储捕获内核

使用 Kdump 有两种可能的方法。

  1. 构建一个单独的自定义转储捕获内核,用于捕获内核核心转储。

  2. 或者将系统内核二进制文件本身用作转储捕获内核,无需构建单独的转储捕获内核。这仅适用于支持可重定位内核的架构。截至目前,i386、x86_64、ppc64、arm 和 arm64 架构支持可重定位内核。

构建可重定位内核的优势在于,无需构建第二个内核来捕获转储。但与此同时,人们可能希望构建一个适合其需求的自定义转储捕获内核。

以下是启用 kdump 支持所需的系统内核和转储捕获内核的配置设置。

系统内核配置选项

  1. 在“处理器类型和特性”中启用“kexec 系统调用”或“基于 kexec 文件的系统调用”。

    CONFIG_KEXEC=y or CONFIG_KEXEC_FILE=y
    

    并且它们都将选择 KEXEC_CORE

    CONFIG_KEXEC_CORE=y
    
  2. 在“文件系统”->“伪文件系统”中启用“sysfs 文件系统支持”。此选项通常默认启用。

    CONFIG_SYSFS=y
    

    请注意,如果在“General Setup”(通用设置)中未启用“Configure standard kernel features (expert users)”(配置标准内核特性(专家用户)),则“sysfs 文件系统支持”可能不会出现在“Pseudo filesystems”(伪文件系统)菜单中。在这种情况下,请检查 .config 文件本身,确保 sysfs 已启用,如下所示

    grep 'CONFIG_SYSFS' .config
    
  3. 在“内核 Hacking”中启用“Compile the kernel with debug info”(编译带调试信息的内核)。

    CONFIG_DEBUG_INFO=Y
    

    这会导致内核使用调试符号构建。转储分析工具需要带调试符号的 vmlinux 才能读取和分析转储文件。

转储捕获内核配置选项(架构无关)

  1. 在“处理器类型和特性”下启用“内核崩溃转储”支持

    CONFIG_CRASH_DUMP=y
    
    这将选择 VMCORE_INFO 和 CRASH_RESERVE:

    CONFIG_VMCORE_INFO=y CONFIG_CRASH_RESERVE=y

  2. 在“文件系统”->“伪文件系统”下启用“/proc/vmcore 支持”

    CONFIG_PROC_VMCORE=y
    

    (当选择 CONFIG_CRASH_DUMP 时,CONFIG_PROC_VMCORE 默认设置为 y。)

转储捕获内核配置选项(架构相关,i386 和 x86_64)

  1. 在 i386 上,在“处理器类型和特性”下启用高内存支持

    CONFIG_HIGHMEM4G
    
  2. 当 CONFIG_SMP=y 时,通常在加载转储捕获内核时需要在内核命令行上指定 nr_cpus=1,因为对于大多数系统而言,一个 CPU 足以让 kdump 内核转储 vmcore。

    但是,您也可以指定 nr_cpus=X 以在 kdump 内核中启用多个处理器。

    当 CONFIG_SMP=n 时,上述内容无关。

  3. 建议默认构建可重定位内核。如果尚未构建,请在“处理器类型和特性”下启用“构建可重定位内核”支持。

    CONFIG_RELOCATABLE=y
    
  4. 为“内核加载的物理地址”(在“处理器类型和特性”下)使用合适的值。此选项仅在启用“内核崩溃转储”时出现。合适的值取决于内核是否可重定位。

    如果您正在使用可重定位内核,请使用 CONFIG_PHYSICAL_START=0x100000。这将为物理地址 1MB 编译内核,但鉴于内核是可重定位的,它可以在任何物理地址运行,因此 kexec 引导加载程序会将其加载到为转储捕获内核保留的内存区域中。

    否则,它应该是使用引导参数“crashkernel=Y@X”为第二个内核保留的内存区域的起始地址。其中 X 是为转储捕获内核保留的内存区域的起始地址。通常 X 是 16MB (0x1000000)。因此您可以设置 CONFIG_PHYSICAL_START=0x1000000

  5. 编译并安装内核及其模块。不要将此内核添加到引导加载程序配置文件中。

转储捕获内核配置选项(架构相关,ppc64)

  1. 在“内核”选项下启用“构建 kdump 崩溃内核”支持

    CONFIG_CRASH_DUMP=y
    
  2. 启用“构建可重定位内核”支持

    CONFIG_RELOCATABLE=y
    

编译并安装内核及其模块。

转储捕获内核配置选项(架构相关,arm)

  • 要使用可重定位内核,请在“引导”选项下启用“AUTO_ZRELADDR”支持

    AUTO_ZRELADDR=y
    

转储捕获内核配置选项(架构相关,arm64)

  • 请注意,即使配置了转储捕获内核的 kvm,在非 VHE 系统上也不会启用它。这是因为在 panic 时 CPU 不会重置为 EL2。

crashkernel 语法

  1. crashkernel=size@offset

    此处“size”指定为转储捕获内核保留多少内存,“offset”指定该保留内存的起始地址。例如,“crashkernel=64M@16M”告诉系统内核为转储捕获内核保留从物理地址 0x01000000 (16MB) 开始的 64 MB 内存。

    crashkernel 区域可以在运行时由系统内核自动放置。这可以通过将基地址指定为 0,或完全省略它来实现

    crashkernel=256M@0
    

    crashkernel=256M
    

    如果指定了起始地址,请注意内核的起始地址将对齐到某个值(取决于架构),因此如果未对齐,则对齐点以下的任何空间都将被浪费。

  2. range1:size1[,range2:size2,...][@offset]

    虽然“crashkernel=size[@offset]”语法对于大多数配置来说已经足够,但有时让保留内存取决于系统 RAM 的值会很方便——这主要适用于预先设置内核命令行以避免在机器移除部分内存后系统无法启动的发行商。

    语法如下

    crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset]
    range=start-[end]
    

    例如

    crashkernel=512M-2G:64M,2G-:128M
    

    这将意味着

    1. 如果 RAM 小于 512M,则不保留任何内存(这是“救援”情况)

    2. 如果 RAM 大小在 512M 和 2G 之间(不包括 2G),则保留 64M

    3. 如果 RAM 大小大于 2G,则保留 128M

  3. crashkernel=size,high and crashkernel=size,low

    如果优先使用 4G 以上的内存,可以使用 crashkernel=size,high 来满足需求。这样,物理内存可以从顶部开始分配,因此如果系统安装了超过 4G 的 RAM,则可以分配到 4G 以上。否则,如果可用,内存区域将在 4G 以下分配。

    当传递 crashkernel=X,high 时,内核可能会在 4G 以上分配物理内存区域,此时需要 4G 以下的低内存。获取低内存有三种方式

    1. 如果未指定 crashkernel=Y,low,内核将自动分配至少 256M 位于 4G 以下的内存。

    2. 让用户指定低内存大小。

    3. 指定值为 0 将禁用低内存分配

      crashkernel=0,low
      

引导进入系统内核

  1. 根据需要更新引导加载程序(如 grub、yaboot 或 lilo)配置文件。

  2. 使用引导参数“crashkernel=Y@X”引导系统内核。

    在 x86 和 x86_64 上,使用“crashkernel=Y[@X]”。大多数情况下,起始地址“X”不是必需的,内核会搜索一个合适的区域。除非需要明确的起始地址。

    在 ppc64 上,使用“crashkernel=128M@32M”。

    在 s390x 上,通常使用“crashkernel=xxM”。xx 的值取决于 kdump 系统的内存消耗。一般来说,这不取决于生产系统的内存大小。

    在 arm 上,不再需要使用“crashkernel=Y@X”;如果未给定 X,内核将自动在 RAM 的前 512MB 内定位崩溃内核映像。

    在 arm64 上,使用“crashkernel=Y[@X]”。请注意,内核的起始地址(如果明确指定为 X)必须对齐到 2MiB (0x200000)。

加载转储捕获内核

引导到系统内核后,需要加载转储捕获内核。

根据架构和映像类型(是否可重定位),可以选择加载转储捕获内核的未压缩 vmlinux 或压缩 bzImage/vmlinuz。以下是摘要。

对于 i386 和 x86_64

  • 如果内核是可重定位的,请使用 bzImage/vmlinuz。

  • 如果内核不可重定位,请使用 vmlinux。

对于 ppc64

  • 使用 vmlinux

对于 s390x

  • 使用 image 或 bzImage

对于 arm

  • 使用 zImage

对于 arm64

  • 使用 vmlinux 或 Image

如果您正在使用未压缩的 vmlinux 映像,请使用以下命令加载转储捕获内核

kexec -p <dump-capture-kernel-vmlinux-image> \
--initrd=<initrd-for-dump-capture-kernel> --args-linux \
--append="root=<root-dev> <arch-specific-options>"

如果您正在使用压缩的 bzImage/vmlinuz,请使用以下命令加载转储捕获内核

kexec -p <dump-capture-kernel-bzImage> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"

如果您正在使用压缩的 zImage,请使用以下命令加载转储捕获内核

kexec --type zImage -p <dump-capture-kernel-bzImage> \
--initrd=<initrd-for-dump-capture-kernel> \
--dtb=<dtb-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"

如果您正在使用未压缩的 Image,请使用以下命令加载转储捕获内核

kexec -p <dump-capture-kernel-Image> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"

以下是加载转储捕获内核时要使用的架构特定命令行选项。

对于 i386 和 x86_64

“1 irqpoll nr_cpus=1 reset_devices”

对于 ppc64

“1 maxcpus=1 noirqdistrib reset_devices”

对于 s390x

“1 nr_cpus=1 cgroup_disable=memory”

对于 arm

“1 maxcpus=1 reset_devices”

对于 arm64

“1 nr_cpus=1 reset_devices”

加载转储捕获内核的注意事项

  • 默认情况下,ELF 头部以 ELF64 格式存储,以支持内存超过 4GB 的系统。在 i386 上,kexec 会自动检查物理 RAM 大小是否超过 4 GB 限制,如果没有,则使用 ELF32。因此,在非 PAE 系统上,始终使用 ELF32。

    --elf32-core-headers 选项可用于强制生成 ELF32 头部。这是必需的,因为 GDB 目前无法在 32 位系统上打开带有 ELF64 头部格式的 vmcore 文件。

  • “irqpoll”引导参数可减少因转储捕获内核中共享中断而导致的驱动程序初始化失败。

  • 您必须以与 mount 命令输出中的根设备名称相对应的格式指定 <root-dev>。

  • 引导参数“1”将转储捕获内核引导到单用户模式,不带网络。如果您需要网络,请使用“3”。

  • 我们通常不需要启动 SMP 内核只是为了捕获转储。因此,通常构建一个 UP 转储捕获内核或在加载转储捕获内核时指定 maxcpus=1 选项会很有用。请注意,虽然 maxcpus 总是有效,但如果当前 ARCH 支持(例如 x86),您最好将其替换为 nr_cpus 以节省内存。

  • 如果您打算使用多线程程序,例如 makedumpfile 的并行转储功能,则应在转储捕获内核中启用多 CPU 支持。否则,多线程程序可能会出现严重的性能下降。要启用多 CPU 支持,您应该启动一个 SMP 转储捕获内核,并在加载它时指定 maxcpus/nr_cpus 选项。

  • 对于 s390x,有两种 kdump 模式:如果使用 elfcorehdr= 内核参数指定了 ELF 头部,kdump 内核会像在所有其他架构上一样使用它。如果未指定 elfcorehdr= 内核参数,s390x kdump 内核会动态创建头部。第二种模式的优点是,对于 CPU 和内存热插拔,kdump 无需使用 kexec_load() 重新加载。

  • 对于连接了许多设备的 s390x 系统,应为 kdump 内核使用“cio_ignore”内核参数,以防止为与 kdump 无关的设备分配内核内存。这同样适用于使用 SCSI/FCP 设备的系统。在这种情况下,在将 FCP 设备联机之前,应将“allow_lun_scan”zfcp 模块参数设置为零。

内核恐慌

如前所述成功加载转储捕获内核后,如果触发系统崩溃,系统将重新引导到转储捕获内核。触发点位于 panic()、die()、die_nmi() 和 sysrq 处理程序 (ALT-SysRq-c) 中。

以下条件将执行崩溃触发点

如果检测到硬锁死并配置了“NMI watchdog”,系统将引导到转储捕获内核(die_nmi())。

如果调用 die(),并且它是 pid 为 0 或 1 的线程,或者 die() 在中断上下文中被调用,或者 die() 被调用且 panic_on_oops 已设置,系统将引导到转储捕获内核。

在 powerpc 系统上,当生成软重置时,所有 CPU 都会调用 die(),系统将引导到转储捕获内核。

出于测试目的,您可以使用“ALT-SysRq-c”、“echo c > /proc/sysrq-trigger”或编写模块来强制触发恐慌。

写出转储文件

转储捕获内核引导后,使用以下命令写出转储文件

cp /proc/vmcore <dump-file>

或者使用 scp 在网络上的主机之间写出转储文件,例如

scp /proc/vmcore remote_username@remote_ip:<dump-file>

您还可以使用 makedumpfile 工具,通过指定选项写出转储文件,以过滤掉不需要的内容,例如

makedumpfile -l --message-level 1 -d 31 /proc/vmcore <dump-file>

分析

在分析转储映像之前,您应该重新引导到一个稳定的内核。

您可以使用 GDB 对从 /proc/vmcore 复制出来的转储文件进行有限的分析。使用用 -g 编译的调试 vmlinux 并运行以下命令

gdb vmlinux <dump-file>

处理器 0 上的任务堆栈跟踪、寄存器显示和内存显示都能正常工作。

注意:GDB 无法分析为 x86 生成的 ELF64 格式核心文件。在最大内存为 4GB 的系统上,您可以使用转储内核上的 --elf32-core-headers 内核选项生成 ELF32 格式的头部。

您还可以使用 Crash 工具分析 Kdump 格式的转储文件。Crash 可在以下 URL 获取

Crash 文档可在以下网址找到

https://crash-utility.github.io/

在 WARN() 上触发 Kdump

内核参数 panic_on_warn 在所有 WARN() 路径中调用 panic()。这将在 panic() 调用时触发 kdump。如果用户想在运行时指定此行为,可以将 /proc/sys/kernel/panic_on_warn 设置为 1 以达到相同的效果。

在 add_taint() 上触发 Kdump

内核参数 panic_on_taint 允许在 add_taint() 内部根据条件调用 panic(),只要此位掩码中设置的值与 add_taint() 设置的位标志匹配。这将在 add_taint()->panic() 调用时触发 kdump。

将转储文件写入加密磁盘卷

可以启用 CONFIG_CRASH_DM_CRYPT 以支持将转储文件保存到加密磁盘卷(目前仅支持 x86_64)。用户空间可以通过 /sys/kernel/config/crash_dm_crypt_keys 进行设置,

  1. 告诉第一个内核解锁磁盘卷所需的登录密钥,

    # 添加密钥 #1 mkdir /sys/kernel/config/crash_dm_crypt_keys/7d26b7b4-e342-4d2d-b660-7426b0996720 # 添加密钥 #1 的描述 echo cryptsetup:7d26b7b4-e342-4d2d-b660-7426b0996720 > /sys/kernel/config/crash_dm_crypt_keys/description

    # 我们现在有多少密钥?cat /sys/kernel/config/crash_dm_crypt_keys/count 1

    # 以同样的方式添加密钥 #2

    # 我们现在有多少密钥?cat /sys/kernel/config/crash_dm_crypt_keys/count 2

    # 为了支持 CPU/内存热插拔,重用已保存到保留内存中的密钥 echo true > /sys/kernel/config/crash_dm_crypt_key/reuse

  2. 加载转储捕获内核

  3. 转储捕获内核启动后,将密钥恢复到用户密钥环:echo yes > /sys/kernel/crash_dm_crypt_keys/restore

联系方式

GDB 宏

#
# This file contains a few gdb macros (user defined commands) to extract
# useful information from kernel crashdump (kdump) like stack traces of
# all the processes or a particular process and trapinfo.
#
# These macros can be used by copying this file in .gdbinit (put in home
# directory or current directory) or by invoking gdb command with
# --command=<command-file-name> option
#
# Credits:
# Alexander Nyberg <alexn@telia.com>
# V Srivatsa <vatsa@in.ibm.com>
# Maneesh Soni <maneesh@in.ibm.com>
#

define bttnobp
        set $tasks_off=((size_t)&((struct task_struct *)0)->tasks)
        set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next)
        set $init_t=&init_task
        set $next_t=(((char *)($init_t->tasks).next) - $tasks_off)
        set var $stacksize = sizeof(union thread_union)
        while ($next_t != $init_t)
                set $next_t=(struct task_struct *)$next_t
                printf "\npid %d; comm %s:\n", $next_t.pid, $next_t.comm
                printf "===================\n"
                set var $stackp = $next_t.thread.sp
                set var $stack_top = ($stackp & ~($stacksize - 1)) + $stacksize

                while ($stackp < $stack_top)
                        if (*($stackp) > _stext && *($stackp) < _sinittext)
                                info symbol *($stackp)
                        end
                        set $stackp += 4
                end
                set $next_th=(((char *)$next_t->thread_group.next) - $pid_off)
                while ($next_th != $next_t)
                        set $next_th=(struct task_struct *)$next_th
                        printf "\npid %d; comm %s:\n", $next_t.pid, $next_t.comm
                        printf "===================\n"
                        set var $stackp = $next_t.thread.sp
                        set var $stack_top = ($stackp & ~($stacksize - 1)) + stacksize

                        while ($stackp < $stack_top)
                                if (*($stackp) > _stext && *($stackp) < _sinittext)
                                        info symbol *($stackp)
                                end
                                set $stackp += 4
                        end
                        set $next_th=(((char *)$next_th->thread_group.next) - $pid_off)
                end
                set $next_t=(char *)($next_t->tasks.next) - $tasks_off
        end
end
document bttnobp
        dump all thread stack traces on a kernel compiled with !CONFIG_FRAME_POINTER
end

define btthreadstack
        set var $pid_task = $arg0

        printf "\npid %d; comm %s:\n", $pid_task.pid, $pid_task.comm
        printf "task struct: "
        print $pid_task
        printf "===================\n"
        set var $stackp = $pid_task.thread.sp
        set var $stacksize = sizeof(union thread_union)
        set var $stack_top = ($stackp & ~($stacksize - 1)) + $stacksize
        set var $stack_bot = ($stackp & ~($stacksize - 1))

        set $stackp = *((unsigned long *) $stackp)
        while (($stackp < $stack_top) && ($stackp > $stack_bot))
                set var $addr = *(((unsigned long *) $stackp) + 1)
                info symbol $addr
                set $stackp = *((unsigned long *) $stackp)
        end
end
document btthreadstack
         dump a thread stack using the given task structure pointer
end


define btt
        set $tasks_off=((size_t)&((struct task_struct *)0)->tasks)
        set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next)
        set $init_t=&init_task
        set $next_t=(((char *)($init_t->tasks).next) - $tasks_off)
        while ($next_t != $init_t)
                set $next_t=(struct task_struct *)$next_t
                btthreadstack $next_t

                set $next_th=(((char *)$next_t->thread_group.next) - $pid_off)
                while ($next_th != $next_t)
                        set $next_th=(struct task_struct *)$next_th
                        btthreadstack $next_th
                        set $next_th=(((char *)$next_th->thread_group.next) - $pid_off)
                end
                set $next_t=(char *)($next_t->tasks.next) - $tasks_off
        end
end
document btt
        dump all thread stack traces on a kernel compiled with CONFIG_FRAME_POINTER
end

define btpid
        set var $pid = $arg0
        set $tasks_off=((size_t)&((struct task_struct *)0)->tasks)
        set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next)
        set $init_t=&init_task
        set $next_t=(((char *)($init_t->tasks).next) - $tasks_off)
        set var $pid_task = 0

        while ($next_t != $init_t)
                set $next_t=(struct task_struct *)$next_t

                if ($next_t.pid == $pid)
                        set $pid_task = $next_t
                end

                set $next_th=(((char *)$next_t->thread_group.next) - $pid_off)
                while ($next_th != $next_t)
                        set $next_th=(struct task_struct *)$next_th
                        if ($next_th.pid == $pid)
                                set $pid_task = $next_th
                        end
                        set $next_th=(((char *)$next_th->thread_group.next) - $pid_off)
                end
                set $next_t=(char *)($next_t->tasks.next) - $tasks_off
        end

        btthreadstack $pid_task
end
document btpid
        backtrace of pid
end


define trapinfo
        set var $pid = $arg0
        set $tasks_off=((size_t)&((struct task_struct *)0)->tasks)
        set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next)
        set $init_t=&init_task
        set $next_t=(((char *)($init_t->tasks).next) - $tasks_off)
        set var $pid_task = 0

        while ($next_t != $init_t)
                set $next_t=(struct task_struct *)$next_t

                if ($next_t.pid == $pid)
                        set $pid_task = $next_t
                end

                set $next_th=(((char *)$next_t->thread_group.next) - $pid_off)
                while ($next_th != $next_t)
                        set $next_th=(struct task_struct *)$next_th
                        if ($next_th.pid == $pid)
                                set $pid_task = $next_th
                        end
                        set $next_th=(((char *)$next_th->thread_group.next) - $pid_off)
                end
                set $next_t=(char *)($next_t->tasks.next) - $tasks_off
        end

        printf "Trapno %ld, cr2 0x%lx, error_code %ld\n", $pid_task.thread.trap_no, \
                                $pid_task.thread.cr2, $pid_task.thread.error_code

end
document trapinfo
        Run info threads and lookup pid of thread #1
        'trapinfo <pid>' will tell you by which trap & possibly
        address the kernel panicked.
end

define dump_record
        set var $desc = $arg0
        set var $info = $arg1
        if ($argc > 2)
                set var $prev_flags = $arg2
        else
                set var $prev_flags = 0
        end

        set var $prefix = 1
        set var $newline = 1

        set var $begin = $desc->text_blk_lpos.begin % (1U << prb->text_data_ring.size_bits)
        set var $next = $desc->text_blk_lpos.next % (1U << prb->text_data_ring.size_bits)

        # handle data-less record
        if ($begin & 1)
                set var $text_len = 0
                set var $log = ""
        else
                # handle wrapping data block
                if ($begin > $next)
                        set var $begin = 0
                end

                # skip over descriptor id
                set var $begin = $begin + sizeof(long)

                # handle truncated message
                if ($next - $begin < $info->text_len)
                        set var $text_len = $next - $begin
                else
                        set var $text_len = $info->text_len
                end

                set var $log = &prb->text_data_ring.data[$begin]
        end

        # prev & LOG_CONT && !(info->flags & LOG_PREIX)
        if (($prev_flags & 8) && !($info->flags & 4))
                set var $prefix = 0
        end

        # info->flags & LOG_CONT
        if ($info->flags & 8)
                # (prev & LOG_CONT && !(prev & LOG_NEWLINE))
                if (($prev_flags & 8) && !($prev_flags & 2))
                        set var $prefix = 0
                end
                # (!(info->flags & LOG_NEWLINE))
                if (!($info->flags & 2))
                        set var $newline = 0
                end
        end

        if ($prefix)
                printf "[%5lu.%06lu] ", $info->ts_nsec / 1000000000, $info->ts_nsec % 1000000000
        end
        if ($text_len)
                eval "printf \"%%%d.%ds\", $log", $text_len, $text_len
        end
        if ($newline)
                printf "\n"
        end

        # handle dictionary data

        set var $dict = &$info->dev_info.subsystem[0]
        set var $dict_len = sizeof($info->dev_info.subsystem)
        if ($dict[0] != '\0')
                printf " SUBSYSTEM="
                set var $idx = 0
                while ($idx < $dict_len)
                        set var $c = $dict[$idx]
                        if ($c == '\0')
                                loop_break
                        else
                                if ($c < ' ' || $c >= 127 || $c == '\\')
                                        printf "\\x%02x", $c
                                else
                                        printf "%c", $c
                                end
                        end
                        set var $idx = $idx + 1
                end
                printf "\n"
        end

        set var $dict = &$info->dev_info.device[0]
        set var $dict_len = sizeof($info->dev_info.device)
        if ($dict[0] != '\0')
                printf " DEVICE="
                set var $idx = 0
                while ($idx < $dict_len)
                        set var $c = $dict[$idx]
                        if ($c == '\0')
                                loop_break
                        else
                                if ($c < ' ' || $c >= 127 || $c == '\\')
                                        printf "\\x%02x", $c
                                else
                                        printf "%c", $c
                                end
                        end
                        set var $idx = $idx + 1
                end
                printf "\n"
        end
end
document dump_record
        Dump a single record. The first parameter is the descriptor,
        the second parameter is the info, the third parameter is
        optional and specifies the previous record's flags, used for
        properly formatting continued lines.
end

define dmesg
        # definitions from kernel/printk/printk_ringbuffer.h
        set var $desc_committed = 1
        set var $desc_finalized = 2
        set var $desc_sv_bits = sizeof(long) * 8
        set var $desc_flags_shift = $desc_sv_bits - 2
        set var $desc_flags_mask = 3 << $desc_flags_shift
        set var $id_mask = ~$desc_flags_mask

        set var $desc_count = 1U << prb->desc_ring.count_bits
        set var $prev_flags = 0

        set var $id = prb->desc_ring.tail_id.counter
        set var $end_id = prb->desc_ring.head_id.counter

        while (1)
                set var $desc = &prb->desc_ring.descs[$id % $desc_count]
                set var $info = &prb->desc_ring.infos[$id % $desc_count]

                # skip non-committed record
                set var $state = 3 & ($desc->state_var.counter >> $desc_flags_shift)
                if ($state == $desc_committed || $state == $desc_finalized)
                        dump_record $desc $info $prev_flags
                        set var $prev_flags = $info->flags
                end

                if ($id == $end_id)
                        loop_break
                end
                set var $id = ($id + 1) & $id_mask
        end
end
document dmesg
        print the kernel ring buffer
end