本节介绍可以应用于设备驱动程序的两个调试程序。《Solaris 模块调试器指南》中详细介绍了这两个调试程序。
kmdb(1) 内核调试程序可提供典型的运行时调试程序功能,如断点、监视点和单步执行。kmdb 调试程序取代了以前发行版中的 kadb。除新功能外,在 kmdb 中还可以使用先前在 kadb 中可用的命令。kadb 只能在引导时装入,而 kmdb 可随时装入。由于 kmdb 调试程序可进行执行控制,因此它是用于实时、交互调试的首选方法。
mdb(1) 模块调试程序 作为实时调试程序比 kmdb 的功能要有限一些,但 mdb 具有很多可用于事后调试的功能。
kmdb 和 mdb 调试程序的用户界面大部分是一样的。因此,许多调试方法都可在这两种工具中使用相同命令来应用。这两种调试程序都支持宏、dcmd 和 dmod。dcmd(读作为 dee-command)是调试程序中的例程,它可以访问当前目标程序的任何属性。dcmd 可在运行时动态装入。dmod(调试程序模块的缩写)是可以装入以提供非标准行为的 dcmd 包。
mdb 和 kmdb 都可向后兼容传统调试程序(如 adb 和 kadb)。mdb 调试程序可以执行可用于 kmdb 的所有宏以及用于 adb 的任何用户定义的传统宏。有关在何处查找标准宏集的信息,请参见《Solaris 模块调试器指南》。
事后分析为驱动程序开发者提供了许多益处。多个开发者可以并行检查一个问题。可以针对单个崩溃转储使用调试程序的多个实例。可以脱机执行分析,以便在可能的情况下使崩溃的系统恢复运行。事后分析允许以 dmod 形式使用用户开发的调试程序功能。Dmod 可为实时调试程序(如 kmdb)捆绑内存密集程度过高的功能。
如果装入 kmdb 时系统出现紧急情况,则控制权会传递给调试程序,以便立即进行检查。如果不适合使用 kmdb 分析当前问题,则使用 :c 继续执行并保存崩溃转储不失为一种好的策略。系统重新引导时,可以使用 mdb 对已保存的崩溃转储执行事后分析。此过程类似于从进程核心转储文件调试应用程序崩溃。
在 Solaris 操作系统的早期版本中,adb(1) 是推荐用于事后分析的工具。在当前的 Solaris 操作系统中,mdb(1) 是推荐用于事后分析的工具。mdb() 功能集不仅包含传统 crash (1M) 实用程序中的命令集,还具有更多功能。Solaris 操作系统中不再提供 crash 实用程序。
kmdb 调试程序是可提供以下功能的交互式内核调试程序:
控制内核执行
检查内核状态
实时修改代码
本节假定您已熟悉 kmdb 调试程序。本节重点介绍在设备驱动程序设计中非常有用的 kmdb 功能。要详细了解如何使用 kmdb,请参阅kmdb(1) 手册页和《Solaris 模块调试器指南》。如果您熟悉 kadb,请参阅 kadb(1M) 手册页以了解 kadb 与 kmdb 的主要差别。
可以任意装入和卸载 kmdb 调试程序。《Solaris 模块调试器指南》中包括装入和卸载 kmdb 的说明。为了安全和方便起见,强烈建议使用替代内核进行引导。如本节中所述,在 SPARC 平台与 x86 平台上引导过程略有不同。
缺省情况下,当 kmdb 运行时,kmdb 使用 CPU ID 作为提示符。在本章的示例中,除非另有指定,否则使用 [0] 作为提示符。
使用以下任一命令通过 kmdb 和替代内核引导 SPARC 系统:
boot kmdb -D kernel.test/sparcv9/unix boot kernel.test/sparcv9/unix -k |
使用以下任一命令通过 kmdb 和替代内核引导 x86 系统:
b kmdb -D kernel.test/unix b kernel.test/unix -k |
使用 bp 命令设置断点,如以下示例中所示。
[0]> myModule`myBreakpointLocation::bp |
如果尚未装入目标模块,则会显示指示这一情况的错误消息,并且不会创建断点。在这种情况下,可以使用延迟断点。装入指定的模块时,会自动激活延迟断点。通过在 bp 命令后面指定目标位置可以设置延迟断点。以下示例对延迟断点进行了说明。
[0]>::bp myModule`myBreakpointLocation |
有关使用断点的更多信息,请参见《Solaris 模块调试器指南》。也可以通过键入以下任意一行来获取帮助:
> ::help bp > ::bp dcmd |
kmdb(1M) 调试程序支持可用于显示内核数据结构的宏。可以使用 $M 来显示 kmdb 宏。宏的使用形式为:
[ address ] $<macroname |
这些宏所显示的信息以及显示信息所用的格式都不构成接口。因此,该信息和格式可以随时更改。
下表中的 kmdb 宏对于设备驱动程序的开发者特别有用。为方便起见,给出了传统的宏名称(如果适用)。
表 22–1 kmdb 宏
Dcmd |
传统宏 |
说明 |
---|---|---|
::devinfo |
devinfo devinfo_brief devinfo.prop |
列显设备节点的摘要 |
::walk devinfo_parents |
devinfo.parent |
遍历设备节点的祖先 |
::walk devinfo_sibling |
devinfo.sibling |
遍历设备节点的同级节点 |
::minornodes |
devinfo.minor |
列显与给定设备节点对应的次要节点 |
::major2name |
列显绑定到给定设备节点的设备的名称。 |
|
::devbindings |
列显绑定到给定设备节点或主设备号的设备节点。 |
::devinfo dcmd 显示节点状态,其值为以下所列之一:
驱动程序的 attach(9E) 例程成功返回。
节点已绑定到驱动程序,但尚未调用驱动程序的 probe(9E) 例程。
父结点已为驱动程序指定总线地址。特定于实现的初始化已完成。此时尚未调用驱动程序的 probe(9E) 例程。
设备节点已链接至内核的设备树中,但系统尚未找到用于此节点的驱动程序。
驱动程序的 probe(9E) 例程成功返回。
设备已完全配置。
mdb(1) 模块调试程序可以应用于以下文件类型:
实时操作系统组件
操作系统崩溃转储
用户进程
用户进程核心转储
对象文件
mdb 调试程序可为分析内核问题提供复杂的调试支持。本节概述 mdb 功能。有关 mdb 的完整讨论,请参阅《Solaris 模块调试器指南》。
尽管 mdb 可用来改变实时内核状态,但 mdb 缺少 kmdb 提供的内核执行控制。因此,kmdb 是进行运行时调试的首选调试程序,而 mdb 调试程序更多用于静态情况。
mdb 的提示符为 >。
mdb 为实现调试程序模块提供了大量编程 API,从而使驱动程序开发者可以实现自定义调试支持。mdb 调试程序还提供了许多可用功能,如命令行编辑、命令历史记录、输出页面调度程序和联机帮助。
不应再使用 adb 宏。该功能已被 mdb 中的 dcmd 替代。
mdb 调试程序提供了一组丰富的模块和 dcmd。借助这些工具,可以调试 Solaris 内核、任何关联的模块以及设备驱动程序。通过这些功能可以执行一些任务,如:
阐明复杂的调试查询
查找特定线程分配的所有内存
列显内核 STREAM 的直观图
确定特定地址所引用的结构类型
在内核中查找已泄漏的内存块
分析内存以查找栈跟踪
将 dcmd 组装到用于创建自定义操作且名为 dmod 的模块中
首先切换到崩溃目录,键入 mdb 并指定系统崩溃转储,如以下示例所示。
% cd /var/crash/testsystem % ls bounds unix.0 vmcore.0 % mdb unix.0 vmcore.0 Loading modules: [ unix krtld genunix ufs_log ip usba s1394 cpc nfs ] > ::status debugging crash dump vmcore.0 (64-bit) from testsystem operating system: 5.10 Generic (sun4u) panic message: zero dump content: kernel pages only |
当 mdb 以 > 提示符进行响应时,便可运行命令。
要检查实时系统中正在运行的内核,请按如下所示从系统提示符处运行 mdb。
# mdb -k Loading modules: [ unix krtld genunix ufs_log ip usba s1394 ptm cpc ipc nfs ] > ::status debugging live kernel (64-bit) on testsystem operating system: 5.10 Generic (sun4u) |
本节提供了有用的调试任务示例。除非特别说明,否则本节中的任务均可使用 mdb 或 kmdb 来执行。本节假定您已了解 kmdb 和 mdb 的基本使用知识。请注意,此处提供的信息取决于所使用系统的类型。这些示例是使用运行 64 位内核的 Sun Blade 100 工作站生成的。
由于修改内核结构中的数据会导致无法恢复的数据损毁,因此务必要格外谨慎。请勿修改或依赖于不属于 Solaris DDI 结构中的数据。有关属于 Solaris DDI 的结构的信息,请参见 Intro(9S) 手册页。
kmdb 调试程序可按组或单独显示计算机寄存器。要按组显示所有寄存器,请按以下示例所示使用 $r。
[0]: $r g0 0 l0 0 g1 100130a4 debug_enter l1 edd00028 g2 10411c00 tsbmiss_area+0xe00 l2 10449c90 g3 10442000 ti_statetbl+0x1ba l3 1b g4 3000061a004 l4 10474400 ecc_syndrome_tab+0x80 g5 0 l5 3b9aca00 g6 0 l6 0 g7 2a10001fd40 l7 0 o0 0 i0 0 o1 c i1 10449e50 o2 20 i2 0 o3 300006b2d08 i3 10 o4 0 i4 0 o5 0 i5 b0 sp 2a10001b451 fp 2a10001b521 o7 1001311c debug_enter+0x78 i7 1034bb24 zsa_xsint+0x2c4 y 0 tstate: 1604 (ccr=0x0, asi=0x0, pstate=0x16, cwp=0x4) pstate: ag:0 ie:1 priv:1 am:0 pef:1 mm:0 tle:0 cle:0 mg:0 ig:0 winreg: cur:4 other:0 clean:7 cansave:1 canrest:5 wstate:14 tba 0x10000000 pc edd000d8 edd000d8: ta %icc,%g0 + 125 npc edd000dc edd000dc: nop |
调试程序会将每个寄存器值导出到与寄存器同名的一个变量中。如果读取该变量,则返回对应寄存器的当前值。如果写入该变量,则会更改关联的计算机寄存器值。以下示例将一台 x86 计算机上 %o0 寄存器的值由 0 更改为 1。
[0]> <eax=K c1e6e0f0 [0]> 0>eax [0]> <eax=K 0 [0]> c1e6e0f0>eax |
如果需要检查不同处理器的寄存器,则可使用 ::cpuregs dcmd。要检查的处理器的 ID 可以作为 dcmd 的地址或 -c 选项的值来提供,如以下示例所示。
[0]> 0::cpuregs %cs = 0x0158 %eax = 0xc1e6e0f0 kmdbmod`kaif_dvec %ds = 0x0160 %ebx = 0x00000000 |
以下示例从 SPARC 计算机上的处理器 0 切换到处理器 3。检查了寄存器 %g3,然后将其清除。为确认新值,再次读取 %g3。
[0]> 3::switch [3]> <g3=K 24 [3]> 0>g3 [3]> <g3 0 |
::findleaks dcmd 可对内核崩溃转储中的内存泄漏提供强大、有效的检测。必须启用一整套内核内存调试功能,::findleaks 才会有效。有关更多信息,请参见设置 kmem_flags 调试标志。在驱动程序开发和测试期间运行 ::findleaks,以检测泄漏内存从而浪费内核资源的代码。有关 ::findleaks 的完整讨论,请参见《Solaris 模块调试器指南》中的第 9 章 “使用内核内存分配器进行调试”。
泄漏内核内存的代码会使系统容易受到拒绝服务攻击。
mdb 调试程序提供了一个功能强大的 API,用于实现为调试驱动程序而自定义的调试程序功能。《Solaris 模块调试器指南》详细介绍了该编程 API。
SUNWmdbdm 软件包将 mdb 源代码示例安装在目录 /usr/demo/mdb 中。可以使用 mdb 来自动完成冗长的调试日常事务,或帮助验证驱动程序是否正常工作。还可以将 mdb 调试模块与驱动程序产品一起打包。通过打包,服务人员可在客户站点处使用这些功能。
Solaris 内核在可用 kmdb 或 mdb 检查的结构中提供数据类型信息。
kmdb 和 mdb dcmd 只能用于包含设计用于 mdb 的压缩符号调试信息的对象。此信息当前只能用于某些 Solaris 内核模块。必须安装 SUNWzlib 软件包,才能处理符号调试信息。
以下示例说明如何显示 scsi_pkt 结构中的数据。
> 7079ceb0::print -t 'struct scsi_pkt' { opaque_t pkt_ha_private = 0x7079ce20 struct scsi_address pkt_address = { struct scsi_hba_tran *a_hba_tran = 0x70175e68 ushort_t a_target = 0x6 uchar_t a_lun = 0 uchar_t a_sublun = 0 } opaque_t pkt_private = 0x708db4d0 int (*)() *pkt_comp = sd_intr uint_t pkt_flags = 0 int pkt_time = 0x78 uchar_t *pkt_scbp = 0x7079ce74 uchar_t *pkt_cdbp = 0x7079ce64 ssize_t pkt_resid = 0 uint_t pkt_state = 0x37 uint_t pkt_statistics = 0 uchar_t pkt_reason = 0 } |
数据结构的大小在调试中很有用。使用 ::sizeof dcmd 可获取结构的大小,如以下示例所示。
> ::sizeof struct scsi_pkt sizeof (struct scsi_pkt) = 0x58 |
结构中特定成员的地址在调试中也很有用。有几种方法可用来确定成员的地址。
使用 ::offsetof dcmd 可以获取结构中给定成员的偏移,如以下示例所示。
> ::offsetof struct scsi_pkt pkt_state offsetof (struct pkt_state) = 0x48 |
使用带 -a 选项的 ::print dcmd 可以显示结构中所有成员的地址,如以下示例所示。
> ::print -a struct scsi_pkt { 0 pkt_ha_private 8 pkt_address { ... } 18 pkt_private ... } |
如果结合使用 ::print 和 -a 选项来指定地址,则会显示每个成员的绝对地址。
> 10000000::print -a struct scsi_pkt { 10000000 pkt_ha_private 10000008 pkt_address { ... } 10000018 pkt_private ... } |
使用 ::print、::sizeof 和 ::offsetof dcmd,可在驱动程序与 Solaris 内核交互时调试问题。
通过此功能可访问原始内核数据结构。您可以检查任何结构,无论该结构是否显示为 DDI 的一部分。因此,应避免依赖于未显式构成 DDI 的任何数据结构。
这些 dcmd 只能用于包含设计用于 mdb 的压缩符号调试信息的对象。符号调试信息当前只能用于某些 Solaris 内核模块。必须安装 SUNWzlib(32 位)或 SUNWzlibx(64 位)解压缩软件,才能处理符号调试信息。无论是否包含 SUNWzlib 或 SUNWzlibx 软件包,kmdb 调试程序均可处理符号类型数据。
mdb 调试程序提供了用于显示内核设备树的 ::prtconf dcmd。::prtconf dcmd 的输出与 prtconf(1M) 命令的输出相似。
> ::prtconf 300015d3e08 SUNW,Sun-Blade-100 300015d3c28 packages (driver not attached) 300015d3868 SUNW,builtin-drivers (driver not attached) 300015d3688 deblocker (driver not attached) 300015d34a8 disk-label (driver not attached) 300015d32c8 terminal-emulator (driver not attached) 300015d30e8 obp-tftp (driver not attached) 300015d2f08 dropins (driver not attached) 300015d2d28 kbd-translator (driver not attached) 300015d2b48 ufs-file-system (driver not attached) 300015d3a48 chosen (driver not attached) 300015d2968 openprom (driver not attached) |
可以使用宏(如 ::devinfo dcmd)来显示节点,如以下示例所示。
> 300015d3e08::devinfo 300015d3e08 SUNW,Sun-Blade-100 System properties at 0x300015abdc0: name='relative-addressing' type=int items=1 value=00000001 name='MMU_PAGEOFFSET' type=int items=1 value=00001fff name='MMU_PAGESIZE' type=int items=1 value=00002000 name='PAGESIZE' type=int items=1 value=00002000 Driver properties at 0x300015abe00: name='pm-hardware-state' type=string items=1 value='no-suspend-resume' |
使用 ::prtconf 可以查看驱动程序在设备树中连接的位置,以及显示设备属性。还可以为 ::prtconf 指定详细 (-v) 标志,以显示每个设备节点的属性,如下所示。
> ::prtconf -v DEVINFO NAME 300015d3e08 SUNW,Sun-Blade-100 System properties at 0x300015abdc0: name='relative-addressing' type=int items=1 value=00000001 name='MMU_PAGEOFFSET' type=int items=1 value=00001fff name='MMU_PAGESIZE' type=int items=1 value=00002000 name='PAGESIZE' type=int items=1 value=00002000 Driver properties at 0x300015abe00: name='pm-hardware-state' type=string items=1 value='no-suspend-resume' ... 300015ce798 pci10b9,5229, instance #0 Driver properties at 0x300015ab980: name='target2-dcd-options' type=any items=4 value=00.00.00.a4 name='target1-dcd-options' type=any items=4 value=00.00.00.a2 name='target0-dcd-options' type=any items=4 value=00.00.00.a4 |
另一种查找驱动程序实例的方法是使用 ::devbindings dcmd。在给定驱动程序名称的情况下,该命令会显示指定驱动程序的所有实例的列表,如以下示例所示。
> ::devbindings dad 300015ce3d8 ide-disk (driver not attached) 300015c9a60 dad, instance #0 System properties at 0x300015ab400: name='lun' type=int items=1 value=00000000 name='target' type=int items=1 value=00000000 name='class_prop' type=string items=1 value='ata' name='type' type=string items=1 value='ata' name='class' type=string items=1 value='dada' ... 300015c9880 dad, instance #1 System properties at 0x300015ab080: name='lun' type=int items=1 value=00000000 name='target' type=int items=1 value=00000002 name='class_prop' type=string items=1 value='ata' name='type' type=string items=1 value='ata' name='class' type=string items=1 value='dada' |
调试驱动程序的常见问题是检索特定驱动程序实例的软状态。软状态使用 ddi_soft_state_zalloc(9F) 例程来分配。驱动程序可以通过 ddi_get_soft_state(9F) 获取软状态。软状态指针的名称是 ddi_soft_state_init (9F) 的第一个参数。根据名称,可以使用 mdb 通过 ::softstate dcmd 检索特定驱动程序实例的软状态:
> *bst_state::softstate 0x3 702b7578 |
在此示例中,::softstate 用来获取 bst 示例驱动程序的实例 3 的软状态。此指针引用由驱动程序使用的 bst_soft 结构,以便跟踪该实例的状态。
可以使用 kmdb 和 mdb 来修改内核变量或其他内核状态。使用 mdb 修改内核状态时要格外谨慎,因为 mdb 在进行修改前不会停止内核。使用 kmdb 可以原子方式进行成组修改,因为 kmdb 会在允许用户访问之前停止内核。mdb 调试程序只能进行单个原子修改。
务必要使用正确的格式指示符来进行修改。格式可以为:
w-将每个表达式值的最低 2 个字节写入从点所指定的位置开始的目标位置
W-将每个表达式值的最低 4 个字节写入从点所指定的位置开始的目标位置
Z-将每个表达式值的全部 8 个字节写入从点所指定的位置开始的目标位置
使用 ::sizeof dcmd 可以确定要修改的变量的大小。
以下示例使用值 0x80000000 覆写 moddebug 的值。
> moddebug/W 0x80000000 moddebug: 0 = 0x80000000 |