10.5 使用 DDB 进行在线内核调试

  尽管作为离线调试方式的 gdb -k 提供了非常高级的用户界面, 但它仍然有许多无法完成的工作。 最重要的几项功能是设置断点, 以及以单步方式执行内核代码。

  如果您需要对内核进行较为底层的调试, 则可以利用称为 DDB 的在线调试器。 它能够让您设置断点、 单步执行内核函数, 查看或修改内核变量等。 然而, 它不能访问内核源代码文件, 而且只能访问全局和静态符号, 而不像 gdb 那样能访问全部调试符号。

  要配置您的内核使其包含 DDB, 需要在您的内核配置文件中加入

options DDB
然后重新编译。 (参见 FreeBSD 使用手册 以了解如何配置 FreeBSD 内核的进一步详情)。

注意: 如果您使用的引导块版本较早, 则可能完全无法加载调试符号。 遇到这种情况时请更新引导块; 新版本的引导块能够自动加载 DDB 符号了。

  只要使用了包含 DDB 功能的内核, 就可以通过多种途径进入 DDB 了。 第一种, 同时也是最简单的方式, 是在启动时指定引导参数 -d。 这样, 内核就会以调试方式启动, 并在开始检测设备之前进入 DDB。 这样一来, 即使是那些用于设备检测/挂接的函数, 也都能很容易地进行调试了。

  第二种情形, 是在系统启动之后进入调试器。 有两种简单的方法来达到这一目的。 如果您希望从命令行进入调试器, 只需简单地输入下面的命令:

# sysctl debug.enter_debugger=ddb

  另外, 如果您能操作系统的控制台, 也可以使用一组热键。 默认情况下, 可以通过连续按下 Ctrl+Alt+ESC 来进入调试器。 对 syscons 而言, 还可以重新映射具体的按键序列, 因此您应仔细检查来确认实际的按键。 此外, 在使用串口控制台时, 也可以使用串口线的 BREAK 来进入 DDB (在内核配置文件中, 需要加入 options BREAK_TO_DEBUGGER)。 这一选项并非默认值, 因为许多串口适配器会莫名其妙地生成 BREAK, 例如在拔下电缆时。

  第三种方式是, 如果事先对内核进行了配置,在发生 panic 时, 就会自动进入 DDB。 显然, 将一台无人看管的服务器的内核进行这样的配置, 不是一项理性的做法。

  DDB 的命令和某些 gdb 命令大体类似。 您需要做的第一件事可能是设置断点:

b function-name
b address

  默认情况下, 调试器接受十六进制的数字。 不过, 为了与符号的名字相区分, 以字母 a-f 开头的数字必须带有 0x 前缀 (对其他数字而言, 这是可选的)。 除此之外, 也可以使用简单的表达式, 例如: function-name + 0x103

  要使被中断的内核继续执行下去, 只需简单地输入:

c

  要查看调用堆栈, 则可以使用:

trace

注意: 需要说明的是, 如果您是使用热键进入 DDB, 则内核正在执行中断服务, 因而此时的调用堆栈可能用处不大。

  要去掉一处断点, 可以使用:

del
del address-expression

  第一种形式可以在遇到断点之后使用, 起作用是删除当前的断点。 第二种形式可以用来删除任意的断点, 但您需要精确地指定地址; 这些地址可以通过下面的命令来获得:

show b

  要单步执行内核, 则可以使用:

s

  这个命令会跟入函数, 但您也可以让 DDB 跟踪指令的执行, 直到对应的返回语句为止:

n

注意: 这个命令与 gdbnext 语句不同; 它更像 gdbfinish

  要检查内存中的数据, 可以用 (下面是一个例子):

x/wx 0xf0133fe0,40
x/hd db_symtab_space
x/bc termbuf,10
x/s stringbuf
来完成对 字/半字/字节 的访问, 并使用 十六进制/十进制/字符/字符串 的格式来显示。 在逗号后的数字是对象的数量。 要显示接下来的 0x10 项, 可以简单地使用:

x ,10

  类似地, 使用

x/ia foofunc,10
可以对 foofunc 的前 0x10 条指令进行反汇编, 并同时显示它们相对于 foofunc 起点的偏移量。

  要修改内存, 应使用 write 命令:

w/b termbuf 0xa 0xb 0
w/w 0xf0010030 0 0

  这个命令的修饰参数 (b/h/w) 指定了将要写的数据的尺寸, 其后的第一个表达式表示将要写入的地址, 而余下的责备认为是写入连续内存位置的数据。

  如果需要知道寄存器的当前内容, 使用:

show reg

  除此之外, 也可以显示单个寄存器的值, 例如:

p $eax
并修改之:

set $eax new-value

  如果希望从 DDB 调用某个内核函数, 可以简单地使用:

call func(arg1, arg2, ...)

  调试器将显示其返回值。

  如果想查看 ps(1) 风格的进程表, 使用:

ps

  了解了内核出现了什么问题之后, 一般会希望重新启动系统。 您应牢记的一点是, 取决于内核之前出现问题的严重程度, 内核中的某些组件可能已经无法正常工作。 此时, 应执行下列操作之一来关闭和重新启动系统:

panic

  这会让内核执行转存操作并重新启动, 这样, 您就可以在之后使用 gdb 进行更高级的分析了。 这个命令通常还需要使用一个 continue 语句才能够完成。

call boot(0)

  这个命令可能是完好地关闭正在运行的系统, sync() 所有的磁盘, 最后重新启动的一种好办法。 假如内核中的磁盘和文件系统接口没有遭到破坏的话, 这样做能够几乎完全正常地关闭系统。

call cpu_reset()

  这是在发生重大问题时的终极解决方法, 其作用与按下复位按钮是一样的。

  假如需要简短的命令介绍, 可以使用:

help

  然而, 我们强烈建议您打印一份 ddb(4) 的联机手册之后再开始调试, 因为在单步执行内核时, 查看联机手册是很麻烦的。

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.