揭开Linux系统内核调试器神秘面纱(上)

来源:http://searchserver.techtarget 作者:论坛编辑
  调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。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]

 

(未完,待续)


时间:2007-06-21 20:31 来源:http://searchserver.techtarget 作者:论坛编辑 原文链接

好文,顶一下
(2)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量