Linux 内核调试器 调试指南

Linux 内核调试器内幕

KDB 入门指南

Hariprasad Nellitheertha (nharipra@in.ibm.com), 软件工程师, IBM

简介: 调试内核问题时,能够跟踪内核执行情况并查看其内存和数据结构是非常有用的。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 <kdb-v4.2-2.4.20-common-1#patch -p1 <kdb-v4.2-2.4.20-i386-1

这些补丁应该干净利落地加以应用。查找任何以 .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

在引导期间还可以将另一个标志传递给内核。 kdb=early 标志将导致在引导过程的初始阶段就把控制权传递给 KDB。如果您需要在引导过程初始阶段进行调试,那么这将有所帮助。

调用 KDB 的方式有很多。如果 KDB 处于打开状态,那么只要内核中有紧急情况就自动调用它。按下键盘上的 PAUSE 键将手工调用 KDB。调用 KDB 的另一种方式是通过串行控制台。当然,要做到这一点,需要设置串行控制台(请参阅参考资料以获取这方面的帮助)并且需要一个从串行控制台进行读取的程序。按键序列 Ctrl-A 将从串行控制台调用 KDB。

KDB 命令

KDB 是一个功能非常强大的工具,它允许进行几个操作,比如内存和寄存器修改、应用断点和堆栈跟踪。根据这些,可以将 KDB 命令分成几个类别。下面是有关每一类中最常用命令的详细信息。

内存显示和修改

这一类别中最常用的命令是 mdmdrmmmmW

md 命令以一个地址/符号和行计数为参数,显示从该地址开始的 line-count 行的内存。如果没有指定line-count ,那么就使用环境变量所指定的缺省值。如果没有指定地址,那么 md 就从上一次打印的地址继续。地址打印在开头,字符转换打印在结尾。

mdr 命令带有地址/符号以及字节计数,显示从指定的地址开始的 byte-count 字节数的初始内存内容。它本质上和md 一样,但是它不显示起始地址并且不在结尾显示字符转换。 mdr 命令较少使用。

mm 命令修改内存内容。它以地址/符号和新内容作为参数,用 new-contents 替换地址处的内容。

mmW 命令更改从地址开始的 W 个字节。请注意, mm 更改一个机器字。

示例


显示从 0xc000000 开始的 15 行内存:
[0]kdb> md 0xc000000 15


将内存位置为 0xc000000 上的内容更改为 0x10:
[0]kdb> mm 0xc000000 0x10

寄存器显示和修改

这一类别中的命令有 rdrmef

rd 命令(不带任何参数)显示处理器寄存器的内容。它可以有选择地带三个参数。如果传递了 c 参数,则 rd 显示处理器的控制寄存器;如果带有 d 参数,那么它就显示调试寄存器;如果带有 u 参数,则显示上一次进入内核的当前任务的寄存器组。

rm 命令修改寄存器的内容。它以寄存器名称和 new-contents 作为参数,用 new-contents 修改寄存器。寄存器名称与特定的体系结构有关。目前,不能修改控制寄存器。

ef 命令以一个地址作为参数,它显示指定地址处的异常帧。

示例


显示通用寄存器组:
[0]kdb> rd



[0]kdb> rm %ebx 0x25

断点

常用的断点命令有 bpbcbdbebl

bp 命令以一个地址/符号作为参数,它在地址处应用断点。当遇到该断点时则停止执行并将控制权交予 KDB。该命令有几个有用的变体。 bpa 命令对 SMP 系统中的所有处理器应用断点。 bph 命令强制在支持硬件寄存器的系统上使用它。 bpha 命令类似于bpa 命令,差别在于它强制使用硬件寄存器。

bd 命令禁用特殊断点。它接收断点号作为参数。该命令不是从断点表中除去断点,而只是禁用它。断点号从 0 开始,根据可用性顺序分配给断点。

be 命令启用断点。该命令的参数也是断点号。

bl 命令列出当前的断点集。它包含了启用的和禁用的断点。

bc 命令从断点表中除去断点。它以具体的断点号或 * 作为参数,在后一种情况下它将除去所有断点。


示例

对函数 sys_write() 设置断点:

[0]kdb> bp sys_write


列出断点表中的所有断点:
[0]kdb> bl


清除断点号 1:
[0]kdb> bc 1

>堆栈跟踪

主要的堆栈跟踪命令有 btbtpbtcbta

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> btp 575

其它命令

下面是在内核调试过程中非常有用的其它几个 KDB 命令。

id 命令以一个地址/符号作为参数,它对从该地址开始的指令进行反汇编。环境变量 IDCOUNT 确定要显示多少行输出。

ss 命令单步执行指令然后将控制返回给 KDB。该指令的一个变体是 ssb ,它执行从当前指令指针地址开始的指令(在屏幕上打印指令),直到它遇到将引起分支转移的指令为止。分支转移指令的典型示例有callreturnjump

go 命令让系统继续正常执行。一直执行到遇到断点为止(如果已应用了一个断点的话)。

reboot 命令立刻重新引导系统。它并没有彻底关闭系统,因此结果是不可预测的。

ll 命令以地址、偏移量和另一个 KDB 命令作为参数。它对链表中的每个元素反复执行作为参数的这个命令。所执行的命令以列表中当前元素的地址作为参数。

示例


反汇编从例程 schedule 开始的指令。所显示的行数取决于环境变量 IDCOUNT
[0]kdb> id schedule


执行指令直到它遇到分支转移条件(在本例中为指令 jne )为止:
 
[0]kdb> ssb0xc0105355  default_idle+0x25:  cli0xc0105356  default_idle+0x26:  mov  0x14(%edx),%eax0xc0105359  default_idle+0x29:  test %eax, %eax0xc010535b  default_idle+0x2b:  jne  0xc0105361 default_idle+0x31

技巧和诀窍

调试一个问题涉及到:使用调试器(或任何其它工具)找到问题的根源以及使用源代码来跟踪导致问题的根源。单单使用源代码来确定问题是极其困难的,只有老练的内核黑客才有可能做得到。相反,大多数的新手往往要过多地依靠调试器来修正错误。这种方法可能会产生不正确的问题解决方案。我们担心的是这种方法只会修正表面症状而不能解决真正的问题。此类错误的典型示例是添加错误处理代码以处理 NULL 指针或错误的引用,却没有查出无效引用的真正原因。

结合研究代码和使用调试工具这两种方法是识别和修正问题的最佳方案。

调试器的主要用途是找到错误的位置、确认症状(在某些情况下还有起因)、确定变量的值,以及确定程序是如何出现这种情况的(即,建立调用堆栈)。有经验的黑客会知道对于某种特定的问题应使用哪一个调试器,并且能迅速地根据调试获取必要的信息,然后继续分析代码以识别起因。

因此,这里为您介绍了一些技巧,以便您能使用 KDB 快速地取得上述结果。当然,要记住,调试的速度和精确度来自经验、实践和良好的系统知识(硬件和内核内部机理等)。

技巧 #1

在 KDB 中,在提示处输入地址将返回与之最为匹配的符号。这在堆栈分析以及确定全局数据的地址/值和函数地址方面极其有用。同样,输入符号名则返回其虚拟地址。

示例


表明函数 sys_read 从地址 0xc013db4c 开始:
[0]kdb> 0xc013db4c0xc013db4c = 0xc013db4c (sys_read)

同样,


同样,表明 sys_write 位于地址 0xc013dcc8:
[0]kdb> sys_writesys_write = 0xc013dcc8 (sys_write)

这些有助于在分析堆栈时找到全局数据和函数地址。

技巧 #2

在编译带 KDB 的内核时,只要 CONFIG_FRAME_POINTER 选项出现就使用该选项。为此,需要在配置内核时选择“Kernel hacking”部分下面的“Compile the kernel with frame pointers”选项。这确保了帧指针寄存器将被用作帧指针,从而产生正确的回溯。实际上,您可以手工转储帧指针寄存器的内容并跟踪整个堆栈。例如,在 i386 机器上,%ebp 寄存器可以用来回溯整个堆栈。

例如,在函数 rmqueue() 上执行第一个指令后,堆栈看上去类似于下面这样:

[0]kdb> md %ebp0xc74c9f38 c74c9f60  c0136c40 000001f0 000000000xc74c9f48 08053328 c0425238 c04253a8 000000000xc74c9f58 000001f0  00000246 c74c9f6c c0136a250xc74c9f68 c74c8000  c74c9f74  c0136d6d c74c9fbc0xc74c9f78 c014fe45  c74c8000  00000000 08053328[0]kdb> 0xc0136c400xc0136c40 = 0xc0136c40 (__alloc_pages +0x44)[0]kdb> 0xc0136a250xc0136a25 = 0xc0136a25 (_alloc_pages +0x19)[0]kdb> 0xc0136d6d0xc0136d6d = 0xc0136d6d (__get_free_pages +0xd)

我们可以看到 rmqueue()__alloc_pages 调用,后者接下来又被 _alloc_pages 调用,以此类推。

每一帧的第一个双字(double word)指向下一帧,这后面紧跟着调用函数的地址。因此,跟踪堆栈就变成一件轻松的工作了。

技巧 #3

go 命令可以有选择地以一个地址作为参数。如果您想在某个特定地址处继续执行,则可以提供该地址作为参数。另一个办法是使用 rm 命令修改指令指针寄存器,然后只要输入 go 。如果您想跳过似乎会引起问题的某个特定指令或一组指令,这就会很有用。但是,请注意,该指令使用不慎会造成严重的问题,系统可能会严重崩溃。

技巧 #4

您可以利用一个名为 defcmd 的有用命令来定义自己的命令集。例如,每当遇到断点时,您可能希望能同时检查某个特殊变量、检查某些寄存器的内容并转储堆栈。通常,您必须要输入一系列命令,以便能同时执行所有这些工作。defcmd 允许您定义自己的命令,该命令可以包含一个或多个预定义的 KDB 命令。然后只需要用一个命令就可以完成所有这三项工作。其语法如下:

 
[0]kdb> defcmd name "usage" "help"[0]kdb> [defcmd] type the commands here[0]kdb> [defcmd] endefcmd

例如,可以定义一个(简单的)新命令 hari ,它显示从地址 0xc000000 开始的一行内存、显示寄存器的内容并转储堆栈:

[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

该命令的输出会是:

[0]kdb> hari[hari]kdb> md 0xc000000 10xc000000 00000001 f000e816 f000e2c3 f000e816[hari]kdb> rdeax = 0x00000000 ebx = 0xc0105330 ecx = 0xc0466000 edx = 0xc0466000.......[hari]kdb> md %ebp 10xc0467fbc c0467fd0 c01053d2 00000002 000a0200[0]kdb>

技巧 #5

可以使用 bphbpha 命令(假如体系结构支持使用硬件寄存器)来应用读写断点。这意味着每当从某个特定地址读取数据或将数据写入该地址时,我们都可以对此进行控制。当调试数据/内存毁坏问题时这可能会极其方便,在这种情况中您可以用它来识别毁坏的代码/进程。

示例


每当将四个字节写入地址 0xc0204060 时就进入内核调试器:
[0]kdb> bph 0xc0204060 dataw 4


在读取从 0xc000000 开始的至少两个字节的数据时进入内核调试器:
[0]kdb> bph 0xc000000 datar 2

结束语

对于执行内核调试,KDB 是一个方便的且功能强大的工具。它提供了各种选项,并且使我们能够分析内存内容和数据结构。最妙的是,它不需要用另一台机器来执行调试。


参考资料

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

  • 请在 Documentation/kdb 目录中查找 KDB 手册页。



  • 有关设置串行控制台的信息,请查找 Documentation 目录中的 serial-console.txt。



  • 请在 SGI 的内核调试器项目网站上 下载 KDB。



  • 有关几个基于方案的 Linux 调试技术的概述,请阅读“ 掌握 Linux 调试技术”( developerWorks,2002 年 8 月)。



  • 教程“ 编译 Linux 内核”( developerWorks,2000 年 8 月)让您完整地了解配置、编译和安装内核的过程。



  • IBM AIX 用户可以在 KDB Kernel Debugger and Command页面上获取有关用于 AIX 的 KDB 的使用帮助。



  • 那些寻求有关调试 OS/2 信息的读者应该阅读 IBM 红皮书 The OS/2 Debugging Handbook(共四卷)的 第 II 卷。



  • developerWorksLinux 专区中查找更多 针对 Linux 开发人员的参考资料。



关于作者

Hariprasad Nellitheertha 在印度班加罗尔(Bangalore)的 IBM Linux 技术中心工作。他目前正在 Linux Change Team 从事修正内核和其它 Linux 错误的工作。Hari 研究过 OS/2 内核和文件系统。他的兴趣包括 Linux 内核内部机理、文件系统和自主计算。可以通过nharipra@in.ibm.com与 Hari 联系。



嵌入式操作系统的调试

熊竞 (jxiong@ict.ac.cn)中科院计算所

简介: 调试是开发过程中必不可少的环节,通用的桌面操作系统与嵌入式操作系统在调试环境上存在明显的差别。前者,调试器与被调试的程序往往是运行在同一台机器、相同的操作系统上的两个进程,调试器进程通过操作系统专门提供的调用接口(早期UNIX系统的ptrace调用、如今的进程文件系统等)控制、访问被调试进程。后者(又称为远程调试),为了向系统开发人员提供灵活、方便的调试界面,调试器还是运行于通用桌面操作系统的应用程序,被调试的程序则运行于基于特定硬件平台的嵌入式操作系统(目标操作系统)。这就带来以下问题:调试器与被调试程序如何通信,被调试程序产生异常如何及时通知调试器,调试器如何控制、访问被调试程序,调试器如何识别有关被调试程序的多任务信息并控制某一特定任务,调试器如何处理某些与目标硬件平台相关的信息(如目标平台的寄存器信息、机器代码的反汇编等)。

我们介绍两种远程调试的方案,看它们怎样解决这些问题。

调试方案

一 插桩(stub)

第一种方案是在目标操作系统和调试器内分别加入某些功能模块,二者互通信息来进行调试。上述问题可通过以下途径解决:

  1. 调试器与被调试程序的通信
    调试器与目标操作系统通过指定通信端口(串口、网卡、并口)遵循远程调试协议进行通信。
  2. 被调试程序产生异常及时通知调试器
    目标操作系统的所有异常处理最终都要转向通信模块,告知调试器当前的异常号;调试器据此向用户显示被调试程序产生了哪一类异常。
  3. 调试器控制、访问被调试程序
    调试器的这类请求实际上都将转换成对被调试程序的地址空间或目标平台的某些寄存器的访问,目标操作系统接收到这样的请求可以直接处理。对于没有虚拟存储概念的简单的嵌入式操作系统而言,完成这些任务十分容易。
  4. 调试器识别有关被调试程序的多任务信息并控制某一特定任务
    由目标操作系统提供相关接口。目标系统根据调试器发送的关于多任务的请求,调用该接口提供相应信息或针对某一特定任务进行控制,并返回信息给调试器。
  5. 调试器处理与目标硬件平台相关的信息
    第2条所述调试器应能根据异常号识别目标平台产生异常的类型也属于这一范畴,这类工作完全可以由调试器独立完成。支持多种目标平台正是GNUGDB的一大特色。

综上所述,这一方案需要目标操作系统提供支持远程调试协议的通信模块(包括简单的设备驱动)和多任务调试接口,并改写异常处理的有关部分。另外目标操作系统还需要定义一个设置断点的函数;因为有的硬件平台提供能产生特定调试陷阱异常(debugtrap)的断点指令以支持调试(如X86的INT3),而另一些机器没有类似的指令,就用任意一条不能被解释执行的非法(保留)指令代替。目标操作系统添加的这些模块统称为"插桩"(见下图),驻留于ROM中则称为ROMmonitor。通用操作系统也有具备这类模块的:编译运行于Alpha、Sparc或PowerPC平台的LINUX内核时若将kgdb开关打开,就相当于加入了插桩。


图1
图1

运行于目标操作系统的被调试的应用程序要在入口处调用这个设置断点的函数以产生异常,异常处理程序调用调试端口通信模块,等待主机(host)上的调试器发送信息。双方建立连接后调试器便等待用户发出调试命令,目标系统等待调试器根据用户命令生成的指令。这一过程如下图所示。


图2
图2

这一方案的实质是用软件接管目标系统的全部异常处理(exceptionhandler)及部分中断处理,在其中插入调试端口通信模块,与主机的调试器交互。它只能在目标操作系统初始化,特别是调试通信端口初始化完成后才起作用,所以一般只用于调试运行于目标操作系统之上的应用程序,而不宜用来调试目标操作系统,特别是无法调试目标操作系统的启动过程。而且由于它必然要占用目标平台的某个通信端口,该端口的通信程序就无法调试了。最关键的是它必须改动目标操作系统,这一改动即使没有对操作系统在调试过程中的表现造成不利影响,至少也会导致目标系统多了一个不用于正式发布的调试版。

二 片上调试(On Chip Debugging)及EmbeddedPowerPC Background Debug Mode

片上调试是在处理器内部嵌入额外的控制模块,当满足了一定的触发条件时进入某种特殊状态。在该状态下,被调试程序停止运行,主机的调试器可以通过处理器外部特设的通信接口访问各种资源(寄存器、存储器等)并执行指令。为了实现主机通信端口与目标板调试通信接口各引脚信号的匹配,二者往往通过一块简单的信号转换电路板连接(如下图所示)。内嵌的控制模块以基于微码的监控器(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为例介绍片上调试方式。


图3
图3

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),预取的指令并不一定执行(由于跳转等原因);另外,指令往往经过动态调度后在流水线中乱序执行,如何再现其原始顺序也是个问题。解决方案大致有以下三种:

  1. 有的处理器除了正常运行外,还能以串行方式运行,所有的取指周期都可呈现于片外总线(相当于禁用缓存与流水线)。这样一来,跟踪容易多了,处理器性能也大大降低了,根本不适用于实时要求严格的系统。
  2. 编译器自动在指定的分支及函数出入口插入对特定内存区域的写指令(与gprof等profiling工具采用的手段类似),它们都是不通过缓存而直接向内存写的,这就能反映于芯片外总线从而被外接的逻辑分析仪记录,最终由主机端的调试工具分析并结合符号表重构程序流。这种方法虽被广泛使用,但毕竟是干扰式的(intrusive),对系统性能也有影响。
  3. 像上文所述的片上调试那样,也有处理器在片内附加了跟踪电路,收集程序流运行时的"不连贯"(discontinuities)信息(分支和异常处理的跳转目的及源地址等),压缩后送至特定端口,再由逻辑分析仪捕获送至主机端调试工具重构程序流。该方案对系统性能影响最小。

总之,处理器厂家提供集成于片内的调试电路为高档嵌入式系统开发提供各种非干扰式的调试手段早已是大势所趋。为了解决该领域标准化的需要,一些处理器厂家、工具开发公司和仪器制造商于1998年组成了Nexus5001Forum,这是一个旨在为嵌入式控制应用产生和定义嵌入式处理器调试接口标准的联合组织,以前的名称是GlobalEmbedded Processor Debug Interface StandardConsortium(全球嵌入式处理器调试接口标准协会)。Nexus现在有24个成员单位,包括创始成员Motorola、InfineonTechnologies、日立、ETAS和HP等公司。该组织首先处理的是汽车动力应用所需要的调试,现在已发展成为调试数据通信、无线系统和其他实时嵌入式应用的通用接口。


参考资料

  • MPC860 PowerQUICC User's Manual MOTOROLA

  • http://www.vas-gmbh.de/software/mpcbdm/

  • http://www.metrowerks.com/tools/documentation/embedded/zenofbdm

  • http://www.redhat.com/support/wpapers/cygnus_heinsenberg/trace.html

  • http://www.ednmag.com/reg/2000/05112000/10tt.htm

关于作者

熊竞,任职于中科院计算所嵌入式系统软件组。主要从事开发嵌入式操作系统的仿真器、调试器及集成开发环境。您可以通过电子邮件jxiong@ict.ac.cn与他联系。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/453646.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习API HOOK,编写了一个winsock 的封包抓取程序,可免费使用;

开发环境是:windows 2000 delphi 7 监视API&#xff1a;recv,recvfrom,WSARecvEx,send,sendto,accept,bind,closesocket,connect socket 版本&#xff1a;wsock32.dll/*ws2_32.dll(暂时有兼容问题) 目前还不支持修改封包&#xff1b; 当前实现针对某个进程或多个选定进程的通…

MyBatis学习总结(二)——使用MyBatis对表执行CRUD操作

MyBatis学习总结(二)——使用MyBatis对表执行CRUD操作 上一篇博文MyBatis学习总结(一)——MyBatis快速入门中我们讲了如何使用Mybatis查询users表中的数据&#xff0c;算是对MyBatis有一个初步的入门了&#xff0c;今天讲解一下如何使用MyBatis对users表执行CRUD操作。本文中使…

cifs mount 挂载共享目录_安装cifsutils解决linux挂载windows共享文件夹

1、安装mount.cifs软件包yum install cifs-utils -y如果是离线环境&#xff0c;请参考我的另一篇文章https://blog.csdn.net/qq_37119960/article/details/1083313732、开始挂载mount.cifs //192.168.1.110/share /usr/local/winshare -o useradministrator,pass123456参数说明…

JFinal框架

FJinal过滤器(tomcat) 创建java类继承JFinalConfig 会实现六个方法(有一个是拦截器的方法好像是,那个我好像看的跟struts2一样但是又没看懂暂时不写) Controller层的测试方法 Entity实体类 常用方法 查询 增加 删除 修改 转载于:https://www.cnblogs.com/guanzhuang/p/8317949.…

掌握 Linux 调试技术 使用 GDB 调试 Linux 软件

简介&#xff1a; 您可以用各种方法来监控运行着的用户空间程序&#xff1a;可以为其运行调试器并单步调试该程序&#xff0c;添加打印语句&#xff0c;或者添加工具来分析程序。本文描述了几种可以用来调试在 Linux 上运行的程序的方法。我们将回顾四种调试问题的情况&#xf…

集合之二:迭代器

迭代器的简单使用 在遍历容器时&#xff0c;我们可以使用for循环或者是增强for循环&#xff0c;但是不同的集合结构在遍历时&#xff0c;我们要针对集合特点采取不同的方式&#xff0c;比如List是链表&#xff0c;我们可以直接当做数组处理&#xff0c;但Map是Key—Value的形式…

开源Java反编译工具

Java 反编译器 1. JD-GUI JD-GUI 是一个用 C 开发的 Java 反编译工具&#xff0c;由 Pavel Kouznetsov开发&#xff0c;支持Windows、Linux和苹果Mac Os三个平台。 而且提供了Eclipse平台下的插件JD-Eclipse。JD-GUI不需要安装&#xff0c;直接点击运行&#xff0c;可以反编译j…

python自动取款机程序_python ATM取款机----运维开发初学(上篇)

自动取款机基本功能&#xff1a;可以存取转账&#xff0c;刷卡信息查询&#xff0c;银行卡号历史信息查询&#xff0c;消费记录查询&#xff0c;修改密码。思维导图如下&#xff1a;数据库设计&#xff1a;mysql> desc balan_list; #保存账号交易记录option_type-----------…

阿里服务器+Centos7.4+Tomcat+JDK部署

适用对象 本文档介绍如何使用一台基本配置的云服务器 ECS 实例部署 Java web 项目。适用于刚开始使用阿里云进行建站的个人用户。 配置要求 这里列出的软件版本仅代表写作本文档使用的版本。操作时&#xff0c;请您以实际软件版本为准。 操作系统&#xff1a;CentOS 7.4Tomcat …

php输出mysqli查询出来的结果

php连接mysql我有文章已经写过了&#xff0c;这篇文章主要是介绍从mysql中查询出结果之后怎么输出的问题。 一&#xff1a;mysqli_fetch_row(); 查询结果&#xff1a;array([0]>小王) 查询&#xff1a; [php] view plaincopy while ($row mysqli_fetch_assoc($result)) …

rhel mysql安装_RHEL6.4下MySQL安装方法及简单配置

1.MySQL安装方法简介 1.rpm包yum安装 2.通用二进制包安装 3.源码编译安装 注意&#xff1a;实验所采用的系统平台为&#xff1a;RHEL6.4 2.rpm ins首页 → 数据库技术背景&#xff1a;阅读新闻RHEL6.4下MySQL安装方法及简单配置[日期&#xff1a;2014-04-08]来源&#xff1a;Li…

H.264算法的DSP移植与优化

摘要&#xff1a;在TMS320DM643平台上实现H&#xff0e;264基档次编码器的移植与优化显得格外实用和必要。基于对DSP平台的结构特性和H&#xff0e;264的计算复杂度分析&#xff0c;主要从核心算法、数据传输和存储器&#xff0f;Cache使用几方面对H&#xff0e;264编码器进行了…

java-linux-eclipse配置

转载于:https://www.cnblogs.com/sheying/p/8327517.html

n皇后问题java_经典n皇后问题java代码实现

问题描述&#xff1a;在n*n的二维表格&#xff0c;把n个皇后在表格上&#xff0c;要求同一行、同一列或同一斜线上不能有2个以上的皇后。例如八皇后有92种解决方案&#xff0c;五皇后有10种解决方案。public class TestQueen {int n; //皇后的个数int num 0; // 记录方案数int…

nagios监控服务器的搭建

nagios 概述&#xff1a; 开源的免费的网络监视工具。 监控&#xff1a; windows, Linux,Unix,交换机和路由器。报警。 Nagios是插件式的结构&#xff0c;它本身没有任何监控功能&#xff0c;所有的监控都是通过插件进行的&#xff0c;因此其是高度模块化和富于弹性的。Nagios…

BZOJ1031: [JSOI2007]字符加密Cipher

1031: [JSOI2007]字符加密Cipher Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 7882 Solved: 3425[Submit][Status][Discuss]Description 喜欢钻研问题的JS同学&#xff0c;最近又迷上了对加密方法的思考。一天&#xff0c;他突然想出了一种他认为是终极的加密办法&#…

LoadRunner远程监测Centos服务性能配置过程

由于公司的需要&#xff0c;经过一段时间的探索&#xff0c;参考了很多业内人士的文档&#xff0c;终于完成LoadRunner远程监测centos服务器的配置过程。 首先监测所需要服务是否存&#xff0c;如果存在就不必要安装&#xff0c;如果不存在&#xff0c;需要安装对应的服务。 监…

day 68 增删改查 语法

1 普通正则 2 分组正则 url(r/blog/(\d)/(\d),views.blog) blog(request,arq1,arq2) 按照位置传参 3 分组命名 url(r/blog/(?P<year>\d)/(?P<month>\d),views.blog) blog(request,year,month) 4 用name 指定别名 url(r/blog/(?P<year>\d)/(?P…

编译器入门 语法分析器 java_从零开始写个编译器吧 - Parser 语法分析器

Parser(语法分析器)的编写相对于 Tokenizer (词法分析器)要复杂得多&#xff0c;因此&#xff0c;在编写之前可能也会铺垫得更多一些。当然&#xff0c;本系列旨在“写出”一个编译器&#xff0c;所以理论方面只会简单介绍 tao 语言所涉及的部分。之前的几章中&#xff0c;我纯…

Spring-data-jpa常用方法

转载于:https://www.cnblogs.com/summary-2017/p/7904926.html