这些补丁应该干净利落地加以应用。
查找任何以.rej结尾的文件。
这个扩展名表明这些是失败的补丁。
如果内核树没问题,那么补丁的应用就不会有任何问题。
接下来,需要构建内核以支持KDB。
第一步是设置CONFIG_KDB选项。
使用您喜欢的配置机制(xconfig和menuconfig等)来完成这一步。
转到结尾处的“Kernelhacking”部分并选择“Built-inKernelDebuggersupport”选项。
您还可以根据自己的偏好选择其它两个选项。
选择“Compilethekernelwithframepointers”选项(如果有的话)则设置CONFIG_FRAME_POINTER标志。
这将产生更好的堆栈回溯,因为帧指针寄存器被用作帧指针而不是通用寄存器。
您还可以选择“KDBoffbydefault”选项。
这将设置CONFIG_KDB_OFF标志,并且在缺省情况下将关闭KDB。
我们将在后面一节中对此进行详细介绍。
保存配置,然后退出。
重新编译内核。
建议在构建内核之前执行“makeclean”。
用常用方式安装内核并引导它。
初始化并设置环境变量
您可以定义将在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
在引导期间还可以将另一个标志传递给内核。
kdb=early标志将导致在引导过程的初始阶段就把控制权传递给KDB。
如果您需要在引导过程初始阶段进行调试,那么这将有所帮助。
调用KDB的方式有很多。
如果KDB处于打开状态,那么只要内核中有紧急情况就自动调用它。
按下键盘上的PAUSE键将手工调用KDB。
调用KDB的另一种方式是通过串行控制台。
当然,要做到这一点,需要设置串行控制台(请参阅参考资料以获取这方面的帮助)并且需要一个从串行控制台进行读取的程序。
按键序列Ctrl-A将从串行控制台调用KDB。
KDB命令
KDB是一个功能非常强大的工具,它允许进行几个操作,比如内存和寄存器修改、应用断点和堆栈跟踪。
根据这些,可以将KDB命令分成几个类别。
下面是有关每一类中最常用命令的详细信息。
内存显示和修改
这一类别中最常用的命令是md、mdr、mm和mmW。
md命令以一个地址/符号和行计数为参数,显示从该地址开始的line-count行的内存。
如果没有指定line-count,那么就使用环境变量所指定的缺省值。
如果没有指定地址,那么md就从上一次打印的地址继续。
地址打印在开头,字符转换打印在结尾。
mdr命令带有地址/符号以及字节计数,显示从指定的地址开始的byte-count字节数的初始内存内容。
它本质上和md一样,但是它不显示起始地址并且不在结尾显示字符转换。
mdr命令较少使用。
mm命令修改内存内容。
它以地址/符号和新内容作为参数,用new-contents替换地址处的内容。
mmW命令更改从地址开始的W个字节。
请注意,mm更改一个机器字。
示例
显示从0xc000000开始的15行内存:
[0]kdb>md0xc00000015
将内存位置为0xc000000上的内容更改为0x10:
[0]kdb>mm0xc0000000x10
寄存器显示和修改
这一类别中的命令有rd、rm和ef。
rd命令(不带任何参数)显示处理器寄存器的内容。
它可以有选择地带三个参数。
如果传递了c参数,则rd显示处理器的控制寄存器;如果带有d参数,那么它就显示调试寄存器;如果带有u参数,则显示上一次进入内核的当前任务的寄存器组。
rm命令修改寄存器的内容。
它以寄存器名称和new-contents作为参数,用new-contents修改寄存器。
寄存器名称与特定的体系结构有关。
目前,不能修改控制寄存器。
ef命令以一个地址作为参数,它显示指定地址处的异常帧。
示例
显示通用寄存器组:
[0]kdb>rd
将寄存器ebx的内容设置成0x25:
[0]kdb>rm%ebx0x25
断点
常用的断点命令有bp、bc、bd、be和bl。
bp命令以一个地址/符号作为参数,它在地址处应用断点。
当遇到该断点时则停止执行并将控制权交予KDB。
该命令有几个有用的变体。
bpa命令对SMP系统中的所有处理器应用断点。
bph命令强制在支持硬件寄存器的系统上使用它。
bpha命令类似于bpa命令,差别在于它强制使用硬件寄存器。
bd命令禁用特殊断点。
它接收断点号作为参数。
该命令不是从断点表中除去断点,而只是禁用它。
断点号从0开始,根据可用性顺序分配给断点。
be命令启用断点。
该命令的参数也是断点号。
bl命令列出当前的断点集。
它包含了启用的和禁用的断点。
bc命令从断点表中除去断点。
它以具体的断点号或*作为参数,在后一种情况下它将除去所有断点。
示例
对函数sys_write()设置断点:
[0]kdb>bpsys_write
列出断点表中的所有断点:
[0]kdb>bl
清除断点号1:
[0]kdb>bc1
堆栈跟踪
主要的堆栈跟踪命令有bt、btp、btc和bta。
bt命令设法提供有关当前线程的堆栈的信息。
它可以有选择地将堆栈帧地址作为参数。
如果没有提供地址,那么它采用当前寄存器来回溯堆栈。
否则,它假定所提供的地址是有效的堆栈帧起始地址并设法进行回溯。
如果内核编译期间设置了CONFIG_FRAME_POINTER选项,那么就用帧指针寄存器来维护堆栈,从而就可以正确地执行堆栈回溯。
如果没有设置CONFIG_FRAME_POINTER,那么bt命令可能会产生错误的结果。
btp命令将进程标识作为参数,并对这个特定进程进行堆栈回溯。
btc命令对每个活动CPU上正在运行的进程执行堆栈回溯。
它从第一个活动CPU开始执行bt,然后切换到下一个活动CPU,以此类推。
bta命令对处于某种特定状态的所有进程执行回溯。
若不带任何参数,它就对所有进程执行回溯。
可以有选择地将各种参数传递给该命令。
将根据参数处理处于特定状态的进程。
选项以及相应的状态如下:
∙D:
不可中断状态
∙R:
正运行
∙S:
可中断休眠
∙T:
已跟踪或已停止
∙Z:
僵死
∙U:
不可运行
这类命令中的每一个都会打印出一大堆信息。
请查阅下面的参考资料以获取这些字段的详细文档。
示例
跟踪当前活动线程的堆栈:
[0]kdb>bt
跟踪标识为575的进程的堆栈:
[0]kdb>btp575
其它命令
下面是在内核调试过程中非常有用的其它几个KDB命令。
id命令以一个地址/符号作为参数,它对从该地址开始的指令进行反汇编。
环境变量IDCOUNT确定要显示多少行输出。
ss命令单步执行指令然后将控制返回给KDB。
该指令的一个变体是ssb,它执行从当前指令指针地址开始的指令(在屏幕上打印指令),直到它遇到将引起分支转移的指令为止。
分支转移指令的典型示例有call、return和jump。
go命令让系统继续正常执行。
一直执行到遇到断点为止(如果已应用了一个断点的话)。
reboot命令立刻重新引导系统。
它并没有彻底关闭系统,因此结果是不可预测的。
ll命令以地址、偏移量和另一个KDB命令作为参数。
它对链表中的每个元素反复执行作为参数的这个命令。
所执行的命令以列表中当前元素的地址作为参数。
示例
反汇编从例程schedule开始的指令。
所显示的行数取决于环境变量IDCOUNT:
[0]kdb>idschedule
执行指令直到它遇到分支转移条件(在本例中为指令jne)为止:
[0]kdb>ssb
0xc0105355default_idle+0x25:
cli
0xc0105356default_idle+0x26:
mov0x14(%edx),%eax
0xc0105359default_idle+0x29:
test%eax,%eax
0xc010535bdefault_idle+0x2b:
jne0xc0105361default_idle+0x31
技巧和诀窍
调试一个问题涉及到:
使用调试器(或任何其它工具)找到问题的根源以及使用源代码来跟踪导致问题的根源。
单单使用源代码来确定问题是极其困难的,只有老练的内核黑客才有可能做得到。
相反,大多数的新手往往要过多地依靠调试器来修正错误。
这种方法可能会产生不正确的问题解决方案。
我们担心的是这种方法只会修正表面症状而不能解决真正的问题。
此类错误的典型示例是添加错误处理代码以处理NULL指针或错误的引用,却没有查出无效引用的真正原因。
结合研究代码和使用调试工具这两种方法是识别和修正问题的最佳方案。
调试器的主要用途是找到错误的位置、确认症状(在某些情况下还有起因)、确定变量的值,以及确定程序是如何出现这种情况的(即,建立调用堆栈)。
有经验的黑客会知道对于某种特定的问题应使用哪一个调试器,并且能迅速地根据调试获取必要的信息,然后继续分析代码以识别起因。
因此,这里为您介绍了一些技巧,以便您能使用KDB快速地取得上述结果。
当然,要记住,调试的速度和精确度来自经验、实践和良好的系统知识(硬件和内核内部机理等)。
技巧#1
在KDB中,在提示处输入地址将返回与之最为匹配的符号。
这在堆栈分析以及确定全局数据的地址/值和函数地址方面极其有用。
同样,输入符号名则返回其虚拟地址。
示例
表明函数sys_read从地址0xc013db4c开始:
[0]kdb>0xc013db4c
0xc013db4c=0xc013db4c(sys_read)
同样,
同样,表明sys_write位于地址0xc013dcc8:
[0]kdb>sys_write
sys_write=0xc013dcc8(sys_write)
这些有助于在分析堆栈时找到全局数据和函数地址。
技巧#2
在编译带KDB的内核时,只要CONFIG_FRAME_POINTER选项出现就使用该选项。
为此,需要在配置内核时选择“Kernelhacking”部分下面的“Compilethekernelwithframepointers”选项。
这确保了帧指针寄存器将被用作帧指针,从而产生正确的回溯。
实际上,您可以手工转储帧指针寄存器的内容并跟踪整个堆栈。
例如,在i386机器上,%ebp寄存器可以用来回溯整个堆栈。
例如,在函数rmqueue()上执行第一个指令后,堆栈看上去类似于下面这样:
[0]kdb>md%ebp
0xc74c9f38c74c9f60c0136c40000001f000000000
0xc74c9f4808053328c0425238c04253a800000000
0xc74c9f58000001f000000246c74c9f6cc0136a25
0xc74c9f68c74c8000c74c9f74c0136d6dc74c9fbc
0xc74c9f78c014fe45c74c80000000000008053328
[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)
我们可以看到rmqueue()被__alloc_pages调用,后者接下来又被_alloc_pages调用,以此类推。
每一帧的第一个双字(doubleword)指向下一帧,这后面紧跟着调用函数的地址。
因此,跟踪堆栈就变成一件轻松的工作了。
技巧#3
go命令可以有选择地以一个地址作为参数。
如果您想在某个特定地址处继续执行,则可以提供该地址作为参数。
另一个办法是使用rm命令修改指令指针寄存器,然后只要输入go。
如果您想跳过似乎会引起问题的某个特定指令或一组指令,这就会很有用。
但是,请注意,该指令使用不慎会造成严重的问题,系统可能会严重崩溃。
技巧#4
您可以利用一个名为defcmd的有用命令来定义自己的命令集。
例如,每当遇到断点时,您可能希望能同时检查某个特殊变量、检查某些寄存器的内容并转储堆栈。
通常,您必须要输入一系列命令,以便能同时执行所有这些工作。
defcmd允许您定义自己的命令,该命令可以包含一个或多个预定义的KDB命令。
然后只需要用一个命令就可以完成所有这三项工作。
其语法如下:
[0]kdb>defcmdname"usage""help"
[0]kdb>[defcmd]typethecommandshere
[0]kdb>[defcmd]endefcmd
例如,可以定义一个(简单的)新命令hari,它显示从地址0xc000000开始的一行内存、显示寄存器的内容并转储堆栈:
[0]kdb>defcmdhari"""noargumentsneeded"
[0]kdb>[defcmd]md0xc0000001
[0]kdb>[defcmd]rd
[0]kdb>[defcmd]md%ebp1
[0]kdb>[defcmd]endefcmd
该命令的输出会是:
[0]kdb>hari
[hari]kdb>md0xc0000001
0xc00000000000001f000e816f000e2c3f000e816
[hari]kdb>rd
eax=0x00000000ebx=0xc0105330ecx=0xc0466000edx=0xc0466000
....
...
[hari]kdb>md%ebp1
0xc0467fbcc0467fd0c01053d200000002000a0200
[0]kdb>
技巧#5
可以使用bph和bpha命令(假如体系结构支持使用硬件寄存器)来应用读写断点。
这意味着每当从某个特定地址读取数据或将数据写入该地址时,我们都可以对此进行控制。
当调试数据/内存毁坏问题时这可能会极其方便,在这种情况中您可以用它来识别毁坏的代码/进程。
示例
每当将四个字节写入地址0xc0204060时就进入内核调试器:
[0]kdb>bph0xc0204060dataw4
在读取从0xc000000开始的至少两个字节的数据时进入内核调试器:
[0]kdb>bph0xc000000datar2
结束语
对于执行内核调试,KDB是一个方便的且功能强大的工具。
它提供了各种选项,并且使我们能够分析内存内容和数据结构。
最妙的是,它不需要用另一台机器来执行调试。
参考资料
∙请在Documentation/kdb目录中查找KDB手册页。
∙有关设置串行控制台的信息,请查找Documentation目录中的serial-console.txt。
∙请在SGI的内核调试器项目网站上下载KDB。
∙有关几个基于方案的Linux调试技术的概述,请阅读“掌握Linux调试技术”(developerWorks,2002年8月)。
∙教程“编译Linux内核”(developerWorks,2000年8月)让您完整地了解配置、编译和安装内核的过程。
∙IBMAIX用户可以在KDBKernelDebuggerandCommand页面上获取有关用于AIX的KDB的使用帮助。
∙那些寻求有关调试OS/2信息的读者应该阅读IBM红皮书TheOS/2DebuggingHandbook(共四卷)的第II卷。
∙在developerWorksLinux专区中查找更多针对Linux开发人员的参考资料。