Linux 内核调试器内幕.docx

上传人:b****5 文档编号:8571663 上传时间:2023-01-31 格式:DOCX 页数:16 大小:26.09KB
下载 相关 举报
Linux 内核调试器内幕.docx_第1页
第1页 / 共16页
Linux 内核调试器内幕.docx_第2页
第2页 / 共16页
Linux 内核调试器内幕.docx_第3页
第3页 / 共16页
Linux 内核调试器内幕.docx_第4页
第4页 / 共16页
Linux 内核调试器内幕.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

Linux 内核调试器内幕.docx

《Linux 内核调试器内幕.docx》由会员分享,可在线阅读,更多相关《Linux 内核调试器内幕.docx(16页珍藏版)》请在冰豆网上搜索。

Linux 内核调试器内幕.docx

Linux内核调试器内幕

Linux内核调试器内幕

KDB入门指南

级别:

初级

HariprasadNellitheertha,软件工程师,IBM

2003年9月01日

调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。

Linux中的内置内核调试器KDB提供了这种功能。

在本文中您将了解如何使用KDB所提供的功能,以及如何在Linux机器上安装和设置KDB。

您还将熟悉KDB中可以使用的命令以及设置和显示选项。

Linux内核调试器(KDB)允许您调试Linux内核。

这个恰如其名的工具实质上是内核代码的补丁,它允许高手访问内核内存和数据结构。

KDB的主要优点之一就是它不需要用另一台机器进行调试:

您可以调试正在运行的内核。

设置一台用于KDB的机器需要花费一些工作,因为需要给内核打补丁并进行重新编译。

KDB的用户应当熟悉Linux内核的编译(在一定程度上还要熟悉内核内部机理),但是如果您需要编译内核方面的帮助,请参阅本文结尾处的参考资料一节。

在本文中,我们将从有关下载KDB补丁、打补丁、(重新)编译内核以及启动KDB方面的信息着手。

然后我们将了解KDB命令并研究一些较常用的命令。

最后,我们将研究一下有关设置和显示选项方面的一些详细信息。

入门

KDB项目是由SiliconGraphics维护的(请参阅参考资料以获取链接),您需要从它的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-dkdb-v4.2-2.4.20-common-1.bz2

#bzip2-dkdb-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等)来完成这一步。

转到结尾处的“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

[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是一个方便的且功能强大的工具。

它提供了各种选项,并且使我们能够分析内存内容和数据结构。

最妙的是,它不需要用另一台机器来执行调试。

回页首

参考资料

∙您可以参阅本文在developerWorks全球站点上的英文原文.

∙请在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开发人员的参考资料。

回页首

关于作者

HariprasadNellitheertha在印度班加罗尔(Bangalore)的IBMLinux技术中心工作。

他目前正在LinuxChangeTeam从事修正内核和其它Linux错误的工作。

Hari研究过OS/2内核和文件系统。

他的兴趣包括Linux内核内部机理、文件系统和自主计算。

可以通过nharipra@与Hari联系。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 其它

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1