尽管作为离线调试方式的 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
注意: 这个命令与 gdb 的 next 语句不同; 它更像 gdb 的 finish。
要检查内存中的数据, 可以用 (下面是一个例子):
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>.