扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
KDB 入门指南
调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。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 #patch -p1 这些补丁应该干净利落地加以应用。查找任何以 .rej 结尾的文件。这个扩展名表明这些是失败的补丁。如果内核树没问题,那么补丁的应用就不会有任何问题。
接下来,需要构建内核以支持 KDB。第一步是设置 CONFIG_KDB 选项。使用您喜欢的配置机制(xconfig 和 menuconfig 等)来完成这一步。转到结尾处的“Kernel hacking”部分并选择“Built-in Kernel Debugger support”选项。
您还可以根据自己的偏好选择其它两个选项。选择“Compile the kernel with frame pointers”选项(如果有的话)则设置 CONFIG_FRAME_POINTER 标志。这把产生更好的堆栈回溯,因为帧指针寄存器被用作帧指针而不是通用寄存器。
您还可以选择“KDB off by default”选项。这把设置 CONFIG_KDB_OFF 标志,并且在缺省情况下把关闭 KDB。我们把在后面一节中对此进行详细介绍。
保存配置,然后退出。重新编译内核。建议在构建内核之前执行“make clean”。用常用方式安装内核并引导它。
初始化并设置环境变量
您可以定义把在 KDB 初始化期间执行的 KDB 命令。需要在纯文本文件 kdb_cmds 中定义这些命令,该文件位于 Linux 源代码树(当然是在打了补丁之后)的 KDB 目录中。该文件还可以用来定义设置显示和打印选项的环境变量。文件开头的注释提供了编辑文件方面的帮助。使用这个文件的缺点是,在您更改了文件之后需要重新构建并重新安装内核。
激活 KDB
如果编译期间没有选中 CONFIG_KDB_OFF,那么在缺省情况下 KDB 是活动的。否则,您需要显式地激活它 - 通过在引导期间把 kdb=on 标志传递给内核或者通过在挂装了 /proc 之后执行该工作:
#echo "1" >/proc/sys/kernel/kdb
倒过来执行上述步骤则会取消激活 KDB。也就是说,如果缺省情况下 KDB 是打开的,那么把 kdb=off 标志传递给内核或者执行下面这个操作把会取消激活 KDB:
#echo "0" >/proc/sys/kernel/kdb
我们可以看到 rmqueue() 被 __alloc_pages 调用,后者接下来又被 _alloc_pages 调用,以此类推。
每一帧的第一个双字(double word)指向下一帧,这后面紧跟着调用函数的地址。因此,跟踪堆栈就变成一件轻松的工作了。
go 命令可以有选择地以一个地址作为参数。如果您想在某个特定地址处继续执行,则可以提供该地址作为参数。另一个办法是使用 rm 命令修改指令指针寄存器,然后只要输入 go。如果您想跳过似乎会引起问题的某个特定指令或一组指令,这就会很有用。但是,请注意,该指令使用不慎会造成严重的问题,系统可能会严重崩溃。
您可以利用一个名为 defcmd 的有用命令来定义自己的命令集。例如,每当遇到断点时,您可能希望能同时检查某个特殊变量、检查某些寄存器的内容并转储堆栈。通常,您必须要输入一系列命令,以便能同时执行所有这些工作。defcmd 允许您定义自己的命令,该命令可以包含一个或多个预定义的 KDB 命令。然后只需要用一个命令就可以完成所有这三项工作。其语法如下:
[code:1:6ddc15f4ad][0]kdb> defcmd name "usage" "help"
[0]kdb> [defcmd] type the commands here
[0]kdb> [defcmd] endefcmd [/code:1:6ddc15f4ad]
例如,可以定义一个(简单的)新命令 hari,它显示从地址 0xc000000 开始的一行内存、显示寄存器的内容并转储堆栈:
[code:1:6ddc15f4ad][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 [/code:1:6ddc15f4ad]
该命令的输出会是:
[code:1:6ddc15f4ad][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> [/code:1:6ddc15f4ad]
可以使用 bph 和 bpha 命令(假如体系结构支持使用硬件寄存器)来应用读写断点。这意味着每当从某个特定地址读取数据或将数据写入该地址时,我们都可以对此进行控制。当调试数据/内存毁坏问题时这可能会极其方便,在这种情况中您可以用它来识别毁坏的代码/进程。
示例
[code:1:6ddc15f4ad]每当将四个字节写入地址 0xc0204060 时就进入内核调试器:
[0]kdb> bph 0xc0204060 dataw 4
在读取从 0xc000000 开始的至少两个字节的数据时进入内核调试器:
[0]kdb> bph 0xc000000 datar 2[/code:1:6ddc15f4ad]
[size=18:6ddc15f4ad]结束语[/size:6ddc15f4ad]
对于执行内核调试,KDB 是一个方便的且功能强大的工具。它提供了各种选项,并且使我们能够分析内存内容和数据结构。最妙的是,它不需要用另一台机器来执行调试。
C 语言作为 Linux 系统上标准的编程语言给予了我们对动态内存分配很大的控制权。然而,这种自由可能会导致严重的内存管理问题,而这些问题可能导致程序崩溃或随时间的推移导致性能降级。
内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放)和缓冲区溢出(例如对以前分配到某数组的内存进行写操作)是一些常见的问题,它们可能很难检测到。这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。
MEMWATCH 由 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具,您可以自己下载它(请参阅本文后面部分的参考资料)。只要在代码中添加一个头文件并在 gcc 语句中定义了 MEMWATCH 之后,您就可以跟踪程序中的内存泄漏和错误了。MEMWATCH 支持 ANSI C,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous free)、没有释放的内存(unfreed memory)、溢出和下溢等等。
清单 1. 内存样本(test1.c)
[code:1:ff78191c7b]#include #include #include "memwatch.h"
int main(void)
{
char *ptr1;
char *ptr2;
ptr1 = malloc(512);
ptr2 = malloc(512);
ptr2 = ptr1;
free(ptr2);
free(ptr1);
}[/code:1:ff78191c7b]
清单 1 中的代码将分配两个 512 字节的内存块,然后指向第一个内存块的指针被设定为指向第二个内存块。结果,第二个内存块的地址丢失,从而产生了内存泄漏。
现在我们编译清单 1 的 memwatch.c。下面是一个 makefile 示例:
test1
[code:1:ff78191c7b]gcc -DMEMWATCH -DMW_STDIO test1.c memwatch c -o test1[/code:1:ff78191c7b]
当您运行 test1 程序后,它会生成一个关于泄漏的内存的报告。清单 2 展示了示例 memwatch.log 输出文件。
清单 2. test1 memwatch.log 文件
[code:1:ff78191c7b]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[/code:1:ff78191c7b]
MEMWATCH 为您显示真正导致问题的行。如果您释放一个已经释放过的指针,它会告诉您。对于没有释放的内存也一样。日志结尾部分显示统计信息,包括泄漏了多少内存,使用了多少内存,以及总共分配了多少内存。
[color=blue:ff78191c7b]YAMD[/color:ff78191c7b]
YAMD 软件包由 Nate Eldredge 编写,可以查找 C 和 C++ 中动态的、与内存分配有关的问题。在撰写本文时,YAMD 的最新版本为 0.32。请下载 yamd-0.32.tar.gz(请参阅参考资料)。执行 make 命令来构建程序;然后执行 make install 命令安装程序并设置工具。
一旦您下载了 YAMD 之后,请在 test1.c 上使用它。请删除 #include memwatch.h 并对 makefile 进行如下小小的修改:
使用 YAMD 的 test1
gcc -g test1.c -o test1
清单 3 展示了来自 test1 上的 YAMD 的输出。
清单 3. 使用 YAMD 的 test1 输出
[code:1:ff78191c7b]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.[/code:1:ff78191c7b]
YAMD 显示我们已经释放了内存,而且存在内存泄漏。让我们在清单 4 中另一个样本程序上试试 YAMD。
清单 4. 内存代码(test2.c)
[code:1:ff78191c7b]#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);
}[/code:1:ff78191c7b]
您可以使用下面的命令来启动 YAMD:
[code:1:ff78191c7b]./run-yamd /usr/src/test/test2/test2 [/code:1:ff78191c7b]
清单 5 显示了在样本程序 test2 上使用 YAMD 得到的输出。YAMD 告诉我们在 for 循环中有“越界(out-of-bounds)”的情况。
清单 5. 使用 YAMD 的 test2 输出
[code:1:ff78191c7b]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.[/code:1:ff78191c7b]
MEMWATCH 和 YAMD 都是很有用的调试工具,它们的使用方法有所不同。对于 MEMWATCH,您需要添加包含文件 memwatch.h 并打开两个编译时间标记。对于链接(link)语句,YAMD 只需要 -g 选项。
[color=blue:ff78191c7b]Electric Fence[/color:ff78191c7b]
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者