内核中的调试支持
编译内核
1.获取内核源码,解压至/usr/src
# tar xf linux-3.13.5.tar.xz -C /usr/src
# ln -sv /usr/src/linux-3.13.5 /usr/src/linux
2.配置内核特性(选择一种方法就可以了)
make config:遍历选择所要编译的内核特性
make allyesconfig:配置所有可编译的内核特性
make allnoconfig:并不是所有的都不编译
make menuconfig:这种就是打开一个文件窗口选择菜单
make kconfig(KDE桌面环境下,并且安装了qt开发环境)
make gconfig(Gnome桌面环境,并且安装gtk开发环境)
3.编译内核
# make [-j #] : #号最多为CPU物理核心总数的两倍,这样会快点哦
4.安装内核模块
# make modules_install
5.安装内核
# make install
调试选项(“kernel hacking”)
CONFIG_DEBUG_KERNEL
该选项只是使其它的调试选项可用;它必须被打开,但是,它自己不打开任何特性。
CONFIG_DEBUG_SLAB
这个选项打开对许多种类型的内核内存分配函数的检查;激活这些检查,就可能发现许多内存越界和未初始化错误。每一个分配的内存字节在传递给调用者之前设置为0xa5,释放之后设置为0x6b。如果你曾经看到这些“毒剂”字符出现在你的驱动程序的输出中(或者经常出现在一个oops列表中),你就能确切地知道该去找寻什么样的错误。当调试选项被激活的时候,内核在分配内存对象之前和之后都在其中放置特殊的监视值;如果这些值曾经被修改,内核就知道有内存分配已经越界,它就会不客气地提出警告。许多其它不显眼的错误检查也被激活。
CONFIG_DEBUG_PAGEALLOC
页面被释放时是整个的从内核地址空间中移除的。该选项显著地降低了速度,但它也能迅速指出特定类型的内存崩溃错误。
CONFIG_DEBUG_SPINLOCK
激活该选项,内核捕捉对未初始化自旋锁的操作和各种其它错误(比如解锁同一个锁两次)。
CONFIG_DEBUG_SPINLOCK_SLEEP
该选项能执行对持有自旋锁的时候试图睡眠的检查。事实上,如果你调用一个可能潜在地睡眠的函数,它会提出警告,即使调用的函数不可能睡眠。
CONFIG_INIT_DEBUG
有__init(或__initdata)标记的项在系统初始化或模块加载时被丢弃。该选项使你能对在初始化完成之后试图访问初始化时的内存的代码进行检查。
CONFIG_DEBUG_INFO
该选项使得内核编译时包含完整的调试信息。如果你想使用gdb来调试内核,你就需要这些信息。如果你计划使用gdb,你可能也想激活。
CONFIG_MAGIC_SYSRQ
激活“神奇的系统请求键”。
CONFIG_DEBUG_STACKOVERFLOW
CONFIG_DEBUG_STACK_USAGE
这些选项可以帮助追踪内核的栈溢出。栈溢出的确定迹象是一个没有任何合理的回溯线索。第一个选项给内核添加明确的溢出检查;第二个让内核监视栈使用并通过神奇的系统请求键提供一些可用的数据。
CONFIG_KALLSYMS
该选项(位于“General setup/Standard features”)使得内核符号信息编译到内核中;它默认是激活的。符号信息用来调试上下文;没有它,oops列表仅能让你以十六进制的方式回溯内核,这种方法通常没有什么用处。
CONFIG_IKCONFIG
CONFIG_IKCONFIG_PROC
这些选项(在“General setup”菜单下)使完整的内核配置状态编译进内核并通过/proc可用。大多数的内核开发者了解他们使用的配置,所以不需要这个选项(它使内核变得更大)。如果你尝试调试其它人编译的内核中的问题,它会非常有帮助。
CONFIG_ACPI_DEBUG
位于“Power management/ACPI”中。该选项打开详细的ACPI(Advanced Configuration and Power Interface)调试信息,当你怀疑出现ACPI相关的问题时将非常有用。
CONFIG_DEBUG_DRIVER
位于“Device drivers”。打开驱动程序核心的调试信息,跟踪底层支持代码产生的问题时很有用。
CONFIG_SCSI_CONSTANTS
该选项位于“Device drivers/SCSI device support”。它可以建立详细的SCSI错误信息。如果你编写一个SCSI驱动程序,你可能想激活这个选项。
CONFIG_INPUT_EVBUG
该选项(位于“Device drivers/Input device support”)打开对输入事件的详细记录。如果你在为一个输入设备编写驱动程序,这个选项可能非常有帮助。这个选项有潜在的安全问题,因为它记录你输入的所有信息,包括你的密码。
CONFIG_PROFILING
该选项在“Profiling support”中。Profiling通常用于系统性能调节,但是它对跟踪一些内核挂起及相关的错误也非常有用。
通过 打印调试
printk
printk和printf的差别之一:通过附加不同日志级别(loglevel),或者说消息优先级,可以让printk根据这些级别所表示的严重程度对消息进行分类。
通常采用宏来指示日志级别。表示日志级别的宏会展开为一个字符串,在编译时由预处理器将它和消息文本拼接在一起,这也就是优先级和格式字符串之间没有逗号的原因。
printk(KERN_DEBUG "***");
printk(KERN_CRIT "***");
在头文件<linux/kernrl.h>中定义了八种可用的日志级别字符串,严重程度以降序排列:
KERN_EMERG //用于紧急情况信息,通常是那些在系统崩溃之前的信息。
KERN_ALERT //一个必须被立即处理的错误。
KERN_CRIT //临界情况,常常与严重的硬件或软件失败相关。
KERN_ERR //用于报告错误情况;设备驱动程序常常使用KERN_ERR报告一个硬件问题。
KERN_WARNING //对一个有问题的情况提出警告,这些情况并不对系统造成严重的问题。
KERN_NOTICE //一个普通的,不过也有可能需要注意到的情况。许多安全相关的情况都是在这个级别被报告的。
KERN_INFO //提供信息的消息。很多的驱动程序在这个级别打印它们在启动时找到的硬件的信息。
KERN_DEBUG //用于调试信息。
未指定优先级的printk语句采用默认级别时DEFAULT_MESSAGE_LOGLEVEL,这个宏在kernel/printk.c中被指定为一个整数。在2.6.10内核中就是KERN_WARNING。
重定向控制台消息
Linux在控制台的日志记录策略允许你把信息发送到一个指定的虚拟控制台(如果你的控制台是一个文本屏幕)来提供一定的灵活性。默认情况下,“控制台”就是当前的虚拟终端。你可以在任何控制台设备上使用ioctl(TIOCLINUX)来选择一个不同的虚拟终端来接收信息。下面的setconsole程序可以用来选择哪一个控制台接收内核信息;它在misc-progs目录下并且它必须由根用户来运行。
下面是整个的程序代码。你应该在调用它的时候使用一个参数来指定接收信息的控制台数目。
int main(int argc, char **argv)
{
/* 11 is the TIOCLINUX cmd number */
char bytes[2] = {
11, 0};
if (argc == 2)
bytes[1] = atoi(argv[1]); /* the chosen console */
else {
fprintf(stderr, "%s: need a single arg\n", argv[0]);
exit(1);
}
/* use stdin */
if (ioctl(STDIN_FILENO, TIOCLINUX, bytes) < 0) {
fprintf(stderr, "%s: ioctl(stdin, TIOCLINUX): %s\n",
argv[0], strerror(errno));
exit(1);
}
exit(0);
}
setconsole使用了特殊的ioctl命令TIOCLINUX,它是一个Linux特有的实现函数。使用TIOCLINUX,你必须给它传递一个指向字节数组的指针参数。数组的第一个字节指定请求的子命令的编号,后面的字节是子命令特定的。在setconsole中,使用的子命令是11,接着的字节(存储在bytes[1]中)标识虚拟控制台。TIOCLINUX的完整描述可以在内核源文件中的drivers/char/tty_io.c文件中找到。
消息如何被记录
printk函数把信息写入到一个__LOG_BUF_LEN字节长的循环缓冲区中:一个在配置内核的时候选择的介于4KB到1MB之间的值。函数接着唤醒等待信息的所有进程,即那些在syslog系统调用或读取/proc/kmsg时睡眠的进程。这两个记录引擎几乎是相同的,但是请注意从/proc/kmsg读取信息时会消耗记录缓冲区中的数据,然而syslog系统调用能随意地返回记录数据并把它留给其它进程。一般来说,读取/proc文件更简单并且是klogd的默认行为。dmesg命令可以用来查看缓冲区的内容而不冲洗(清除)它的内容;事实上,该命令将缓冲区的所有内容返回到stdout(标准输出)中,不论它是否已经被读取过。
如果循环缓冲区被填满,prink绕回并且在缓冲区开始的地方开始添加新数据,覆盖最老的数据。因此,记录进程失去最老的数据。
开启及关闭消息
定义一个宏,在需要时,这个宏展开为一个printk(或printf)调用:
▪ 每一个打印语句都能通过移除或添加单个的字母到宏的名字中来允许或禁止。
▪ 所有的信息都可以通过在编译之前改变CFLAGS变量的值来禁止。
▪ 相同的打印语句能在内核和用户级的代码中使用,这样驱动程序和测试程序就能以相同的方式来管理额外的信息。
scull.h中这样定义:
#undef PDEBUG /* undef it, just in case */
#ifdef SCULL_DEBUG
# ifdef __KERNEL__
/* This one if debugging is on, and kernel space */
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
# else
/* This one for user space */
# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
# endif
#else
# define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#undef PDEBUGG