内核开发比用户空间开发更难的一个诱因就是内核调试艰辛。内核错误常常会造成系统宕机,很难保留出错时的现场。调试内核的关键在于你的对内核的深刻理解。
嵌入式进阶教程分门别类整理好了,看的时侯非常便捷,因为内容较多,这儿就截取一部份图吧。
须要的同事私信【内核】即可申领。
内核学习地址:Linux内核源码/显存调优/文件系统/进程管理/设备驱动/网路合同栈-学习视频教程-腾讯课堂
调试前的打算
在调试一个bug之前,我们所要做的打算工作有:
有一个被确认的bug,包含这个bug的内核版本号,须要剖析出这个bug在哪一个版本被引入,这个对于解决问题有极大的帮助。可以采用二分查找法来逐渐锁定bug引入版本号。
对内核代码理解越深刻越好,同时还须要一点点运气,该bug可以复现。假如才能找到规律,这么离找到问题的诱因就不远了;最小化系统。把可能形成bug的诱因逐一排除掉。
内核中的bug
内核中的bug也是多种多样的。它们的形成有无数的缘由,同时假象也变化多端。从隐藏在源代码中的错误到诠释在目击者面前的bug,其发作常常是一系列连锁反应的风波才可能触发的。其实内核调试有一定的困难linux系统编程,并且通过你的努力和理解,说不定你会喜欢上这样的挑战。
内核调试配置选项
学习编撰驱动程序要建立安装自己的内核(标准主线内核)。最重要的诱因之一是:内核开发者早已构建了多项用于调试的功能。并且因为这种功能会导致额外的输出,并造成能量升高,因而发行版厂商一般会严禁发行版内核中的调试功能。
内核配置
为了实现内核调试,在内核配置上降低了几项:
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开发,为了检测各种由原子操作引起的问题,内核提供了绝佳的工具。
内核提供了一个原子操作计数器,它可以配置成,一旦在原子操作过程中,常常步入睡眠或则做了一些可能导致睡眠的操作,就复印警告信息并提供追踪线索。
所以,包括在使用锁的时侯调用schedule(),正使用锁的时侯以阻塞形式恳求分配显存等,各类潜在的bug都还能被侦测到。
下边这种选项可以最大限度地借助该特点:
CONFIG_PREEMPT = y
CONFIG_DEBUG_KERNEL = y
CONFIG_KLLSYMS = y CONFIG_SPINLOCK_SLEEP = y
引起bug并复印信息
BUG()和BUG_ON()
一些内核调用可以拿来便捷标记bug,提供断定并输出信息。最常用的两个是BUG()和BUG_ON()。定义在中:
#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,致使栈的回溯和错误消息的复印。
※可以把这两个调用当成断定使用,如:BUG_ON(bad_thing);
WARN(x)和WARN_ON(x)
而WARN_ON则是调用dump_stack,复印堆栈信息,不会OOPS。定义在中:
#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
dump_stack()
有些时侯,只须要在终端上复印一下栈的回溯信息来帮助你调试。这时可以使用dump_stack()。这个函数只是在终端上复印寄存器上下文和函数的跟踪线索。
if (!debug_check) { printk(KERN_DEBUG “provide some information…/n”); dump_stack(); }
printk()
内核提供的低格复印函数。
printk函数的强壮性
强壮性是printk最容易被接受的一个特质,几乎在任何地方,任何时侯内核都可以调用它(中断上下文、进程上下文、持有锁时、多处理器处理时等)。
printk函数脆弱之处
在系统启动过程中,终端初始化之前,在个别地方是不能调用的。假如真的须要调试系统启动过程最开始的地方,有以下方式可以使用:
使用并口调试,将调试信息输出到其他终端设备。
使用early_printk(),该函数在系统启动早期就有复印能力。但它只支持部份硬件体系。
LOG等级
printk和printf一个主要的区别就是后者可以指定一个LOG等级。内核按照这个等级来判定是否在终端上复印消息。内核把比指定等级高的所有消息显示在终端。
可以使用下边的形式指定一个LOG级别:
printk(KERN_CRIT “Hello, world! ”);
注意,第一个参数并不一个真正的参数,由于其中没有用于分隔级别(KERN_CRIT)和格式字符的冒号(,)。KERN_CRIT本身只是一个普通的字符串(事实上,它表示的是字符串"";表1列举了完整的日志级别清单)。作为预处理程序的一部份,C会手动地使用一个名为字符串串联的功能将这两个字符串组合在一起。组合的结果是将日志级别和用户指定的格式字符串包含在一个字符串中。
内核使用这个指定LOG级别与当前终端LOG等级console_loglevel来决定是不是向终端复印。下边是可使用的LOG等级:
#define KERN_EMERG "" /* system is unusable */#define KERN_ALERT "" /* action must be taken immediately */ #define KERN_CRIT "" /* critical conditions */#define KERN_ERR "" /* error conditions */#define KERN_WARNING "" /* warning conditions */#define KERN_NOTICE "" /* normal but significant condition */#define KERN_INFO "" /* informational */#define KERN_DEBUG "" /* debug-level messages */#define KERN_DEFAULT "" /* Use the default kernel loglevel */
注意,假若调用者未将日志级别提供给printk,这么系统都会使用默认值KERN_WARNING""(表示只有KERN_WARNING级别以上的日志消息会被记录)。因为默认值存在变化,所以在使用时最好指定LOG级别。有LOG级别的一个用处就是我们可以选择性的输出LOG。例如平常我们只须要复印KERN_WARNING级别以上的关键性LOG,然而调试的时侯,我们可以选择复印KERN_DEBUG等以上的详尽LOG。而这种都不须要我们更改代码linux认证,只须要通过命令更改默认日志输出级别:
mtj@ubuntu :~$ cat /proc/sys/kernel/printk4 4 1 7mtj@ubuntu :~$ cat /proc/sys/kernel/printk_delay0mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit5mtj@ubuntu :~$ cat /proc/sys/kernel/printk_ratelimit_burst10
第一项定义了printkAPI当前使用的日志级别。这种日志级别表示了控制台的日志级别、默认消息日志级别、最小控制台日志级别和默认控制台日志级别。printk_delay值表示的是printk消息之间的延后微秒数(用于增强个别场景的可读性)。注意,这儿它的值为0,而它是不可以通过的/proc设置的。printk_ratelimit定义了消息之间容许的最小时间间隔(当前定义为每5秒内的某个内核消息数)。消息数目是由printk_ratelimit_burst定义的(当前定义为10)。倘若您拥有一个非即将内核而又使用有带宽限制的控制台设备(如通过并口),这么这特别有用。注意,在内核中,速率限制是由调用者控制的,而不是在printk中实现的。假如一个printk假如用户要求进行速率限制,这么该用户就须要调用printk_ratelimit函数。
记录缓冲区
内核消息都被保存在一个LOG_BUF_LEN大小的环型队列中。
关于LOG_BUF_LEN定义:
#define__LOG_BUF_LEN(1/dynamic_debug/control
参考:
1内核日志及printk结构探讨--TekkamanNinja
2内核日志:API及实现
3printk实现剖析
4dynamic-debug-howto.txt
显存调试工具
MEMWATCH
MEMWATCH由JohanLindh编撰,是一个开放源代码C语言显存错误检查工具,您可以自己下载它。只要在代码中添加一个头文件并在gcc句子中定义了MEMWATCH以后,您就可以跟踪程序中的显存泄露和错误了。MEMWATCH支持ANSIC,它提供结果日志纪录,能测量双重释放(double-free)、错误释放(erroneousfree)、没有释放的显存(unfreedmemory)、溢出和下溢等等。
清单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的memwatch.c。
下面是一个 makefile 示例:
test1
gcc -DMEMWATCH -DMW_STDIO test1.c memwatchc -o test1
当您运行test1程序后,它会生成一个关于泄露的显存的报告。清单2展示了示例memwatch.log输出文件。
清单2.test1memwatch.log文件
MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh...double-free: test1.c(15), 0x80517b4 was freed from test1.c(14)...unfreed: 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
MEMWATCH为您显示真正引起问题出现的信息。倘若您释放一个早已释放过的表针,它会告诉您。对于没有释放的显存也一样。日志结尾部份显示统计信息,包括窃取了多少显存,使用了多少显存,以及总共分配了多少显存。
YAMD
YAMD软件包由NateEldredge编撰,可以查找C++和C++动态的、与显存分配有关的问题。在撰写本文时,YAMD的最新版本为0.32。请下载yamd-0.32.tar.gz。执行make命令来建立程序;之后执行makeinstall命令安装程序并设置工具。
一旦您下载了YAMD以后,请在test1.c上使用它。请删掉#includememwatch.h并对makefile进行如下小小的更改:
使用YAMD的test1
gcc-gtest1.c-otest1
清单3展示了来自test1上的YAMD的输出。
清单3.使用YAMD的test1输出
YAMD version 0.32Executable: /usr/src/test/yamd-0.32/test1...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal deallocation of this blockAddress 0x40025e00, size 512...ERROR: Multiple freeing Atfree of pointer already freedAddress 0x40025e00, size 512...WARNING: Memory leakAddress 0x40028e00, size 512WARNING: Total memory leaks:1 unfreed allocations totaling 512 bytes*** Finished at Tue ... 10:07:15 2002Allocated a grand total of 1024 bytes 2 allocationsAverage of 512 bytes per allocationMax bytes allocated at one time: 102424 K alloced internally / 12 K mapped now / 8 K maxVirtual program size is 1416 KEnd.
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循环中有“越界(out-of-bounds)”的情况。
清单5.使用YAMD的test2输出
Running /usr/src/test/test2/test2Temp output to /tmp/yamd-out.1243*********./run-yamd: line 101: 1248 Segmentation fault (core dumped)YAMD version 0.32Starting run: /usr/src/test/test2/test2Executable: /usr/src/test/test2/test2Virtual program size is 1380 K...INFO: Normal allocation of this blockAddress 0x40025e00, size 512...INFO: Normal allocation of this blockAddress 0x40028e00, size 512...INFO: Normal allocation of this blockAddress 0x4002be00, size 512ERROR: Crash...Tried to write address 0x4002c000Seems 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.
MEMWATCH和YAMD都是很有用的调试工具,不过它们的使用方式有所不同。对于MEMWATCH,您须要添加包含文件memwatch.h并打开两个编译时间标记。对于链接(link)句子,YAMD只须要-g选项。
ElectricFence
多数Linux分发版包含一个ElectricFence包,不过您也可以选择下载它。ElectricFence是一个由BrucePerens编撰的malloc()调试库。它就在您分配显存后分配受保护的显存。假如存在fencepost错误(超过链表末尾运行),程序都会形成保护错误,并立刻结束。通过结合ElectricFence和gdb,您可以精确地跟踪到哪一行企图访问受保护显存。ElectricFence的另一个功能就是才能测量显存泄露。
strace
strace命令是一种强悍的工具,它还能显示所有由用户空间程序发出的系统调用。strace显示这种调用的参数并返回符号方式的值。strace从内核接收信息,但是不须要以任何特殊的方法来建立内核。将跟踪信息发送到应用程序及内核开发者都很有用。在清单6中,分区的一种格式有错误,清单显示了strace的开头部份,内容是关于调出创建文件系统操作(mkfs)的。strace确定那个调用造成问题出现。
清单6.mkfs上strace的开头部份
execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], &...open("/dev/test1", O_RDWR|O_LARGEFILE) = 4stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0ioctl(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 ) = 98stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)write(2, "mkfs.jfs: can't determine device"..., ..._exit(1) = ?
清单6显示ioctl调用造成拿来低格分区的mkfs程序失败。ioctlBLKGETSIZE64失败。(BLKGET-SIZE64在调用ioctl的源代码中定义。)BLKGETSIZE64ioctl将被添加到Linux中所有的设备,而在这儿,逻辑卷管理器还不支持它。为此,假如BLKGETSIZE64ioctl调用失败,mkfs代码将改为调用较早的ioctl调用;这促使mkfs适用于逻辑卷管理器。
OOPS
OOPS(俗称Panic)消息包含系统错误的细节,如CPU寄存器的内容等。是内核告知用户有不幸发生的最常用的方法。
内核只能发布OOPS,这个过程包括向终端上输出错误消息,输出寄存器保存的信息,并输出可供跟踪的回溯线索。一般,发送完OOPS以后,内核会处于一种不稳定的状态。
OOPS的形成有好多可能缘由,其中包括显存访问越界或非法的指令等。
※作为内核的开发者,必将将会时常处理OOPS。
※OOPS中包含的重要信息,对所有体系结构的机器都是完全相同的:寄存器上下文和回溯线索(回溯线索显示了造成错误发生的函数调用链)。
ksymoops
在Linux中,调试系统崩溃的传统方式是剖析在发生崩溃时发送到系统控制台的Oops消息。一旦您把握了细节,就可以将消息发送到ksymoops使用程序,它将企图将代码转换为指令并将堆栈值映射到内核符号。
※如:回溯线索中的地址,会通过ksymoops转化成名称可见的函数名。
ksymoops须要几项内容:Oops消息输出、来自正在运行的内核的System.map文件,还有/proc/ksyms、vmlinux和/proc/modules。
关于怎么使用ksymoops,内核源代码/usr/src/linux/Documentation/oops-tracing.txt中或ksymoops指南页上有完整的说明可以参考。Ksymoops返回编代码部份,强调发生错误的指令,并显示一个跟踪部份表明代码怎样被调用。
首先,将Oops消息保存在一个文件中便于通过ksymoops使用程序运行它。清单7显示了由安装JFS文件系统的mount命令创建的Oops消息。
清单7.ksymoops处理后的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 Code; c01588fc 00000000 :Code; c01588fc <===== 0: 8b 2d 00 00 00 00 mov 0x0,%ebp 6: 55 push %ebp
接出来,您要确定jfs_mount中的哪一行代码导致了这个问题。Oops消息告诉我们问题是由坐落偏斜地址3c的指令造成的。做这件事的办法之一就是对jfs_mount.o文件使用objdump使用程序,之后查看偏斜地址3c。Objdump拿来反汇编模块函数,瞧瞧您的C源代码会形成哪些汇编指令。清单8显示了使用objdump后您将见到的内容,接着,我们查看jfs_mount的C代码,可以看见空值是第109行导致的。偏斜地址3c之所以如此重要,是由于Oops消息将该处标示为造成问题的位置。
清单8.jfs_mount汇编程序清单
109 printk("%d ",*ptr);objdump jfs_mount.ojfs_mount.o: file format elf32-i386Disassembly 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
kallsyms
开发版2.5内核引入了kallsyms特点,它可以通过定义CONFIG_KALLSYMS编译选项启用。该选项可以载入内核镜像所对应的显存地址的符号名称(即函数名),所以内核可以复印解码以后的跟踪线索。相应,解码OOPS也不再须要System.map和ksymoops工具了。另外,这样做,会使内核变大些查看linux系统内核,由于地址对应符号名称必须仍然留驻在内核所在显存上。
#cat /proc/kallsyms c0100240 T _stext c0100240 t run_init_process c0100240 T stext c0100269 t init …
Kdump
哪些是kexec?
Kexec是实现kdump机制的关键,它包括2一是组成部份:一是内核空间的系统调用kexec_load,负责在生产内核(productionkernel或firstkernel)启动时将捕获内核(capturekernel或sencondkernel)加载到指定地址。二是用户空间的工具kexec-tools,他将捕获内核的地址传递给生产内核,因而在系统崩溃的时侯才能找到捕获内核的地址并运行。没有kexec就没有kdump。先有kexec实现了在一个内核中可以启动另一个内核,才让kdump有了用武之地。kexec原先的目的是为了节约时间kernel开发人员重启系统的时间,谁能想到这个“偷懒”的技术却蕴育了最成功的显存转存机制呢?
哪些是kdump?
Kdump的概念出现在2005左右,是迄今为止最可靠的内核转存机制,早已被主要的linux™厂商选用。kdump是一种先进的基于kexec的内核崩溃轮询机制。当系统崩溃时,kdump使用kexec启动到第二个内核。第二个内核一般称作捕获内核,以很小的显存启动以捕获轮询镜像。第一个内核保留了显存的一部份给第二个内核启动用。因为kdump借助kexec启动捕获内核,绕开了BIOS,所以第一个内核的显存得以保留。这是内核崩溃轮询的本质。
kdump须要两个不同目的的内核,生产内核和捕获内核。生产内核是捕获内核服务的对象。捕获内核会在生产内核崩溃时启动上去,与相应的ramdisk一起成立一个微环境,用以对生产内核下的显存进行搜集和转存。
怎样使用kdump
建立系统和dump-capture内核,此操作有2种方法可选:
1)建立一个单独的自定义轮询捕获内核以捕获内核轮询;
2)或则将系统内核本身作为轮询捕获内核,这就不须要建立一个单独的轮询捕获内核。
方式(2)只能用于可支持可重定位内核的体系结构上;目前i386,x86_64,ppc64和ia64体系结构支持可重定位内核。建立一个可重定位内核促使不须要建立第二个内核就可以捕获轮询。并且可能有时想建立一个自定义轮询捕获内核以满足特定要求。
怎样访问捕获显存
在内核崩溃之前所有关于核心映像的必要信息都用ELF格式编码并储存在保留的显存区域中。ELF头所在的化学地址被作为命令行参数(fcorehdr=)传递给新启动的轮询内核。
在i386体系结构上,启动的时侯须要使用化学显存开始的640K,而不管操作系统内核转载在何处。因而,这个640K的区域在重新启动第二个内核的时侯由kexec备份。
在第二个内核中,“前一个系统的显存”可以通过两种方法访问:
1)通过/dev/oldmem这个设备插口。
一个“捕捉”设备可以使用“raw”(裸的)形式“读”这个设备文件并写出到文件。这是关于显存的“裸”的数据存贮,同时这种剖析/捕捉工具应当足够“智能”从而可以晓得从那里可以得到正确的信息。ELF文件头(通过命令行参数传递过来的elfcorehdr)可能会有帮助。
2)通过/proc/vmcore。
这个方法是将轮询输出为一个ELF格式的文件,但是可以使用一些文件拷贝命令(例如cp,scp等)将信息读下来。同时,gdb可以在得到的轮询文件上做一些调试(有限的)。这些方法保证了显存中的页面都以正确的途径被保存(注意显存开始的640K被重新映射了)。
kdump的优势
1)高可靠性
崩溃轮询数据可从一个新启动内核的上下文中获取,而不是从早已崩溃内核的上下文。
2)多版本支持
LKCD(LinuxKernelCrashDump),netdump,diskdump已被列入LDPs(LinuxDocumen-tationProject)内核。SUSE和RedHat都对kdump有技术支持。
配置kdump
安装软件包和实用程序
Kdump用到的各类工具都在kexec-tools中。kernel-debuginfo则是拿来剖析vmcore文件。从rhel5开始,kexec-tools已被默认安装在发行版。而novell也在sles10发行版中把kdump集成进来。所以假如使用的是rhel5和sles10以后的发行版,那就省去了安装kexec-tools的步骤。而倘若须要调试kdump生成的vmcore文件,则须要自动安装kernel-debuginfo包。检测安装包操作:
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
系统内核设置选项和轮询捕获内核配置选择在《使用Crash工具剖析Linuxdump文件》一文中已有说明,在此不再赘言。仅列举内核引导参数设置以及配置文件设置。
1)更改内核引导参数,为启动捕获内核预留显存
通过下边的方式来配置kdump使用的显存大小。添加启动参数"crashkernel=Y@X",这儿,Y是为kdump捕捉内核保留的显存,X是保留部份显存的开始位置。
对于i386和x86_64,编辑/etc/grub.conf,在内核行的最后添加"crashkernel=128M"。
对于ppc64,在/etc/yaboot.conf最后添加"crashkernel=128M"。
在ia64,编辑/etc/elilo.conf,添加"crashkernel=256M"到内核行。
2)kdump配置文件
kdump的配置文件是/etc/kdump.conf(RHEL6.2);/etc/sysconfig/kdump(SLES11sp2)。每位文件腹部都有选项说明,可以按照使用需求设置相应的选项。
启动kdump服务
在设置了预留显存后,须要重启机器,否则kdump是不可使用的。启动kdump服务:
Rhel6.2:
# chkconfig kdump on # service kdump status Kdump is operational # service kdump start
SLES11SP2:
# chkconfig boot.kdump on # service boot.kdump start
测试配置是否有效
可以通过kexec加载内核镜像,让系统打算好去捕获一个崩溃时形成的vmcore。可以通过sysrq强制系统崩溃。
# echo c > /proc/sysrq-trigger
这导致内核崩溃,如配置有效,系统将重启步入kdump内核,当系统进程步入到启动kdump服务的点时,vmcore将会拷贝到你在kdump配置文件中设置的位置。RHEL的缺省目录是:/var/crash;SLES的缺省目录是:/var/log/dump。之后系统重启步入到正常的内核。一旦回复到正常的内核,就可以在上述的目录下发觉vmcore文件,即显存存贮文件。可以使用之前安装的kernel-debuginfo中的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
载入“转储捕获”内核
须要引导系统内核时,可使用如下步骤和命令载入“转储捕获”内核:
kexec -p --initrd=for-dump-capture-kernel> --args-linux --append="root= init 1 irqpoll"
装载轮询捕捉内核的注意事项:
轮询捕捉内核应该是一个vmlinux格式的映像(即是一个未压缩的ELF映像文件),而不能是bzImage格式;
默认情况下,ELF文件头采用ELF64格式储存以支持这些拥有超过4GB显存的系统。并且可以指定“--elf32-core-headers”标志以强制使用ELF32格式的ELF文件头。这个标志是有必要注意的,一个重要的缘由就是:当前版本的GDB不能在一个32位系统上打开一个使用ELF64格式的vmcore文件。ELF32格式的文件头不能使用在一个“没有数学地址扩充”(non-PAE)的系统上(即:多于4GB显存的系统);
一个“irqpoll”的启动参数可以减少因为在“转储捕获内核”中使用了“共享中断”技术而造成出现驱动初始化失败此类情况发生的机率;
必须指定,指定的格式是和要使用根设备的名子。具体可以查看mount命令的输出;“init1”这个命令将启动“转储捕捉内核”到一个没有网路支持的单用户模式。假如你希望有网路支持,这么使用“init3”。
杂记
Kdump是一个强悍的、灵活的内核轮询机制查看linux系统内核,才能在生产内核上下文中执行捕获内核是十分有价值的。本文仅介绍在RHEL6.2和SLES11中怎样配置kdump。望抛砖引玉,对阅读本文的读者有益。