Linux 内核调试器内幕。
调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。Linux 中的内置内核调试器 KDB 提供了这种功能。在本文中您将了解如何使用 KDB 所提供的功能,以及如何在 Linux 机器上安装和设置 KDB。您还将熟悉 KDB 中可以使用的命令以及设置和显示选项。
Linux 内核调试器(KDB)允许您调试 Linux 内核。这个恰如其名的工具实质上是内核代码的补丁,它允许高手访问内核内存和数据结构。KDB 的主要优点之一就是它不需要用另一台机器进行调试:您可以调试正在运行的内核。
设置一台用于 KDB 的机器需要花费一些工作,因为需要给内核打补丁并进行重新编译。KDB 的用户应当熟悉 Linux 内核的编译(在一定程度上还要熟悉内核内部机理),但是如果您需要编译内核方面的帮助,请参阅本文结尾处的 参考资料一节。
在本文中,我们将从有关下载 KDB 补丁、打补丁、(重新)编译内核以及启动 KDB 方面的信息着手。然后我们将了解 KDB 命令并研究一些较常用的命令。最后,我们将研究一下有关设置和显示选项方面的一些详细信息。
KDB 项目是由 Silicon Graphics 维护的(请参阅 参考资料以获取链接),您需要从它的 FTP 站点下 载与内核版本有关的补丁。(在编写本文时)可用的最新 KDB 版本是 4.2。您将需要下载并应用两个补丁。一个是“公共的”补丁,包含了对通用内核代码的更改,另一个是特定于体系结构的补丁。补丁可作为 bz2 文件获取。例如,在运行 2.4.20 内核的 x86 机器上,您会需要 kdb-v4.2-2.4.20-common-1.bz2 和 kdb-v4.2-2.4.20-i386-1.bz2。
这里所提供的所有示例都是针对 i386 体系结构和 2.4.20 内核的。您将需要根据您的机器和内核版本进行适当的更改。您还需要拥有 root 许可权以执行这些操作。
将文件复制到 /usr/src/linux 目录中并从用 bzip2 压缩的文件解压缩补丁文件:
#bzip2 -d kdb-v4.2-2.4.20-common-1.bz2 #bzip2 -d kdb-v4.2-2.4.20-i386-1.bz2 |
您将获得 kdb-v4.2-2.4.20-common-1 和 kdb-v4.2-2.4-i386-1 文件。
现在,应用这些补丁:
#patch -p1 <kdb-v4.2-2.4.20-common-1 #patch -p1 <kdb-v4.2-2.4.20-i386-1 |
这些补丁应该干净利落地加以应用。查找任何以 .rej 结尾的文件。这个扩展名表明这些是失败的补丁。如果内核树没问题,那么补丁的应用就不会有任何问题。
接下来,需要构建内核以支持 KDB。第一步是设置
您还可以根据自己的偏好选择其它两个选项。选择“Compile the kernel with frame pointers”选项(如果有的话)则设置
保存配置,然后退出。重新编译内核。建议在构建内核之前执行“make clean”。用常用方式安装内核并引导它。
![]() ![]() |
![]()
|
您可以定义将在 KDB 初始化期间执行的 KDB 命令。需要在纯文本文件 kdb_cmds 中定义这些命令,该文件位于 Linux 源代码树(当然是在打了补丁之后)的 KDB 目录中。该文件还可以用来定义设置显示和打印选项的环境变量。文件开头的注释提供了编辑文件方面的帮助。使用这个文件的缺点是,在您更改了文件之后需要重 新构建并重新安装内核。
![]() ![]() |
![]()
|
如果编译期间没有选中
#echo "1" >/proc/sys/kernel/kdb |
倒过来执行上述步骤则会取消激活 KDB。也就是说,如果缺省情况下 KDB 是打开的,那么将
#echo "0" >/proc/sys/kernel/kdb |
在引导期间还可以将另一个标志传递给内核。
调用 KDB 的方式有很多。如果 KDB 处于打开状态,那么只要内核中有紧急情况就自动调用它。按下键盘上的 PAUSE 键将手工调用 KDB。调用 KDB 的另一种方式是通过串行控制台。当然,要做到这一点,需要设置串行控制台(请参阅 参考资料以获取这方面的帮助)并且需要一个从串行控制台进行读取的程序。按键序列 Ctrl-A 将从串行控制台调用 KDB。
![]() ![]() |
![]()
|
KDB 是一个功能非常强大的工具,它允许进行几个操作,比如内存和寄存器修改、应用断点和堆栈跟踪。根据这些,可以将 KDB 命令分成几个类别。下面是有关每一类中最常用命令的详细信息。
这一类别中最常用的命令是
[0]kdb> md 0xc000000 15 |
将内存位置为 0xc000000 上的内容更改为 0×10:
[0]kdb> mm 0xc000000 0x10 |
这一类别中的命令有
示例
显示通用寄存器组:
[0]kdb> rd |
[0]kdb> rm %ebx 0x25 |
常用的断点命令有
示例
对函数
[0]kdb> bp sys_write |
[0]kdb> bl |
[0]kdb> bc 1 |
主要的堆栈跟踪命令有
- D:不可中断状态
- R:正运行
- S:可中断休眠
- T:已跟踪或已停止
- Z:僵死
- U:不可运行
这类命令中的每一个都会打印出一大堆信息。请查阅下面的 参考资料以获取这些字段的详细文档。
示例
跟踪当前活动线程的堆栈:
[0]kdb> bt |
[0]kdb> btp 575 |
下面是在内核调试过程中非常有用的其它几个 KDB 命令。
示例
反汇编从例程 schedule 开始的指令。所显示的行数取决于环境变量
[0]kdb> id schedule |
执行指令直到它遇到分支转移条件(在本例中为指令
[0]kdb> ssb 0xc0105355 default_idle+0x25: cli 0xc0105356 default_idle+0x26: mov 0x14(%edx),%eax 0xc0105359 default_idle+0x29: test %eax, %eax 0xc010535b default_idle+0x2b: jne 0xc0105361 default_idle+0x31 |
![]() ![]() |
![]()
|
调试一个问题涉及到:使用调试器(或任何其它工具)找到问题的根源以及使用源代码来跟踪导致问题的根源。单单使用源代码来确定问题是极其困难的,只有老练 的内核黑客才有可能做得到。相反,大多数的新手往往要过多地依靠调试器来修正错误。这种方法可能会产生不正确的问题解决方案。我们担心的是这种方法只会修 正表面症状而不能解决真正的问题。此类错误的典型示例是添加错误处理代码以处理 NULL 指针或错误的引用,却没有查出无效引用的真正原因。
结合研究代码和使用调试工具这两种方法是识别和修正问题的最佳方案。
调试器的主要用途是找到错误的位置、确认症状(在某些情况下还有起因)、确定变量的值,以及确定程序是如何出现这种情况的(即,建立调用堆栈)。有经验的黑客会知道对于某种特定的问题应使用哪一个调试器,并且能迅速地根据调试获取必要的信息,然后继续分析代码以识别起因。
因此,这里为您介绍了一些技巧,以便您能使用 KDB 快速地取得上述结果。当然,要记住,调试的速度和精确度来自经验、实践和良好的系统知识(硬件和内核内部机理等)。
在 KDB 中,在提示处输入地址将返回与之最为匹配的符号。这在堆栈分析以及确定全局数据的地址/值和函数地址方面极其有用。同样,输入符号名则返回其虚拟地址。
示例
表明函数
[0]kdb> 0xc013db4c 0xc013db4c = 0xc013db4c (sys_read) |
同样,
同样,表明
[0]kdb> sys_write sys_write = 0xc013dcc8 (sys_write) |
这些有助于在分析堆栈时找到全局数据和函数地址。
在编译带 KDB 的内核时,只要
例如,在函数
[0]kdb> md %ebp 0xc74c9f38 c74c9f60 c0136c40 000001f0 00000000 0xc74c9f48 08053328 c0425238 c04253a8 00000000 0xc74c9f58 000001f0 00000246 c74c9f6c c0136a25 0xc74c9f68 c74c8000 c74c9f74 c0136d6d c74c9fbc 0xc74c9f78 c014fe45 c74c8000 00000000 08053328 [0]kdb> 0xc0136c40 0xc0136c40 = 0xc0136c40 (__alloc_pages +0x44) [0]kdb> 0xc0136a25 0xc0136a25 = 0xc0136a25 (_alloc_pages +0x19) [0]kdb> 0xc0136d6d 0xc0136d6d = 0xc0136d6d (__get_free_pages +0xd) |
我们可以看到
每一帧的第一个双字(double word)指向下一帧,这后面紧跟着调用函数的地址。因此,跟踪堆栈就变成一件轻松的工作了。
您可以利用一个名为
[0]kdb> defcmd name "usage" "help" [0]kdb> [defcmd] type the commands here [0]kdb> [defcmd] endefcmd |
例如,可以定义一个(简单的)新命令
[0]kdb> defcmd hari "" "no arguments needed" [0]kdb> [defcmd] md 0xc000000 1 [0]kdb> [defcmd] rd [0]kdb> [defcmd] md %ebp 1 [0]kdb> [defcmd] endefcmd |
该命令的输出会是:
[0]kdb> hari [hari]kdb> md 0xc000000 1 0xc000000 00000001 f000e816 f000e2c3 f000e816 [hari]kdb> rd eax = 0x00000000 ebx = 0xc0105330 ecx = 0xc0466000 edx = 0xc0466000 .... ... [hari]kdb> md %ebp 1 0xc0467fbc c0467fd0 c01053d2 00000002 000a0200 [0]kdb> |
可以使用
示例
每当将四个字节写入地址 0xc0204060 时就进入内核调试器:
[0]kdb> bph 0xc0204060 dataw 4 |
在读取从 0xc000000 开始的至少两个字节的数据时进入内核调试器:
[0]kdb> bph 0xc000000 datar 2 |
![]() ![]() |
![]()
|
对于执行内核调试,KDB 是一个方便的且功能强大的工具。它提供了各种选项,并且使我们能够分析内存内容和数据结构。最妙的是,它不需要用另一台机器来执行调试。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 请在 Documentation/kdb 目录中查找 KDB 手册页。
- 有关设置串行控制台的信息,请查找 Documentation 目录中的 serial-console.txt。
- 请在 SGI 的内核调试器项目网站上 下载 KDB。
- 有关几个基于方案的 Linux 调试技术的概述,请阅读“ 掌握 Linux 调试技术”( developerWorks,2002 年 8 月)。
- 教程“ 编译 Linux 内核”( developerWorks,2000 年 8 月)让您完整地了解配置、编译和安装内核的过程。
- IBM AIX 用户可以在 KDB Kernel Debugger and Command页面上获取有关用于 AIX 的 KDB 的使用帮助。
- 那些寻求有关调试 OS/2 信息的读者应该阅读 IBM 红皮书 The OS/2 Debugging Handbook(共四卷)的 第 II 卷。
- 在 developerWorksLinux 专区中查找更多 针对 Linux 开发人员的参考资料。