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://kernel.linux.club.tw/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz

這是指向最新版本的符號連結。

最新的 kexec-tools Git 倉庫位於

此外,還可以透過 GitWeb 介面訪問:https://kernel.linux.club.tw/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