kdump簡介
kdump是系統崩潰的時候,用來轉儲運行內存的一個工具。
系統一旦崩潰,內核就沒法正常工作了,這個時候將由kdump提供一個用於捕獲當前運行信息的內核,
該內核會將此時內存中的所有運行狀態和數據信息收集到一個dump core文件中以便之后分析崩潰原因。
一旦內存信息收集完成,可以讓系統將自動重啟。
kdump是RHEL5之后才支持的,2006被主線接收為內核的一部分。它的原理簡單來說是在內存中保留一塊
區域,這塊區域用來存放capture kernel,當production kernel發生crash的時候,通過kexec把保留區域的
capure kernel給運行起來,再由捕獲內核負責把產品內核的完整信息 - 包括CPU寄存器、堆棧數據等轉儲
到指定位置的文件中。
kdump原理
kexec是kdump機制的關鍵,包含兩部分:
內核空間的系統調用kexec_load。負責在生產內核啟動時將捕獲內核加載到指定地址。
用戶空間的工具kexec-tools。將捕獲內核的地址傳遞給生產內核,從而在系統崩潰的時候找到捕獲內核的地址並運行。
kdump是一種基於kexec的內核崩潰轉儲機制。當系統崩潰時,kdump使用kexec啟動到第二個內核。第二個內核通常
叫做捕獲內核,以很小內存啟動以捕獲轉儲鏡像。第一個內核保留了內存的一部分給第二個內核啟動使用。
由於kdump利用kexec啟動捕獲內核,繞過了BIOS,所以第一個內核的內存得以保留。這是內存崩潰轉儲的本質。
捕獲內核啟動后,會像一般內核一樣,去運行為它創建的ramdisk上的init程序。而各種轉儲機制都可以事先在init中實現。
為了在生產內核崩潰時能順利啟動捕獲內核,捕獲內核以及它的ramdisk是事先放到生產內核的內存中的。
生產內核的內存是通過/proc/vmcore這個文件交給捕獲內核的。為了生成它,用戶工具在生產內核中分析出內存的使用和
分布等情況,然后把這些信息綜合起來生成一個ELF頭文件保存起來。捕獲內核被引導時會被同時傳遞這個ELF文件頭的
地址,通過分析它,捕獲內核就可以生成出/proc/vmcore。有了/proc/vmcore這個文件,捕獲內核的ramdisk中的腳本就
可以通過通常的文件讀寫和網絡來實現各種策略了。
kdump配置
RHEL5開始,kexec-tools是默認安裝的。
如果需要調試kdump生成的vmcore文件,需要手動安裝kernel-debuginfo包。
(1) 預留內存
可以修改內核引導參數,為啟動捕獲內核預留指定內存。
在/etc/grub.conf
(一般為/boot/grub/grub.conf
的軟鏈接)中:
crashkernel=Y@X
,Y是為kdump捕獲內核保留的內存,X是保留部分內存的起始位置。
默認為crashkernel=auto
,可自行設定如crashkernel=256M
。
(2) 配置文件
配置文件為/etc/kdump.conf
,以下是幾個常用配置:
# path /var/crash
默認的vmcore存放目錄為/var/crash/%HOST-%DATE/
,包括兩個文件:vmcore和vmcore-dmesg.txt
(3) 啟動服務
# chkconfig kdump on // 開機啟動
# service kdump status // start、stop、restart等
(4) 功能驗證
Magic System request key is a magical key combo you can hit which the kernel will respond to regardless
of whatever else it is doing, unless it is completely locked up.
使用sysrq需要編譯選項CONFIG_MAGIC_SYSRQ的支持。詳細信息可看documentation/sysrq.txt。
故意讓系統崩潰,來測試kdump是否正常工作:
# echo c > /proc/sysrq-trigger
Will perform a system crash by a NULL pointer dereference.
A crash dump will be taken if configured.
Magic SysRq還有一些很有趣的值,有的具有很大的破環性,輸出在/var/log/messages:
f:call oom_kill to kill a memory hog process. 執行oom killer。
l:shows a stack backtrace for all active CPUs. 打印出所有CPU的stack backtrace。
m:dump current memory info. 打印出內存使用信息。
p:dump the current registers and flags. 打印出所在CPU的寄存器信息。
(5) 捕獲內核
捕獲內核是一個未壓縮的ELF映像文件,查看捕獲內核是否加載到內存中:
# cat /sys/kernel/kexec_crash_loaded
縮小捕獲內核占用的內存:
# echo N > /sys/kernel/kexec_crash_size
crash簡介
當系統崩潰時,通過kdump可以獲得當時的內存轉儲文件vmcore,但是該如何分析vmcore呢?
crash是一個用於分析內核轉儲文件的工具,一般和kdump搭配使用。
使用crash時,要求調試內核vmlinux在編譯時帶有-g選項,即帶有調試信息。
如果沒有指定vmcore,則默認使用實時系統的內存來分析。
值得一提的是,crash也可以用來分析實時的系統內存,是一個很強大的調試工具。
crash使用gdb作為內部引擎,語法類似於gdb,命令的使用說明可以用
使用crash需要安裝crash工具包和內核調試信息包:
crash
kernel-debuginfo-common
kernel-debuginfo
crash使用
Analyze Linux crash dump data or a live system.
crash [OPTION] NAMELIST MEMORY-IMAGE (dumpfile form)
crash [OPTION] [NAMELIST] (live system form)
使用crash來調試vmcore,至少需要兩個參數:
NAMELIST:未壓縮的內核映像文件vmlinux,默認位於/usr/lib/debug/lib/modules/$(uname -r)/vmlinux
,由
內核調試信息包提供。
MEMORY-IMAGE:內存轉儲文件vmcore,默認位於/var/crash/%HOST-%DATE/vmcore,由kdump生成。
例如:# crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/%HOST-%DATE/vmcore
(1) 錯誤類型
首先可以在vmcore-dmesg.txt中先查看錯誤類型,如:
-
divide error: 0000 [#1] SMP,除數為0造成內核崩潰,由1號CPU觸發。
-
BUG: unable to handle kernel NULL pointer dereference at 000000000000012c,引用空指針。
這樣一來就能知道引發內核崩潰的錯誤類型。
(2) 錯誤地點
RIP為造成內核崩潰的指令,Call Trace為函數調用棧,通過RIP和Call Trace可以確定函數的調用路徑,以及在
哪個函數中的哪條指令引發了錯誤。
例如RIP為:[<ffffffff812cdb54>] ? tcp_enter_loss+0x1d3/0x23b
[<ffffffff812cdb54>]
是指令在內存中的虛擬地址。
tcp_enter_loss
是函數名(symbol)。
0x1d3
是這條指令相對於tcp_enter_loss
入口的偏移,0x23b
是函數編譯成機器碼后的長度。
這樣一來就能確定在哪個函數中引發了錯誤,以及錯誤的大概位置。
Call Trace為函數的調用棧,是從下往上看的。可以用來分析函數的調用關系。
(3) crash基本輸出
# crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/%HOST-%DATE/vmcore
KERNEL: /usr/lib/debug/lib/modules/2.6.32-358.el6.x86_64/vmlinux
DUMPFILE: vmcore [PARTIAL DUMP]
CPUS: 12
DATE: Fri Sep 19 16:47:01 2014
UPTIME: 7 days, 06:37:46
LOAD AVERAGE: 0.19, 0.05, 0.01
TASKS: 282
NODENAME: localhost.localdomain
RELEASE: 2.6.32-358.el6.x86_64
VERSION: #1 SMP Tue Oct 29 10:18:21 CST 2013
MACHINE: x86_64 (1999 Mhz)
MEMORY: 48 GB
PANIC: "Oops: 0002 [#1] SMP " (check log for details)
PID: 0
COMMAND: "swapper"
TASK: ffffffff81a8d020 (1 of 12) [THREAD_INFO: ffffffff81a00000]
CPU: 0
STATE: TASK_RUNNING (PANIC)
這些基本輸出信息簡單明了,可由sys命令觸發。
(4) crash常用命令
bt:打印函數調用棧,displays a task's kernel-stack backtrace,可以指定進程號bt
log:打印系統消息緩沖區,displays the kernel log_buf contents,如log | tail -n 30。
ps:顯示進程的狀態,>表示活躍的進程,如ps | grep RU。
sys:顯示系統概況。
kmem -i:顯示內存使用信息。
dis
exception RIP即為造成錯誤的指令。
關於log命令:
內核首先把消息打印到內核態的ring buffer,用戶態的klogd負責讀取並轉發給syslogd,讓它記錄到磁盤。
在內核崩潰時,可能無法把消息記錄到磁盤,但是ring buffer中一般會有記錄。所以log命令有時候能查看
到系統日志中所缺失的信息。
(5) 結構體和變量
查看結構體中所有成員的值,例如:
# ps | grep RU
> 0 0 0 ffffffff81a8d020 RU 0.0 0 0 [swapper]
# struct task_struct ffffffff81a8d020
struct task_struct {
state = 0,
stack = 0xffffffff81a00000,
usage = {
counter = 2
},
flags = 2097408,
顯示整個結構體的定義:
# struct task_struct
struct task_struct {
volatile long int state;
void *stack;
atomic_t usage;
unsigned int flags;
顯示整個結構體的定義,以及每個成員的偏移:
# struct -o task_struct
struct task_struct {
[0] volatile long int state;
[8] void *stack;
[16] atomic_t usage;
[20] unsigned int flags;
...
顯示結構體中的成員定義,以及它的偏移:
# struct task_struct.pid
struct task_struct {
[1192] pid_t pid;
}
顯示結構體中成員的值:
# struct task_struct.pid ffffffff81a8d020
pid = 0
查看全局變量的值:
# p sysctl_tcp_rmem
sysctl_tcp_rmem = $4 =
{40960, 873800, 41943040}
查看percpu全局變量(加前綴per_cpu_):
# p per_cpu__irq_stat
PER-CPU DATA TYPE:
irq_cpustat_t per_cpu__irq_stat; // 變量類型的聲明
PER-CPU ADDRESSES:
[0]: ffff880028216540 // 0號CPU對應變量的地址
[1]: ffff880645416540
...
查看0號CPU對應變量的值:
# struct irq_cpustat_t ffff880028216540
struct irq_cpustat_t {
__softirq_pending = 0,
__nmi_count = 4780195,
irq0_irqs = 148,
...
(6) 反匯編和源碼行
反匯編:
# dis ffffffffa021ba91 // 反匯編一條指令
# dis -l probe_2093+497 10 // 反匯編從某個地址開始的10條指令
對於內核中的符號:
# sym tcp_v4_do_rcv // 通過symbol,顯示虛擬地址和源碼位置
# sym ffffffff8149f930 // 通過虛擬地址,顯示symbol和源碼位置
對於模塊中的符號:
需要先加載相應的模塊進來,才能顯示符號對應的源碼:
# mod // 查看模塊
# mod -s module /path/to/module.ko // 加載模塊
# sym symbol // 顯示符號對應的模塊源碼,也可以用virtual address
(7) 修改內存
提供動態的修改運行中內核的功能,以供調試,但是RHEL和CentOS上不允許。
wr:modifies the contents of memory.
wr [-u | -k | -p] [-8 | -16 | -32 | -64] [address | symbol] value
使用例子:
# p sysctl_tcp_timestamps
sysctl_tcp_timestamps = $3 = 1
# wr sysctl_tcp_timestamps 0
wr: cannot write to /dev/crash!
我勒個擦,/dev/crash的文件屬性是rw,但是crash_fops中並沒有提供寫函數,所以還是只讀的。
這個功能很有用,但被RHEL和CentOS禁止了,所以如需動態修改運行內核還是用systemtap吧。
Reference
[1]. http://www.ibm.com/developerworks/cn/linux/l-cn-kdump1/
[2]. http://www.ibm.com/developerworks/cn/linux/l-cn-kdump2/
[3]. http://www.ibm.com/developerworks/cn/linux/l-cn-kdump3/
[4]. http://www.ibm.com/developerworks/cn/linux/l-cn-kdump4/
[5]. http://www.ibm.com/developerworks/cn/linux/l-cn-dumpanalyse/