简介:调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。Linux 中的内置内核调试器 KDB 提供了这种功能。在本文中您将了解如何使用 KDB 所提供的功能,以及如何在 Linux 机器上安装和设置 KDB。您还将熟悉 KDB 中可以使用的命令以及设置和显示选项。 Linux 内核调试器(KDB)允许您调试 Linux 内核,四川旅游。这个恰如其名的工具实质上是内核代码的补丁,它允许高手访问内核内存和数据结构。KDB 的主要优点之一就是它不需要用另一台机器进行调试:您可以调试正在运行的内核。 设置一台用于 KDB 的机器需要花费一些工作,因为需要给内核打补丁并进行重新编译。KDB 的用户应当熟悉 Linux 内核的编译(在一定程度上还要熟悉内核内部机理),但是如果您需要编译内核方面的帮助,请参阅本文结尾处的一节。 在本文中,我们将从有关下载 KDB 补丁、打补丁、(重新)编译内核以及启动 KDB 方面的信息着手。然后我们将了解 KDB 命令并研究一些较常用的命令。最后,我们将研究一下有关设置和显示选项方面的一些详细信息。
KDB 项目是由 Silicon Graphics 维护的(请参阅 以获取链接),您需要从它的 下载与内核版本有关的补丁。(在编写本文时)可用的最新 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 压缩的文件解压缩补丁文件:
您将获得 kdb-v4.2-2.4.20-common-1 和 kdb-v4.2-2.4-i386-1 文件。 现在,应用这些补丁:
这些补丁应该干净利落地加以应用。查找任何以 .rej 结尾的文件。这个扩展名表明这些是失败的补丁。如果内核树没问题,那么补丁的应用就不会有任何问题。 接下来,需要构建内核以支持 KDB。第一步是设置 您还可以根据自己的偏好选择其它两个选项。选择“Compile the kernel with frame pointers”选项(如果有的话)则设置 保存配置,然后退出。重新编译内核。建议在构建内核之前执行“make clean”。用常用方式安装内核并引导它。
您可以定义将在 KDB 初始化期间执行的 KDB 命令。需要在纯文本文件 kdb_cmds 中定义这些命令,该文件位于 Linux 源代码树(当然是在打了补丁之后)的 KDB 目录中。该文件还可以用来定义设置显示和打印选项的环境变量。文件开头的注释提供了编辑文件方面的帮助。使用这个文件的缺点是,在您更改了文件之后需要重新构建并重新安装内核。
如果编译期间没有选中
倒过来执行上述步骤则会取消激活 KDB。也就是说,如果缺省情况下 KDB 是打开的,那么将
在引导期间还可以将另一个标志传递给内核。 调用 KDB 的方式有很多。如果 KDB 处于打开状态,那么只要内核中有紧急情况就自动调用它。按下键盘上的 PAUSE 键将手工调用 KDB。调用 KDB 的另一种方式是通过串行控制台。当然,要做到这一点,需要设置串行控制台(请参阅以获取这方面的帮助)并且需要一个从串行控制台进行读取的程序。按键序列 Ctrl-A 将从串行控制台调用 KDB。
KDB 是一个功能非常强大的工具,它允许进行几个操作,比如内存和寄存器修改、应用断点和堆栈跟踪。根据这些,可以将 KDB 命令分成几个类别。下面是有关每一类中最常用命令的详细信息。
这一类别中最常用的命令是
示例
这一类别中的命令有
示例
常用的断点命令有
对函数
主要的堆栈跟踪命令有
这类命令中的每一个都会打印出一大堆信息。请查阅下面的 以获取这些字段的详细文档。 示例
下面是在内核调试过程中非常有用的其它几个 KDB 命令。
示例
调试一个问题涉及到:使用调试器(或任何其它工具)找到问题的根源以及使用源代码来跟踪导致问题的根源。单单使用源代码来确定问题是极其困难的,只有老练的内核黑客才有可能做得到。相反,大多数的新手往往要过多地依靠调试器来修正错误。这种方法可能会产生不正确的问题解决方案。我们担心的是这种方法只会修正表面症状而不能解决真正的问题。此类错误的典型示例是添加错误处理代码以处理 NULL 指针或错误的引用,却没有查出无效引用的真正原因。 结合研究代码和使用调试工具这两种方法是识别和修正问题的最佳方案。 调试器的主要用途是找到错误的位置、确认症状(在某些情况下还有起因)、确定变量的值,以及确定程序是如何出现这种情况的(即,建立调用堆栈)。有经验的黑客会知道对于某种特定的问题应使用哪一个调试器,并且能迅速地根据调试获取必要的信息,然后继续分析代码以识别起因。 因此,这里为您介绍了一些技巧,以便您能使用 KDB 快速地取得上述结果。当然,要记住,调试的速度和精确度来自经验、实践和良好的系统知识(硬件和内核内部机理等)。
在 KDB 中,在提示处输入地址将返回与之最为匹配的符号。这在堆栈分析以及确定全局数据的地址/值和函数地址方面极其有用。同样,输入符号名则返回其虚拟地址。 示例
同样,
这些有助于在分析堆栈时找到全局数据和函数地址。
在编译带 KDB 的内核时,只要 例如,在函数
我们可以看到 每一帧的第一个双字(double word)指向下一帧,这后面紧跟着调用函数的地址。因此,跟踪堆栈就变成一件轻松的工作了。
您可以利用一个名为
例如,可以定义一个(简单的)新命令
该命令的输出会是:
可以使用 示例
对于执行内核调试,KDB 是一个方便的且功能强大的工具。它提供了各种选项,并且使我们能够分析内存内容和数据结构。最妙的是,它不需要用另一台机器来执行调试。
简介:调试是开发过程中必不可少的环节,通用的桌面操作系统与嵌入式操作系统在调试环境上存在明显的差别。前者,调试器与被调试的程序往往是运行在同一台机器、相同的操作系统上的两个进程,调试器进程通过操作系统专门提供的调用接口(早期UNIX系统的ptrace调用、如今的进程文件系统等)控制、访问被调试进程。后者(又称为远程调试),为了向系统开发人员提供灵活、方便的调试界面,调试器还是运行于通用桌面操作系统的应用程序,四川九寨沟旅游,被调试的程序则运行于基于特定硬件平台的嵌入式操作系统(目标操作系统)。这就带来以下问题:调试器与被调试程序如何通信,被调试程序产生异常如何及时通知调试器,调试器如何控制、访问被调试程序,调试器如何识别有关被调试程序的多任务信息并控制某一特定任务,调试器如何处理某些与目标硬件平台相关的信息(如目标平台的寄存器信息、机器代码的反汇编等)。 我们介绍两种远程调试的方案,看它们怎样解决这些问题。
第一种方案是在目标操作系统和调试器内分别加入某些功能模块,二者互通信息来进行调试。上述问题可通过以下途径解决: 综上所述,这一方案需要目标操作系统提供支持远程调试协议的通信模块(包括简单的设备驱动)和多任务调试接口,并改写异常处理的有关部分。另外目标操作系统还需要定义一个设置断点的函数;因为有的硬件平台提供能产生特定调试陷阱异常(debugtrap)的断点指令以支持调试(如X86的INT3),而另一些机器没有类似的指令,就用任意一条不能被解释执行的非法(保留)指令代替。目标操作系统添加的这些模块统称为"插桩"(见下图),驻留于ROM中则称为ROMmonitor。通用操作系统也有具备这类模块的:编译运行于Alpha、Sparc或PowerPC平台的LINUX内核时若将kgdb开关打开,就相当于加入了插桩。 ![]() 运行于目标操作系统的被调试的应用程序要在入口处调用这个设置断点的函数以产生异常,异常处理程序调用调试端口通信模块,等待主机(host)上的调试器发送信息。双方建立连接后调试器便等待用户发出调试命令,目标系统等待调试器根据用户命令生成的指令。这一过程如下图所示。 ![]() 这一方案的实质是用软件接管目标系统的全部异常处理(exceptionhandler)及部分中断处理,在其中插入调试端口通信模块,与主机的调试器交互。它只能在目标操作系统初始化,特别是调试通信端口初始化完成后才起作用,所以一般只用于调试运行于目标操作系统之上的应用程序,而不宜用来调试目标操作系统,特别是无法调试目标操作系统的启动过程。而且由于它必然要占用目标平台的某个通信端口,该端口的通信程序就无法调试了。最关键的是它必须改动目标操作系统,这一改动即使没有对操作系统在调试过程中的表现造成不利影响,至少也会导致目标系统多了一个不用于正式发布的调试版。
片上调试是在处理器内部嵌入额外的控制模块,当满足了一定的触发条件时进入某种特殊状态。在该状态下,被调试程序停止运行,主机的调试器可以通过处理器外部特设的通信接口访问各种资源(寄存器、存储器等)并执行指令。为了实现主机通信端口与目标板调试通信接口各引脚信号的匹配,二者往往通过一块简单的信号转换电路板连接(如下图所示)。内嵌的控制模块以基于微码的监控器(microcodemonitor)或纯硬件资源的形式存在,包括一些提供给用户的接口(如断点寄存器等)。具体产品有MotorolaCPU16、CPU32、Coldfire系列的BDM(Background Debug Mode),MotorolaPowerPC 5xx、8xx系列的EPBDM(Embedded PowerPC Background DebugMode),IBM、TI的JTAG(Joint Test ActionDebug,IEEE标准),还有OnCE、MPSD等等。下面以MPC860的EPBDM为例介绍片上调试方式。 ![]() EPBDM的运作相当于用处理器内嵌的调试模块接管中断及异常处理。用户通过设置调试许可寄存器(debugenableregister)来指定哪些中断或异常发生后处理器直接进入调试状态,而不是操作系统的处理程序。进入调试状态后,内嵌调试模块向外部调试通信接口发出信号,通知一直在通信接口监听的主机调试器,然后调试器便可通过调试模块使处理器执行任意系统指令(相当于特权态)。所有指令均通过调试模块获取,所有load/store均直接访问内存,缓存(cache)及存储管理单元(MMU)均不可用;数据寄存器被映射为一个特殊寄存器DPDR,通过mtspr和mfspr指令访问。调试器向处理器送rfi(returnfrom interrupt)指令便结束调试状态,被调试程序继续运行。 与插桩方式的缺点相对应,OCD不占用目标平台的通信端口,无需修改目标操作系统,能调试目标操作系统的启动过程,大大方便了系统开发人员。随之而来的缺点是软件工作量的增加:调试器端除了需补充对目标操作系统多任务的识别、控制等模块,还要针对使用同一芯片的不同开发板编写各类ROM、RAM的初始化程序。 下面就以调试运行于MPC860的LINUX为例,说明用OCD方式调试OS启动的某些关键细节。 首先,LINUX内核模块以压缩后的zImage形式驻留于目标板的ROM,目标板上电后先运行ROM中指定位置的程序将内核移至RAM并解压缩,然后再跳转至内核入口处运行。要调试内核,必须在上电后ROM中的指令执行之前获得系统的控制权,即进入调试状态、设断点,这样才能开展调试过程。MPC860的EPBDM提供了这一手段。 MPC860没有类似X86的INT3那样能产生特定调试陷阱异常的指令,而操作系统内核往往具有针对非法指令的异常处理;为了使对内核正常运行的干扰降至最小,调试时应尽量设置硬件断点,而不是利用非法指令产生异常的"软"断点。 LINUX实现了虚存管理,嵌入式LINUX往往也有这一功能。地址空间从实到虚的转换在内核启动过程中便完成了,不论调试内核还是应用程序,调试器都无法回避对目标系统虚地址空间的访问,否则断点命中时根本无法根据程序计数器的虚地址显示当前指令,更不用说访问变量了。由于调试状态下转换旁视缓冲器(TranslationLookasideBuffer)无法利用,只能仿照LINUX内核TLB失效时的异常处理程序,根据虚地址中的页表索引位访问特定寄存器查两级页表得出物理页面号,从而完成虚实地址的转换。MPC860采用哈佛结构(Harvardarchitecture),指令和数据缓存分离设置(因为程序的指令段和数据段是分离的,这种结构可以消除取指令和访问数据之间的冲突),二者的TLB也分离设置;然而TLB失效时查找页表计算物理地址的过程是相同的,因为页表只有一个,不存在指令、数据分离的问题。虚实地址转换这一任务虽然完全落在了调试器一方,由于上述原因,再加上调试对象是嵌入式系统,一般不会有外存设备,不必考虑内存访问缺页的情况,所以增加的工作量并不大。
传统的调试方法可概括为如下过程:设断点--程序暂停--观察程序状态--继续运行。被调试的如果是实时系统,即使调试器支持批处理命令避免了用户输入命令、观察结果带来的延迟,它与目标系统之间的通信也完全可能错过对目标平台外设信号的响应。于是,针对某些调试器(如GDB)提供的监视点(tracepoint)这一特殊调试手段,目标方的插桩在原有的基础上被改进,称为代理(agent)。调试时用户首先在调试器设置监视点,以源代码表达式的形式指定感兴趣的对象名。为了减少代理解析表达式的工作,调试器将表达式转换为简单的字节码,传送至代理。程序运行后命中监视点、唤醒代理,代理根据字节码记录用户所需数据存入特定缓冲区(不仅仅是表达式的最终结果,还有中间结果),令程序继续运行;这一步骤无需与调试器通信。当调试器再度得到控制时,就可以发出命令,向代理查询历次监视记录。较之于插桩,代理增加了对接受到的字节码的分析模块,相应的目标代码体积只有大约3K字节;当然,监视记录缓冲区也要占用目标平台的存储空间,不过缓冲区的大小可在代理生成时由用户决定。总之,这一改进以有限的目标系统资源为代价,为实时监视提供了一个低成本的可行方案。 调试并不仅仅意味着设断点--程序暂停--观察--继续这一过程,往往还需要profiling、跟踪(trace)等多种手段,而现代微处理器的技术进步却为这些调试手段的实行带来了困难。以跟踪为例,其目的无非是记录真实的程序运行流;可现代处理器指令缓存都集成于芯片内(RISC处理器尤为如此),运行指令时"取指"这一操作大多在芯片内部针对指令缓存进行,芯片外部总线上只能观察到多条指令的预取(prefetch),预取的指令并不一定执行(由于跳转等原因);另外,指令往往经过动态调度后在流水线中乱序执行,如何再现其原始顺序也是个问题。解决方案大致有以下三种: 总之,处理器厂家提供集成于片内的调试电路为高档嵌入式系统开发提供各种非干扰式的调试手段早已是大势所趋。为了解决该领域标准化的需要,一些处理器厂家、工具开发公司和仪器制造商于1998年组成了Nexus5001Forum,这是一个旨在为嵌入式控制应用产生和定义嵌入式处理器调试接口标准的联合组织,以前的名称是GlobalEmbedded Processor Debug Interface StandardConsortium(全球嵌入式处理器调试接口标准协会)。Nexus现在有24个成员单位,包括创始成员Motorola、InfineonTechnologies、日立、ETAS和HP等公司。该组织首先处理的是汽车动力应用所需要的调试,现在已发展成为调试数据通信、无线系统和其他实时嵌入式应用的通用接口。 |