Linux内核调试方法

 2024-09-26 16:41:01  阅读 0

内核开发比用户空间开发更难的一个原因是内核调试困难。内核错误经常导致系统崩溃,而且很难保留错误现场。调试内核的关键在于你对内核的深刻理解。

在调试一个bug之前,我们需要做以下准备:

有确认的bug,包括这个bug的内核版本号,需要分析这个bug是在哪个版本引入的,对解决问题很有帮助,可以用二分查找法逐步找到这个bug是在哪个版本引入的版本号。

对内核代码了解得越深越好。你也需要一点运气来重现这个 bug。如果你能找到一个模式,你就离找到问题的原因不远了;最小化系统。逐一排除可能导致 bug 的因素。

内核中的错误

内核中的 Bug 也是多种多样的,其发生的原因数不胜数,表现形式也多种多样,从隐藏在源代码中的错误到摆在眼前的 Bug,其发生往往是由一系列连锁反应事件引发的。虽然内核调试有一定的难度,但通过你的努力和理解,你可能会喜欢这种挑战。

内核调试配置选项

学习编写驱动程序需要构建和安装自己的内核(标准主线内核)。其中一个最重要的原因是内核开发人员已经内置了几个调试功能。但是,由于这些功能会导致额外的输出并导致性能下降,因此发行版供应商通常会禁用发行版内核中的调试功能。

内核配置

为了实现内核调试,内核配置中添加了几项:

Kernel hacking ---> 
[*] Magic SysRq key 
[*] Kernel debugging 
[*] Debug slab memory allocaTIons 
[*] Spinlock and rw-lock debugging: basic checks 
[*] Spinlock debugging: sleep-inside-spinlock checking 
         [*] Compile the kernel with debug info 
Device Drivers ---> 
         Generic Driver Options ---> 
         [*] Driver Core verbose debug messages 
General setup ---> 
         [*] Configure standard kernel features (for small systems) ---> 
         [*] Load all symbols for debugging/ksymoops

调试原子操作

从内核 2.5 开始,内核提供了很好的工具来检查原子操作导致的各种问题。内核提供了一个原子操作计数器,可以配置该计数器,如果原子操作经常进入休眠或执行了某些可能导致休眠的操作,则可以打印警告消息并提供跟踪线索。

因此可以检测到各种潜在的错误,包括使用锁时调用()、使用锁时以阻塞方式请求内存分配等。

可以使用以下选项来充分利用此功能:

CONFIG_PREEMPT = y 
CONFIG_DEBUG_KERNEL = y 
CONFIG_KLLSYMS = y 
CONFIG_SPINLOCK_SLEEP = y

触发错误并打印信息BUG()和()

有多个内核调用可用于标记错误、提供断言和输出信息。最常用的两个是 BUG() 和 ()。它们定义在:

#ifndef HAVE_ARCH_BUG 
#define BUG() do { 
   printk("BUG: failure at %s:%d/%s()! ", __FILE__, __LINE__, __FUNCTION__); 
   panic("BUG!"); /* 引发更严重的错误,不但打印错误消息,而且整个系统业会挂起 */ 
while (0) 
#endif 

#ifndef HAVE_ARCH_BUG_ON 
   #define BUG_ON(condiTIon) do { if (unlikely(condiTIon)) BUG(); }while(0) 
  #endif

当调用这两个宏时,会引起Oops,从而导致堆栈回溯并打印错误信息。

※你可以把这两个调用当做断言来使用,如:();

WARN(x) 和 (x)

相反,它调用并打印没有 OOP 的堆栈信息。它的定义如下:

#ifndef __WARN_TAINT
#ifndef __ASSEMBLY__
extern void warn_slowpath_fmt(const char *file, 
        const int line, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
extern void warn_slowpath_fmt_taint(const char *file, const int line, 
                    unsigned taint, const char *fmt, ...)
    __attribute__((format(printf, 4, 5)));
extern void warn_slowpath_null(const char *file, const int line);
#define WANT_WARN_ON_SLOWPATH
#endif
#define __WARN() warn_slowpath_null(__FILE__, __LINE__)
#define __WARN_printf(arg...) warn_slowpath_fmt(__FILE__, __LINE__, arg)
#define __WARN_printf_taint(taint, arg...) 
    warn_slowpath_fmt_taint(__FILE__, __LINE__, taint, arg)
#else
#define __WARN() __WARN_TAINT(TAINT_WARN)
#define __WARN_printf(arg...) do { printk(arg); __WARN(); } while (0)
#define __WARN_printf_taint(taint, arg...) 
    do { printk(arg); __WARN_TAINT(taint); } while (0)  
#endif

#ifndef WARN_ON
#define WARN_ON(condition) ({    
    int __ret_warn_on = !!(condition); 
    if (unlikely(__ret_warn_on)) 
        __WARN(); 
    unlikely(__ret_warn_on); 
})
#endif

#ifndef WARN
#define WARN(condition, format...) ({   
    int __ret_warn_on = !!(condition); 
    if (unlikely(__ret_warn_on)) 
        __WARN_printf(format); 
    unlikely(__ret_warn_on); 
})
#endif

()

有时,您只需要在终端上打印堆栈回溯信息以帮助您进行调试。在这种情况下,您可以使用()。此函数仅在终端上打印寄存器上下文和函数回溯。

if (!debug_check) { 
    printk(KERN_DEBUG “provide some information…/n”);  
    dump_stack();  
    }

()

内核提供的格式化打印功能。

功能稳健性

鲁棒性是最容易接受的品质,几乎可以在任何地方、任何时间被内核调用(中断上下文、进程上下文、持有锁、在多处理器上等等)。

功能漏洞

系统启动过程中,在终端初始化之前,有些地方是无法调用的,如果确实需要调试系统启动过程最开始的地方,可以采用如下方法:

使用串口调试将调试信息输出到其他终端设备。

使用(),该函数在系统启动时就具有打印功能。但它仅支持某些硬件系统。

日志级别

主要区别在于前者可以指定一个LOG级别,内核根据这个级别来决定是否在终端上打印消息,高于指定级别的消息内核会全部显示在终端上。

您可以使用以下方法指定 LOG 级别:

printk(KERN_CRIT “Hello, world! ”);

请注意,第一个参数实际上并不是一个参数,因为没有逗号 (,) 将级别 () 与格式字符分开。它只是一个普通字符串(实际上,它代表字符串“”;请参阅表 1 了解完整的日志级别列表)。

作为预处理器的一部分,C 使用字符串连接功能自动将两个字符串组合在一起。结果是一个包含日志级别和用户指定的格式字符串的字符串。

内核使用这个指定的LOG级别和当前终端的LOG级别来决定是否打印到终端。以下是可用的LOG级别:

#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */ 
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
#define KERN_DEFAULT "" /* Use the default kernel loglevel */

注意,如果调用者没有提供日志级别,系统会使用默认值“”(这意味着只记录该级别以上的日志消息)。由于默认值各不相同,因此使用时最好指定LOG级别。拥有LOG级别的一个好处是,我们可以有选择地输出LOG。

比如我们平时只需要打印级别以上的关键日志,但是在调试的时候,我们可以选择打印级别以上的详细日志。而且我们不需要修改代码,只需通过命令更改默认的日志输出级别即可:

mtj@ubuntu :~$ cat /proc/sys/kernel/printk
4 4 1 7
mtj@ubuntu :~$ cat /proc/sys/kernel/printk_delay
0
mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit

mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit_burst
10

第一项定义了 API 当前使用的日志级别。这些日志级别代表控制台日志级别、默认消息日志级别、最低控制台日志级别和默认控制台日志级别。该值表示消息之间的延迟时间(以毫秒为单位)(用于在某些场景中提高可读性)。

注意这里它的值为0,并且不能通过/proc进行配置。

定义消息之间允许的最小时间(当前定义为每 5 秒一定数量的内核消息)。消息数量由 st 定义(当前定义为 10)。

如果您使用的是非标准内核,并且正在使用带宽受限的控制台设备(例如,通过串行端口),这将非常有用。请注意,在内核中,速度限制由调用者控制,而不是在 中实现。如果用户需要速度限制,则用户需要调用该函数。

记录缓冲区

内核消息存储在一个大小为 的循环队列中。定义如下:

\#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)

※该变量由内核编译时的配置文件定义,对于i386平台,其值定义如下(在/arch/i386/中):

CONFIG_LOG_BUF_SHIFT=18

记录缓冲区操作:

① 当一个消息被读出到用户空间时,它会被从环形队列中删除。

②当消息缓冲区已满时,如果有另外一个()调用,新的消息会覆盖队列中的旧消息。

③ 在读写循环队列的时候,同步问题可以轻松解决。

※这个记录缓冲区之所以被称为环形,是因为它的读写是以环形队列的方式进行的。

/klogd

在标准的Linux系统上,用户空间守护进程klogd从日志缓冲区获取内核消息并通过守护进程将其保存在系统日志文件中。klogd进程可以从/proc/kmsg文件中读取这些消息,也可以通过()系统调用读取这些消息。默认情况下,它选择读取/proc。

klogd 守护进程被阻塞,直到消息缓冲区中有新的消息。一旦有新的内核消息,klogd 被唤醒,读取内核消息并进行处理。默认情况下,处理例程是将内核消息传递给守护进程。守护进程通常将收到的消息写入 /var/log/ 文件。但是,仍然可以通过 /etc/.conf 文件配置它以选择其他输出文件。

linux清空缓冲区_c语言fflusn缓冲代码_c语言清除缓冲区linux

消息

dmesg 命令还可用于打印和控制内核缓冲区。此命令使用系统调用读取内核环形缓冲区并将其转发到标准输出 ()。此命令还可用于清除内核环形缓冲区(使用 -c 选项)、设置控制台日志级别(-n 选项)以及定义用于读取内核日志消息的缓冲区大小(-s 选项)。

请注意,如果未指定缓冲区大小,dmesg 将使用 UFFER 操作来确定缓冲区大小。

注意

a) 虽然很健壮,但从源代码中你就会知道这个函数非常低效:在复制字符时它每次只复制一个字节,而且调用输出可能会引起中断。因此,如果你的驱动程序正在做性能测试或者功能调试后发布,记得尽量减少输出,在出错时只输出少量信息。否则输出无用的信息会影响性能。

b) 临时缓存只有1KB,因此所有一次性函数只能记录内核的整体结构和日志系统。

动态调试

动态调试是通过动态地启用或禁用某些内核代码来获取额外的内核信息。

首先需要设置内核选项,所有通过()/()打印的信息都可以动态显示或者不显示。

您可以使用简单的查询语句来过滤想要显示的信息。

- 源文件名

- 函数名称

- 行号(包括指定范围的行号)

- 模块名称

- 格式字符串

将需要打印的信息的格式写入//。

nullarbor:~ # echo 'file svcsock.c line 1603 +p' > 
/dynamic_debug/control

参考:

内核日志及其结构的简要分析--Ninja

内核日志:API 和实现

实现分析

调试-如何.txt

内存调试工具

它由 Johan Lindh 编写,是一个开源的 C 语言内存错误检测工具,您可以自行下载。只需在代码中添加一个头文件并在 gcc 语句中定义它,您就可以追踪程序中的内存泄漏和错误。

支持ANSIC,提供结果日志记录,可检测双重释放(-free)、错误释放(free)、未释放的内存()、溢出和下溢等。

清单 1. 内存示例(test1.c)

#include  
#include  
#include "memwatch.h" 
int main(void){ 
  char *ptr1; 
  char *ptr2; 
  ptr1 = malloc(512); 
  ptr2 = malloc(512); 
  ptr2 = ptr1; 
  free(ptr2); 
  free(ptr1); 
}

清单 1 中的代码分配了两个 512 字节的内存块,然后将指向第一个内存块的指针设置为指向第二个内存块。结果,第二个内存块的地址丢失了,从而导致内存泄漏。

现在我们编译清单 1 的 .c。以下是一个例子:

测试1

gcc -DMEMWATCH -DMW_STDIO test1.c memwatchc -o test1

当您运行 test1 程序时,它会生成有关泄漏内存的报告。清单 2 显示了示例 .log 输出文件。

清单 2. test1 .log 文件

MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh
...
double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)
...
unfreed: <2> test1.c(11), 512 bytes at 0x80519e4
{FE FE FE FE FE FE FE FE FE FE FE FE ..............}
Memory usage statistics (global):
 N)umber of allocations made: 2 
 L)argest memory usage : 1024 
 T)otal of all alloc() calls: 1024 
 U)nfreed bytes totals : 512

显示导致问题的实际信息。如果您释放一个已经释放的指针,它会告诉您。对于尚未释放的内存也是如此。日志的末尾显示统计信息,包括泄漏了多少内存、使用了多少内存以及总共分配了多少内存。

亚美尼亚德

YAMD 包由 Nate 编写,可以查找 C++ 和 C++ 动态、内存分配相关的问题。在撰写本文时,YAMD 的最新版本是 0.32。请下载 yamd-0.32.tar.gz。执行 make 命令来构建程序;然后执行 make 命令来安装程序并设置工具。

下载 YAMD 后,在 test1.c 上使用它。删除 #.h 并进行以下小更改:

使用 YAMD 的测试 1

gcc -g test1.c -o test1

清单 3 展示了 test1 上的 YAMD 的输出。

清单 3. 使用 YAMD 的 test1 输出

YAMD version 0.32 
Executable: /usr/src/test/yamd-0.32/test1
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
... 
INFO: Normal allocation of this block
Address 0x40028e00, size 512
... 
INFO: Normal deallocation of this block
Address 0x40025e00, size 512
...
ERROR: Multiple freeing At
free of pointer already freed
Address 0x40025e00, size 512
...
WARNING: Memory leak
Address 0x40028e00, size 512
WARNING: Total memory leaks:
1 unfreed allocations totaling 512 bytes
*** Finished at Tue ... 10:07:15 2002 
Allocated a grand total of 1024 bytes 2 allocations
Average of 512 bytes per allocation 
Max bytes allocated at one time: 1024
24 K alloced internally / 12 K mapped now / 8 K max
Virtual program size is 1416 K
End.

YAMD 显示我们已经释放了内存,并且存在内存泄漏。让我们在清单 4 中的另一个示例程序上尝试 YAMD。

清单 4. 内存代码(test2.c)

#include  
#include  
int main(void)

    char *ptr1; 
    char *ptr2; 
    char *chptr; 
    int i = 1; 
    ptr1 = malloc(512); 
    ptr2 = malloc(512); 
    chptr = (char *)malloc(512); 
    for (i; i <= 512; i++) { 
        chptr[i] = 'S'
    } 
    ptr2 = ptr1; 
    free(ptr2); 
    free(ptr1); 
    free(chptr); 
}

您可以使用以下命令启动 YAMD:

./run-yamd /usr/src/test/test2/test2

清单 5 显示了在示例程序 test2 上使用 YAMD 的输出。YAMD 告诉我们 for 循环中存在“越界”情况。

清单 5. 使用 YAMD 的 test2 输出

Running /usr/src/test/test2/test2
Temp output to /tmp/yamd-out.1243
********* 
./run-yamd: line 101: 1248 Segmentation fault (core dumped) 
YAMD version 0.32
Starting run: /usr/src/test/test2/test2 
Executable: /usr/src/test/test2/test2
Virtual program size is 1380 K
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
... 
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal allocation of this block
Address 0x4002be00, size 512
ERROR: Crash
...
Tried to write address 0x4002c000
Seems to be part of this block:
Address 0x4002be00, size 512
...
Address in question is at offset 512 (out of bounds)
Will dump core after checking heap.
Done.

和 YAMD 都是很有用的调试工具,但它们的使用方式不同。对于 ,您需要添加包含文件 .h 并打开两个编译时标志。对于链接语句,YAMD 只需要 -g 选项。

栅栏

大多数 Linux 发行版都包含 Fence 包,但您也可以选择下载它。Fence 是 Bruce 编写的 () 调试库。它会在您分配内存后立即分配受保护的内存。

如果出现错误(超出数组末尾),程序将生成保护错误并立即结束。通过将 Fence 与 gdb 结合使用,您可以准确跟踪哪一行尝试访问受保护的内存。另一个功能是能够检测内存泄漏。

跟踪命令是一个功能强大的工具,可以显示用户空间程序发出的所有系统调用。它以符号形式显示这些调用的参数和返回值。它从内核接收信息,不需要以任何特殊方式构建内核。它对于向应用程序和内核开发人员发送跟踪信息非常有用。

在清单 6 中,其中一个分区有格式错误,清单显示了创建文件系统 (mkfs) 的调用的开头。确定哪个调用导致了问题。

清单 6. mkfs 的开头

execve("/sbin/mkfs.jfs", ["mkfs.jfs""-f""/dev/test1"], &
...
open("/dev/test1", O_RDWR|O_LARGEFILE) = 4
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs:  warning -
cannot set blocksize on block device /dev/test1: Invalid argument ) = 98
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5
ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: can't determine device"...,  ..._exit(1) = ?

清单 6 显示 ioctl 调用导致用于格式化分区的 mkfs 程序失败。ioctl 失败。(- 在调用 ioctl 的源代码中定义。)

ioctl 将添加到 Linux 中的所有设备,而逻辑卷管理器目前尚不支持它。因此,如果 ioctl 调用失败,mkfs 代码将改为调用较旧的 ioctl 调用;这使得 mkfs 可以与逻辑卷管理器配合使用。

糟糕

Oops(也称为 Panic)消息包含系统错误的详细信息,例如 CPU 寄存器的内容等。这是内核通知用户发生了不幸的事情的最常见方式。

内核只能发出OOPS,包括向终端输出错误信息,输出寄存器中存储的信息,以及输出用于追踪的线索。通常,在发出OOPS之后,内核会处于不稳定状态。

OOPS 的可能原因有很多,包括越界内存访问或非法指令。

※ 作为内核开发人员,肯定会经常和OOP打交道。

※ OOP 中包含的重要信息对于所有架构的机器都是完全相同的:寄存器上下文和回溯线索(回溯线索显示导致错误的函数调用链)。

在 Linux 中,调试系统崩溃的传统方法是分析崩溃发生时发送到系统控制台的 Oops 消息。一旦掌握了详细信息,就可以将消息发送给实用程序,实用程序将尝试将代码转换为指令,并将堆栈值映射到内核符号。

※例如线索中的地址会被转换成可见的函数名。

需要几样东西:Oops 消息输出、正在运行的内核的 .map 文件、/proc/ksyms 和 /proc/。

有关如何使用它的完整说明,请参阅内核源代码 /usr/src/linux//oops-.txt 或手册页。它返回代码部分,指向发生错误的指令,并显示一个跟踪部分,说明代码是如何被调用的。

首先,将 Oops 消息保存在文件中,以便您可以通过使用程序运行它。

清单 7 显示了安装 JFS 文件系统的 mount 命令创建的 Oops 消息。

清单 7. 处理后的 Oops 消息

ksymoops 2.4.0 on i686 2.4.17. Options used
... 15:59:37 sfb1  kernel: Unable to handle kernel NULL pointer dereference atvirtual address 0000000
... 15:59:37 sfb1 kernel: c01588fc
... 15:59:37 sfb1 kernel: *pde = 0000000
... 15:59:37 sfb1 kernel: Oops: 0000
... 15:59:37 sfb1 kernel: CPU: 0
... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]
... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688] 
[get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
[do_page_fault+0/1264]
... 15:59:37 sfb1 kernel: Call Trace: []...
... 15:59:37 sfb1 kernel: [
>EIP; c01588fc <=====
...
Trace; c0106cf3 33/40>
Code; c01588fc 00000000 <_EIP>:
Code; c01588fc <===== 
  0: 8b 2d 00 00 00 00 mov 0x0,%ebp <=====
Code; c0158902 42/2c0> 
  6: 55 push %ebp

接下来,您要确定 .oc 中的哪一行代码导致了问题。Oops 消息告诉我们问题是由偏移量 3c 处的指令引起的。一种方法是使用汇编程序处理 .o 文件并查看偏移量 3c。使用汇编程序反汇编模块函数,看看您的 C 源代码会生成哪些汇编指令。

清单 8 显示了使用它之后会看到的内容,然后我们查看 C 代码,发现空值是由第 109 行引起的。偏移地址 3c 很重要,因为 Oops 消息将其标识为导致问题的位置。

清单 8. 汇编程序清单

109 printk("%d ",*ptr);
objdump jfs_mount.o
jfs_mount.o: file format elf32-i386
Disassembly of section .text:
00000000 : 0:55 push %ebp 
...
2c: e8 cf 03 00 00 call 400 
31: 89 c3 mov %eax,%ebx 
33: 58 pop %eax 
34: 85 db test %ebx,%ebx 
36: 0f 85 55 02 00 00 jne 291 0x291> 
3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above 
42: 55 push %ebp

开发版 2.5 内核引入了一项功能,可以通过定义一个编译选项来启用该功能,该选项会加载与内核映像对应的内存地址的符号名称(即函数名称),因此内核可以打印解码的跟踪。

相应的,解码OOPS不再需要.map和工具,另外这样做会使内核变大,因为符号名对应的地址必须一直驻留在内核所在的内存中。

#cat /proc/kallsyms 
c0100240 T _stext 
c0100240 t run_init_process 
c0100240 T stext 
c0100269 t init 

是什么?

kexec 是实现 kdump 机制的关键,它包含两个组件:一个是内核空间中的系统调用,负责在生产内核(或首次)启动时,加载捕获内核(或)到指定地址;另一个是用户空间工具 kexec-tools,将捕获内核的地址传递给生产内核,以便在系统崩溃时能找到捕获内核的地址并运行。

没有 kexec,就没有 kdump。kexec 首先使得在一个内核中启动另一个内核成为可能,这让 kdump 有了用武之地。kexec 最初的目的是为了节省开发人员重启系统的时间。谁能想到,这种“偷懒”的技术,竟然催生出最成功的内存转储机制?

什么是 kdump?

Kdump 的概念出现于 2005 年左右,是目前为止最可靠的内核转储机制,被各大 选用。Kdump 是基于 kexec 的高级内核崩溃转储机制,当系统崩溃时,kdump 使用 kexec 启动到第二个内核,第二个内核通常被称为捕获内核,以非常小的内存启动,用于捕获转储映像。

第一个内核保留一部分内存供第二个内核启动。由于 kdump 使用 kexec 来启动捕获的内核,绕过 BIOS,因此第一个内核的内存得以保留。这就是内核崩溃转储的本质。

kdump 需要两个用途不同的内核,生产内核和捕获内核。生产内核是捕获内核服务的对象,捕获内核会在生产内核崩溃时启动,和对应的内核一起组成一个微环境,对生产内核下的内存进行收集和转储。

如何使用 kdump

构建系统并转储内核。此操作有两个选项:

1)构建单独的自定义转储捕获内核来捕获内核转储;

2) 或者,您可以使用系统内核本身作为转储捕获内核,这样就无需构建单独的转储捕获内核。

方法 (2) 只能在支持可重定位内核的体系结构上使用;目前 i386、ppc64 和 ia64 体系结构支持可重定位内核。构建可重定位内核允许您捕获转储,而无需构建第二个内核。但是,有时您可能希望构建自定义转储捕获内核以满足特定要求。

如何访问捕获内存

内核崩溃前核心映像的所有必要信息都以 ELF 格式编码并存储在保留的内存区域中。ELF 头所在的物理地址作为命令行参数 (=) 传递给新启动的转储内核。

在 i386 架构上,无论操作系统内核重定位到哪里,启动时都会使用前 640K 物理内存。因此,当第二个内核重新启动时,这 640K 区域由 kexec 备份。

在第二个内核中,“前一个系统的内存”可以通过两种方式访问​​:

1)通过/dev/设备接口。

“捕获”设备可以以“原始”模式“读取”设备文件并将其写入文件。这是内存的“原始”转储,分析/捕获工具应该足够“智能”,知道从哪里获取正确的信息。ELF 文件头(通过命令行参数传递)可能会有所帮助。

2)通过/proc/。

这种方法是将 dump 输出为 ELF 格式的文件,可以使用一些文件复制命令(如 cp、scp 等)将信息读出。同时 gdb 可以对得到的 dump 文件进行一些调试(有限)。这种方法可以确保内存中的页面以正确的方式保存(注意,前 640K 内存是重新映射的)。

kdump 的优点

1)高可靠性

崩溃转储数据可以从新启动的内核的上下文中获取,而不是从已经崩溃的内核的上下文中获取。

2)多版本支持

LKCD(Linux Crash Dump)已并入 LDP(Linux - )内核。SUSE 和 Linux 都对 kdump 提供技术支持。

配置 kdump

安装软件包和实用程序

Kdump 使用的工具全部都在 kexec-tools 中。- 用于分析文件。从 rhel5 开始,发行版中已默认安装 kexec-tools。并且 kdump 也集成在发行版中。

所以如果你使用的是rhel5及以后的版本,就不需要安装kexec-tools了,如果需要调试kdump生成的文件,需要手动安装该包,检查安装包运行情况:

3.3.2 参数相关设置 uli13lp1:/  # rpm -qa|grep kexec 
  kexec-tools-2.0.0-53.43.10 
  uli13lp1:/ # rpm -qa 'kernel*debuginfo*' 
  kernel-default-debuginfo-3.0.13-0.27.1 
  kernel-ppc64-debuginfo-3.0.13-0.27.1

系统内核设置选项和dump捕获内核配置选项在《使用Crash工具分析Linux Dump文件》一文中已经介绍过,这里就不再赘述,只列出内核启动参数设置和配置文件设置。

1)修改内核启动参数,为启动捕获内核预留内存

配置kdump使用的内存大小,方法如下:添加启动参数“=Y@X”,其中Y为kdump捕获内核所保留的内存,X为保留部分内存的起始位置。

对于 i386 和,编辑 /etc/grub.conf 并在内核行末尾添加“=128M”。

对于 ppc64,在 /etc/.conf 末尾添加“=128M”。

在 ia64 上,编辑 /etc/elilo.conf 并将“=256M”添加到内核行。

2)kdump配置文件

kdump的配置文件为/etc/kdump.conf(RHEL6.2);/etc//kdump(sp2)。各个文件头部都有选项说明,可以根据需要设置相应选项。

启动 kdump 服务

设置完保留内存后需要重启机器,否则kdump无法使用。启动kdump服务:

Rhel6.2:

# chkconfig kdump on 
# service kdump status 
  Kdump is operational 
# service kdump start

# chkconfig boot.kdump on 
# service boot.kdump start

测试配置是否有效

您可以通过 kexec 加载内核映像,让系统做好捕获崩溃的准备。您可以通过 sysrq 强制系统崩溃。

# echo c > /proc/sysrq-trigger

这会导致内核崩溃。如果配置有效,系统将重新启动进入 kdump 内核。当系统进程到达启动 kdump 服务的点时,它将被复制到您在 kdump 配置文件中设置的位置。

RHEL 默认目录为:/var/crash;SLES 默认目录为:/var/log/dump。然后系统重启,进入正常内核。一旦回到正常内核,就可以在上面的目录中找到这个文件,这就是内存转储文件。可以使用之前安装的 -crash 工具来分析它(更详细的crash使用方法会在本系列的后面文章中介绍)。

# crash /usr/lib/debug/lib/modules/2.6.17-1.2621.el5/vmlinux 
 /var/crash/2006-08-23-15:34/vmcore 
 crash> bt

加载“转储捕获”内核

当需要启动系统内核时,可以使用下面的步骤和命令来加载“dump ”内核:

kexec -p --initrd=for-dump-capture-kernel> --args-linux --append="root= init 1 irqpoll"

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

转储捕获内核应该是格式的图像(即未压缩的ELF图像文件),而不是格式的图像;

默认情况下,ELF 文件头以 ELF64 格式存储,以支持内存超过 4GB 的系统,但可以指定“--elf32-core-”标志强制使用 ELF32 格式的 ELF 文件头。

这个标志需要特别注意,原因很简单:当前版本的GDB无法在32位系统上打开ELF64格式的文件,ELF32格式的文件头不能在“非PAE”系统(即内存小于4GB的系统)上使用;

启动参数为“”,由于“转储捕获内核”采用了“共享中断”技术,可以降低驱动初始化失败的概率;

必须指定,格式为 和要使用的根设备的名称。有关详细信息,您可以查看 mount 命令的输出;命令“init 1”将启动“dump ”进入单用户模式,不支持网络。如果您想要网络支持,请使用“init 3”。

后记

Kdump 是一种强大而灵活的内核转储机制,能够在生产内核的上下文中执行捕获的内核是非常有价值的。本文仅介绍如何在 RHEL6.2 和 RHEL6.3 中配置 kdump。

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码