背景
crash工具是redhat公司的提供的一个开源的内核分析工具,它是在gdb的基础上实现了解析内核的功能,比如查看各个线程的内核栈、各任务的状态、内存的使用情况、中断的统计计数等等,我们可以根据当前内核里的数据来分析一些偶现的稳定性问题。这篇文章我们讲解一些常用的crash命令,让大家能快速上手。
运行crash
在项目中crash常用于分析内核的dump文件,用qemu来生成内核dump文件并用crash工具解析的详细步骤请参考 通过qemu和crash分析崩溃内核 这篇文章。crash运行起来后的日志如下:
max@ubuntu2204:~$ ./crash-8.0.4/crash dump.img linux-stable/build/vmlinux --machdep vabits_actual=48
crash 8.0.4
Copyright (C) 2002-2022 Red Hat, Inc.
……
……
KERNEL: linux-stable/build/vmlinux
DUMPFILE: dump.img
CPUS: 8
DATE: Sun Jan 21 23:02:14 CST 2024
UPTIME: 00:02:57
LOAD AVERAGE: 0.76, 0.25, 0.09
TASKS: 98
NODENAME: (none)
RELEASE: 6.7.0
VERSION: #8 SMP PREEMPT Sun Jan 21 22:29:54 CST 2024
MACHINE: aarch64 (unknown Mhz)
MEMORY: 2 GB
PANIC: ""
PID: 0
COMMAND: "swapper/0"
TASK: ffff8000824d3ec0 (1 of 8) [THREAD_INFO: ffff8000824d3ec0]
CPU: 0
STATE: TASK_RUNNING (ACTIVE)
WARNING: panic task not found
crash>
crash>
如上所示,crash运行起来后会打印出crash的版本信息和dump文件中的内核的相关信息,同时显示“crash>”提示符,这表示crash初步解析dump文件成功了。下面我们继续演示如何通过crash命令查看内核里的数据。
crash常用命令
log命令
log命令可以查看内核ringbuffer中的全部日志,如下:
crash> log
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]
[ 0.000000] Linux version 6.7.0 (max@ubuntu2204) (aarch64-linux-gnu-gcc (Linaro GCC 7.5-2019.12) 7.5.0, GNU ld (Linaro_Binutils-2019.12) 2.28.2.20170706) #8 SMP PREEMPT Sun Jan 21 22:29:54 CST 2024
[ 0.000000] KASLR disabled on command line
[ 0.000000] random: crng init done
[ 0.000000] Machine model: linux,dummy-virt
[ 0.000000] efi: UEFI not found.
[ 0.000000] NUMA: No NUMA configuration found
……
……
crash>
ps命令
ps命令可以查看当前内核中都有哪些任务,该命令会打印出各个任务的pid、ppid、任务状态、该任务对应的task_struct的虚拟地址、任务名称等等,如下:
crash> ps
PID PPID CPU TASK ST %MEM VSZ RSS COMM
> 0 0 0 ffff8000824d3ec0 RU 0.0 0 0 [swapper/0]
> 0 0 1 ffff000002d68ec0 RU 0.0 0 0 [swapper/1]
> 0 0 2 ffff000002d69d80 RU 0.0 0 0 [swapper/2]
> 0 0 3 ffff000002d6ac40 RU 0.0 0 0 [swapper/3]
> 0 0 4 ffff000002d6bb00 RU 0.0 0 0 [swapper/4]
> 0 0 5 ffff000002d6c9c0 RU 0.0 0 0 [swapper/5]
> 0 0 6 ffff000002d6d880 RU 0.0 0 0 [swapper/6]
0 0 7 ffff000002d6e740 RU 0.0 0 0 [swapper/7]
1 0 2 ffff000002cf8000 IN 0.0 2276 1228 linuxrc
2 0 3 ffff000002cf8ec0 IN 0.0 0 0 [kthreadd]
3 2 0 ffff000002cf9d80 IN 0.0 0 0 [pool_workqueue_]
4 2 0 ffff000002cfac40 ID 0.0 0 0
其中左侧有” > ”箭头标注的任务就是当前各个cpu上正在运行的任务。
bt命令
bt命令是查看内核中某些任务的内核栈,比如bt -a是查看当前各个cpu上正在运行的任务的内核栈,如下:
crash> bt -a
PID: 0 TASK: ffff8000824d3ec0 CPU: 0 COMMAND: "swapper/0"
#0 [ffff8000824c3d40] cpu_do_idle at ffff8000810a2a24
#1 [ffff8000824c3d50] default_idle_call at ffff8000810a2b9c
#2 [ffff8000824c3d70] do_idle at ffff8000800d6094
#3 [ffff8000824c3de0] cpu_startup_entry at ffff8000800d6338
#4 [ffff8000824c3e00] rest_init at ffff8000810a2e84
#5 [ffff8000824c3e20] arch_call_rest_init at ffff800081b9096c
#6 [ffff8000824c3e30] start_kernel at ffff800081b90ecc
……
……
当前系统中有8个cpu,所以bt -a命令会依次打出8个cpu上运行的任务的内核栈。同时也可以通过指定某个任务的pid或task_struct来打印该任务的内核栈,比如查看pid为17的任务的内核栈,如下:
crash> bt 17
PID: 17 TASK: ffff000002d68000 CPU: 0 COMMAND: "migration/0"
#0 [ffff800082a93d60] __switch_to at ffff8000810a80d4
#1 [ffff800082a93d80] __schedule at ffff8000810a8418
#2 [ffff800082a93e10] schedule at ffff8000810a8a2c
#3 [ffff800082a93e30] smpboot_thread_fn at ffff8000800b8898
#4 [ffff800082a93e70] kthread at ffff8000800b29ac
crash>
比如查看task_struct的虚拟地址为ffff000002d53b00的任务的内核栈,如下:
crash> bt ffff000002d53b00
PID: 13 TASK: ffff000002d53b00 CPU: 0 COMMAND: "rcu_tasks_kthre"
#0 [ffff800082a73ca0] __switch_to at ffff8000810a80d4
#1 [ffff800082a73cc0] __schedule at ffff8000810a8418
#2 [ffff800082a73d50] schedule at ffff8000810a8a2c
#3 [ffff800082a73d70] rcu_tasks_one_gp at ffff800080109b00
#4 [ffff800082a73e20] rcu_tasks_kthread at ffff800080109f28
#5 [ffff800082a73e70] kthread at ffff8000800b29ac
crash>
runq命令
runq命令可以查看当前各个cpu对应的rq上都有哪些就绪任务,该命令会分别打印出rq上的rt和cfs的就绪队列上的任务,如下:
crash> runq
CPU 0 RUNQUEUE: ffff00007fb33000
CURRENT: PID: 0 TASK: ffff8000824d3ec0 COMMAND: "swapper/0"
RT PRIO_ARRAY: ffff00007fb33200
[no tasks queued]
CFS RB_ROOT: ffff00007fb330c0
[no tasks queued]
……
……
CPU 7 RUNQUEUE: ffff00007fbcd000
CURRENT: PID: 100 TASK: ffff000008268000 COMMAND: "dd"
RT PRIO_ARRAY: ffff00007fbcd200
[no tasks queued]
CFS RB_ROOT: ffff00007fbcd0c0
[no tasks queued]
crash>
task 命令
task命令可以查看某个任务对应的task_stuct实例里的各个成员的值,比如查看pid为3的任务,如下:
crash> task 3
PID: 3 TASK: ffff000002cf9d80 CPU: 0 COMMAND: "pool_workqueue_"
struct task_struct {
thread_info = {
flags = 8,
{
preempt_count = 4294967298,
preempt = {
count = 2,
need_resched = 1
}
},
cpu = 0
},
__state = 1,
saved_state = 0,
stack = 0xffff800082a20000,
usage = {
……
……
也可以根据某个task_struct实例的虚拟地址查看该实例里的各个成员的值,如下:
crash> task ffff000002cf9d80
PID: 3 TASK: ffff000002cf9d80 CPU: 0 COMMAND: "pool_workqueue_"
struct task_struct {
thread_info = {
flags = 8,
{
preempt_count = 4294967298,
preempt = {
count = 2,
need_resched = 1
}
},
cpu = 0
},
__state = 1,
saved_state = 0,
stack = 0xffff800082a20000,
……
……
p 命令
p命令可以打印出某个变量的值,也可以打印出某个结构体实例中的各成员的值,如下:
crash> p linux_banner
linux_banner = $2 = 0xffff800081620c30 <linux_banner> "Linux version 6.7.0 (max@ubuntu2204) (aarch64-linux-gnu-gcc (Linaro GCC 7.5-2019.12) 7.5.0, GNU ld (Linaro_Binutils-2019.12) 2.28.2.20170706) #8 SMP PREEMPT Sun Jan 21 22:29:54 CST 2024\n"
crash>
比如打印出 init_mm 这个实例里各个成员的值,如下:
crash> p init_mm
init_mm = $3 = {
{
{
mm_count = {
counter = 1
}
},
mm_mt = {
{
ma_lock = {
{
rlock = {
raw_lock = {
……
……
sym命令
sym命令用于处理符号相关的操作,比如把某个符号转换成对应的虚拟地址,假如我们要找到linux_banner字符数组的起始虚拟地址, 对应的命令如下:
crash> sym linux_banner
ffff800081620c30 (D) linux_banner
crash>
rd 命令
rd命令可以读取指定虚拟地址或物理地址的数据,比如读取起始地址为ffff800081620c30,长度为 128 的这段内存的数据,如下:
crash> rd ffff800081620c30 128
ffff800081620c30: 65762078756e694c 2e36206e6f697372 Linux version 6.
ffff800081620c40: 78616d2820302e37 3275746e75627540 7.0 (max@ubuntu2
ffff800081620c50: 6161282029343032 696c2d3436686372 204) (aarch64-li
ffff800081620c60: 2d756e672d78756e 6e694c2820636367 nux-gnu-gcc (Lin
ffff800081620c70: 20434347206f7261 393130322d352e37 aro GCC 7.5-2019
ffff800081620c80: 352e37202932312e 20554e47202c302e .12) 7.5.0, GNU
ffff800081620c90: 616e694c2820646c 74756e69425f6f72 ld (Linaro_Binut
ffff800081620ca0: 393130322d736c69 322e32202932312e ils-2019.12) 2.2
ffff800081620cb0: 373130322e322e38 3823202936303730 8.2.20170706) #8
ffff800081620cc0: 45525020504d5320 6e75532054504d45 SMP PREEMPT Sun
ffff800081620cd0: 203132206e614a20 34353a39323a3232 Jan 21 22:29:54
ffff800081620ce0: 3230322054534320 0000000000000a34 CST 2024.......
同时我们也可以通过-r参数把这段内存里的数据单独保存到指定文件,如下:
crash> rd ffff800081620c30 128 -r test.dat
128 bytes copied from 0xffff800081620c30 to test.dat
crash>
vtop 命令
vtop命令是把虚拟地址转换成对应的物理地址,比如把虚拟地址ffff800081620c30转成对应的物理地址,如下:
crash> vtop ffff800081620c30
VIRTUAL PHYSICAL
ffff800081620c30 41820c30
PAGE DIRECTORY: ffff800081b86000
PGD: ffff800081b86800 => 10000000bffff003
PUD: ffff00007ffff010 => 10000000bfffe003
PMD: ffff00007fffe058 => 60000041800781
PAGE: 41800000 (2MB)
PTE PHYSICAL FLAGS
60000041800781 41800000 (VALID|RDONLY|SHARED|AF|PXN|UXN)
PAGE PHYSICAL MAPPING INDEX CNT FLAGS
fffffc0000060800 41820000 0 0 1 3fffc0000004000 reserved
crash>
其中PHYSICAL的值就是对应的物理地址。
pte 命令
pte命令可以把一个页表项里的值转换为对应的物理地址,如下:
crash> pte 18000000bfff7003
PTE PHYSICAL FLAGS
18000000bfff7003 bfff7000 (VALID)
crash>
kmem 命令
kmem命令可以查看内存的相关信息,比如查看当前内存的使用情况,如下:
crash> kmem -i
PAGES TOTAL PERCENTAGE
TOTAL MEM 505032 1.9 GB ----
FREE 486466 1.9 GB 96% of TOTAL MEM
USED 18566 72.5 MB 3% of TOTAL MEM
SHARED 408 1.6 MB 0% of TOTAL MEM
BUFFERS 59 236 KB 0% of TOTAL MEM
CACHED 453 1.8 MB 0% of TOTAL MEM
SLAB 2707 10.6 MB 0% of TOTAL MEM
TOTAL HUGE 0 0 ----
HUGE FREE 0 0 0% of TOTAL HUGE
TOTAL SWAP 0 0 ----
SWAP USED 0 0 0% of TOTAL SWAP
SWAP FREE 0 0 0% of TOTAL SWAP
COMMIT LIMIT 252516 986.4 MB ----
COMMITTED 0 0 0% of TOTAL LIMIT
crash>
struct命令
struct命令实现了结构体操作相关的功能,比如可以查看某个结构体内的各成员的偏移,以及该结构体总的大小,如下:
crash> struct -o tasklet_struct
struct tasklet_struct {
[0] struct tasklet_struct *next;
[8] unsigned long state;
[16] atomic_t count;
[20] bool use_callback;
union {
[24] void (*func)(unsigned long);
[24] void (*callback)(struct tasklet_struct *);
};
[32] unsigned long data;
}
SIZE: 40
crash>
也可以按照指定结构体来解析指定的内存,比如:
crash> struct tasklet_head ffff00007fb2aa98
struct tasklet_head {
head = 0x0,
tail = 0xffff00007fb2aa98
}
crash>
除了上面列举到的命令外,crash还有一些其他命令,比如list、waitq、vm等等,大家可以通过help命令查看详细说明,这里不再一一列举。
我们在解决实际问题过程中,可以根据crash的各个命令灵活组合运用,从而获取到自己需要的信息,找到有用的线索。
请关注微信公众号 “Linux研习社” 获取更多技术内容: