WEB资料库

您所在的位置:首页 > WEB资料库

Linux内核调试工具(二)-crash

当系统崩溃时,通过kdump可以获得当时的内存转储文件vmcore,但是该如何分析vmcore呢?

crash是一个用于分析内核转储文件的工具,一般和kdump搭配使用。

使用crash时,要求调试内核vmlinux在编译时带有-g选项,即带有调试信息。

如果没有指定vmcore,则默认使用实时系统的内存来分析。

 

值得一提的是,crash也可以用来分析实时的系统内存,是一个很强大的调试工具。

crash使用gdb作为内部引擎,语法类似于gdb,命令的使用说明可以用<cmd> help来查看。

使用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中先查看错误类型,如:

1. divide error: 0000 [#1] SMP,除数为0造成内核崩溃,由1号CPU触发。

2. 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

  1. KERNEL: /usr/lib/debug/lib/modules/2.6.32-358.el6.x86_64/vmlinux

  2. UMPFILE: vmcore [PARTIAL DUMP]

  3. CPUS: 12

  4. DATE: Fri Sep 19 16:47:01 2014

  5. UPTIME: 7 days, 06:37:46

  6. AVERAGE: 0.19, 0.05, 0.01

  7. TASKS: 282

  8. ODENAME: localhost.localdomain

  9. RELEASE: 2.6.32-358.el6.x86_64

  10. VERSION: #1 SMP Tue Oct 29 10:18:21 CST 2013

  11. MACHINE: x86_64 (1999 Mhz)

  12. MEMORY: 48 GB

  13. PANIC: "Oops: 0002 [#1] SMP " (check log for details)

  14. PID: 0

  15. COMMAND: "swapper"

  16. TASK: ffffffff81a8d020 (1 of 12) [THREAD_INFO: ffffffff81a00000]

  17. CPU: 0

  18. STATE: TASK_RUNNING (PANIC)

这些基本输出信息简单明了,可由sys命令触发。

 

(4) crash常用命令

bt:打印函数调用栈,displays a task's kernel-stack backtrace,可以指定进程号bt <pid>。

log:打印系统消息缓冲区,displays the kernel log_buf contents,如log | tail -n 30。

ps:显示进程的状态,>表示活跃的进程,如ps | grep RU。

sys:显示系统概况。

kmem -i:显示内存使用信息。

dis <addr>:对给定地址进行反汇编。

 

exception RIP即为造成错误的指令。

关于log命令:

内核首先把消息打印到内核态的ring buffer,用户态的klogd负责读取并转发给syslogd,让它记录到磁盘。

在内核崩溃时,可能无法把消息记录到磁盘,但是ring buffer中一般会有记录。所以log命令有时候能查看

到系统日志中所缺失的信息。

 

(5) 结构体和变量

查看结构体中所有成员的值,例如:

# ps | grep RU

  1. > 0 0 0 ffffffff81a8d020 RU 0.0 0 0 [swapper]

     

# struct task_struct ffffffff81a8d020

 

  1. struct task_struct {

  2. state = 0,

  3. stack = 0xffffffff81a00000,

  4. usage = {

  5. counter = 2

  6. },

  7. flags = 2097408,

显示整个结构体的定义:

# struct task_struct

  1. struct task_struct {

  2. volatile long int state;

  3. void *stack;

  4. atomic_t usage;

  5. unsigned int flags;

 

显示整个结构体的定义,以及每个成员的偏移:

# struct -o task_struct

  1. struct task_struct {

  2. [0] volatile long int state;

  3. [8] void *stack;

  4. [16] atomic_t usage;

  5. [20] unsigned int flags;

  6. ...

显示结构体中的成员定义,以及它的偏移:

# struct task_struct.pid

  1. struct task_struct {

  2. [1192] pid_t pid;

  3. }

显示结构体中成员的值:

# struct task_struct.pid ffffffff81a8d020

  1. pid = 0

查看全局变量的值:

# p sysctl_tcp_rmem

  1. sysctl_tcp_rmem = $4 =

  2. {40960, 873800, 41943040}

查看percpu全局变量(加前缀per_cpu_):

# p per_cpu__irq_stat

  1. PER-CPU DATA TYPE:

  2. irq_cpustat_t per_cpu__irq_stat; // 变量类型的声明

  3. PER-CPU ADDRESSES:

  4. [0]: ffff880028216540 // 0号CPU对应变量的地址

  5. [1]: ffff880645416540

  6. ...

查看0号CPU对应变量的值:

# struct irq_cpustat_t ffff880028216540

  1. truct irq_cpustat_t {

  2. __softirq_pending = 0,

  3. __nmi_count = 4780195,

  4. irq0_irqs = 148,

  5. ...

(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

  1. sysctl_tcp_timestamps = $3 = 1

# wr sysctl_tcp_timestamps 0

/dev/crash的文件属性是rw,但是crash_fops中并没有提供写函数,所以还是只读的。

这个功能很有用,但被RHEL和CentOS禁止了,所以如需动态修改运行内核还是用systemtap吧。

上一篇:Linux基金会决定加速发展blockchain技术...

下一篇:《Linux 工作站安全检查清单》—来自 Linux 基金会内部...