Solaris 模块调试器指南

第 6 章 执行控制

MDB 提供了用于控制和跟踪实时运行程序(包括用户应用程序以及实时操作系统内核和设备驱动程序)执行的工具。可以使用 mdb 命令控制已运行的用户进程或者在调试器的控制下创建新进程。可以引导或装入 kmdb 以控制操作系统内核本身的执行或者调试设备驱动程序。本章介绍可以用于控制目标执行的内置 dcmd。除非另行说明,否则可以在 mdbkmdb 中使用这些命令。仅与 kmdb 中执行控制相关的其他主题将在第 7 章,内核执行控制中讨论。

执行控制

MDB 提供了一种简单的执行控制模型:可以使用 ::run 从调试器内启动目标进程,或者MDB 可以使用 :A::attach-p 命令行选项(请参见第 5 章,内置命令)附加到现有进程。或者,可以使用 kmdb 引导内核,也可以在以后装入 kmdb。在任一情况下,用户都可以指定跟踪的软件事件的列表。每次跟踪的事件在目标程序中发生时,目标中的所有线程都会停止,触发该事件的线程会被选为代表线程,并且控制返回到调试器。将目标程序设置为运行后,即可通过键入用户定义的中断字符(通常为 Ctrl-C)将控制以异步方式返回到调试器。

软件事件是指调试器所观察到的目标程序中的状态转换。例如,调试器可能会观察到程序计数器寄存器到相关值(断点)的转换或特定信号的传送。

软件事件说明符是指调试器所使用的软件事件类的说明,用于检测目标程序以便观察这些事件。 ::events dcmd 用于列出软件事件说明符。 一组标准属性与每个事件说明符相关联,如内置 dcmd::events 之下所述。

调试器可以观察各种不同的软件事件,包括断点、监视点、信号、计算机故障和系统调用。 使用 ::bp::fltbp:: sigbp::sysbp::wp 可以创建新说明符。每个说明符都具有关联的回调(要执行的 MDB 命令字符串,就好像已在命令提示符下键入了它一样)和一组属性,如内置 dcmd::events 之下所述。可以为同一事件创建任意数量的说明符,每个说明符都具有不同的回调和属性。使用 ::events dcmd 可以显示跟踪的事件和对应事件说明符的属性的当前列表。 事件说明符属性是作为内置 dcmd::events::evset dcmd 说明的一部分定义的。

内置 dcmd中所述的执行控制的内置 dcmd 始终可用,但是如果将其应用于不支持执行控制的目标,则会发出一条错误消息,表明其不受支持。

事件回调

通过 ::evset dcmd 和用于事件跟踪的 dcmd,可以将事件回调与每个事件说明符关联(使用 -c 选项)。事件回调是指用于表示在目标中发生对应事件时要执行的 MDB 命令的字符串。 系统将执行这些命令,就好像已在命令提示符下键入了它们一样。执行每个回调之前,会将 dot 变量设置为代表线程的程序计数器的值,并且将 hits 变量设置为与此说明符匹配的次数(包括当前匹配)。

如果事件回调本身包含一个或多个用于继续执行目标的命令(例如 ::cont::step),则这些命令不会立即继续执行目标并等待其再次停止。 相反,在某个事件回调内,用于继续执行的 dcmd 会注意到继续操作现在处于暂挂状态,然后会立即返回。因此,如果某个事件回调中包含多个 dcmd,则用于单步执行或继续执行的 dcmd 应该是指定的最后一个命令。执行所有事件回调后,如果所有匹配的事件回调都请求继续操作,则目标将立即恢复执行。如果请求的继续操作相冲突,则优先级最高的操作将确定发生的继续类型。优先级从高到低的顺序如下:单步执行、步过(下一个)、步出、继续。

线程支持

MDB 提供了用于检查与目标关联的每个线程的栈和寄存器的工具。持久性 "thread" 变量包含当前代表线程的标识符。线程标识符的格式取决于目标。::regs::fpregs dcmd 可以用于检查代表线程的寄存器集或其他线程的寄存器集(如果其寄存器集当前可用)。此外,代表线程的寄存器集会作为一组命名变量导出。 用户可以通过将 > dcmd 应用于对应的命名变量来修改一个或多个寄存器的值。

MDB 内核目标会将对应的内部线程结构的虚拟地址导出作为指定线程的标识符。此地址与操作系统源代码中的 kthread_t 数据结构相对应。使用 kmdb 时,运行 kmdb 的 CPU 的 CPU 标识符会存储在 cpuid 变量中。

MDB 进程目标为检查使用本机 lwp_* 接口、/usr/lib/libthread.so/usr/lib/libpthread.so 的多线程用户进程提供了适当的支持。调试实时用户进程时,MDB 将检测单线程进程是通过 dlopen 打开还是关闭 libthread,并将自动调整其所使用的线程模型视图。 进程目标线程标识符将与代表线程的 lwpid_tthread_tpthread_t 对应,具体取决于应用程序所使用的线程模型。

如果 MDB 正在调试用户进程目标,并且目标可利用编译器支持的线程局部存储空间,则 MDB 会自动将引用线程局部存储空间的符号名称评估为对应于当前代表线程的存储空间地址。::tls 内置 dcmd 可以用于显示除代表线程之外的线程的符号值。

内置 dcmd

[ addr ] ::bp [+/-dDestT] [-c cmd] [-n count] sym ...
addr :b [cmd ... ]

用于在指定的位置设置断点。 ::bp dcmd 可在指定的每个地址或符号上设置断点,其中包括 dcmd 前面的显式表达式所指定的可选地址,以及 dcmd 后面的每个字符串或即时值。参数可以是表示特定的相关虚拟地址的符号名称或即时值。如果指定了符号名称,则它可能引用还无法在目标进程中评估的符号:即该符号可能包含尚未打开的装入对象中的对象名称和函数名称。 在这种情况下,断点将会延迟,在装入与指定名称匹配的对象之前,它在目标中不会处于活动状态。打开装入对象时,将自动启用断点。共享库中所定义的符号上的断点应该始终使用符号名称而不是地址表达式进行设置,因为地址可能会引用对应的过程链接表 (Procedure Linkage Table, PLT) 项而不是实际的符号定义。如果随后将 PLT 项解析为实际的符号定义,则运行时链接编辑器可能会覆写在该 PLT 项上设置的断点。如本节中稍后所述,-d-D-e-s-t-T-c-n 选项具有与用于 ::evset dcmd 时相同的含义。如果使用的是 dcmd 的 :b 形式,则仅会在 dcmd 前面的表达式所指定的虚拟地址上设置断点。 :b dcmd 后面的参数会串联在一起形成回调字符串。 如果此字符串包含元字符,则必须使用引号将此字符串引起来。

function ::call [ arg ... ]

调用操作系统内核中所定义的指定 function(仅在使用 kmdb 时可用)。function 表达式必须与其中一个已知内核模块的符号表中所定义的函数的地址匹配。如果指定的是表达式参数,则这些参数将通过值进行传递。如果指定的是字符串参数,则这些参数将通过引用进行传递。


注意 –

请务必谨慎使用 ::call 命令,决不可将其应用于产品化系统。操作系统内核不会为了执行指定的函数而恢复执行。因此,被调用的函数决不能利用任意内核服务,并且决不能由于任何原因而阻塞。必须完全了解使用此命令调用的任何函数的副作用。


::cont [SIG]
:c [SIG]

用于暂停调试器,继续执行目标程序,并等待它在相关软件事件发生后终止或停止。如果由于在启用了 -o nostop 选项的情况下将调试器附加到正在运行的程序,从而导致目标已在运行,则此 dcmd 仅等待目标在相关事件发生后终止或停止。如果将可选的信号名称或编号(请参见 signal(3HEAD) 手册页)指定为参数,则信号会在其恢复执行的过程中立即传送到目标。如果跟踪了 SIGINT 信号,则可通过键入用户定义的中断字符(通常为 ^C)将控制以异步方式返回到调试器。 此 SIGINT 信号会自动清除,下次继续执行此信号时目标不会观察到它。 如果当前没有任何目标程序正在运行,则 ::cont 将开始运行一个新程序,就好像通过 ::run 执行一样。

addr ::delete [id | all]
addr :d [id | all]

用于删除具有指定 id 号的事件说明符。缺省情况下,id 编号参数按十进制解释。 如果在 dcmd 前面指定了可选地址,则将删除与指定虚拟地址关联的所有事件说明符(例如,影响该地址的所有断点或监视点)。如果指定了特殊参数 "all",则将删除所有事件说明符,但标记为粘滞(T 标志)的说明符除外。 ::events dcmd 可用于显示事件说明符的当前列表。

::events [-av]
$b [-av]

用于显示软件事件说明符的列表。 针对每个事件说明符都会指定一个唯一的 ID 号,稍后可以使用该编号对其进行删除或修改。调试器可能还会启用自己的内部事件进行跟踪;仅当存在 -a 选项时,才会显示这些内部事件。 如果存在 -v 选项,则将显示更详细的内容,其中包括任何说明符不活动的原因。以下的 ::events dcmd 显示了示例输出:

> ::events

   ID S TA HT LM Description                              Action

----- - -- -- -- ---------------------------------------- -------------

[ 1 ] - T   1  0 stop on SIGINT                           -      

[ 2 ] - T   0  0 stop on SIGQUIT                          -

[ 3 ] - T   0  0 stop on SIGILL                           -

 ...

[ 11] - T   0  0 stop on SIGXCPU                          -

[ 12] - T   0  0 stop on SIGXFSZ                          -

[ 13] -     2  0 stop at libc`printf                      ::echo printf

>

以下讨论说明了每列的含义。使用 ::help events 可以显示此信息的摘要。

ID

事件说明符的标识符。如果启用了说明符,则将在方括号 [ ] 中显示标识符;如果禁用了说明符,则在圆括号 ( ) 中显示标识符;如果当前在与指定说明符匹配的事件上停止了目标程序,则在尖括号 < > 中显示标识符。

S

事件说明符的状态。 状态将是以下符号之一:

事件说明符处于空闲状态。如果没有任何目标程序运行,则表明所有说明符都处于空闲状态。目标程序正在运行时,如果无法评估说明符,则表明该说明符可能处于空闲状态(如尚未装入的共享对象中的延迟断点)。 

事件说明符处于活动状态。继续执行目标时,调试器将检测到此类型的事件。 

事件说明符处于待命状态。此状态意味着目标当前正在运行,并且可检测到此类型的事件。仅当使用 -o nostop 选项将调试器附加到正在运行的程序时,此状态才可见。

由于出现操作系统错误,事件说明符未处于待命状态。 可以使用 ::events -v 选项显示有关检测失败原因的更多信息。

TA

“临时”、“粘滞”和“自动”事件说明符的属性。可能显示以下符号中的一个或多个:

事件说明符是临时的,无论它是否匹配,下次目标停止时都会将其删除。 

事件说明符是粘滞说明符,不会被 ::delete all:z 删除。通过将其 id 号显式指定为 ::delete,可以删除该说明符。

命中计数等于命中限制时,将自动禁用事件说明符。 

命中计数等于命中限制时,将自动删除事件说明符。 

命中计数等于命中限制时,目标将自动停止。 

HT

当前的命中计数。 此列显示自创建此事件说明符以来对应软件事件在目标中发生的次数。

LM

当前的命中限制。此列显示自动禁用、自动删除或自动停止行为将生效的命中计数限制。可以使用 ::evset dcmd 配置这些行为。

Description

对给定的说明符所匹配的软件事件类型的说明。

Action

对应软件事件发生时要执行的回调字符串。系统将执行此回调,就好像已在命令提示符下键入了它一样。

id ::evset [+/-dDestT] [-c cmd] [-n count] id ...

用于修改一个或多个软件事件说明符的属性。将设置由 dcmd 前面的可选表达式和 dcmd 后面的可选参数列表所标识的每个说明符的属性。除非指定显式基数,否则参数列表将被解释为十进制整数的列表。::evset dcmd 可识别以下选项:

-d

当命中计数达到命中限制时,禁用事件说明符。如果指定了该选项的 +d 形式,则会禁用此行为。禁用事件说明符后,调试器便会删除任何对应检测并忽略对应的软件事件,直到随后重新启用说明符为止。 如果 -n 选项不存在,则会立即禁用说明符。

-D

当命中计数达到命中限制时,删除事件说明符。如果指定了该选项的 +D 形式,则会禁用此行为。-D 选项优先于 -d 选项。可以使用 -n 选项配置命中限制。

-e

启用事件说明符。如果指定了该选项的 +e 形式,则会禁用说明符。

-s

当命中计数达到命中限制时,停止目标程序。如果指定了该选项的 +s 形式,则会禁用此行为。 -s 行为通知调试器就像在每次执行说明符回调后发出 ::cont 那样进行操作,但第 N 次执行除外,其中 N 是说明符命中限制的当前值。-s 选项优先于 -D 选项和 -d 选项。

-t

将事件说明符标记为临时说明符。无论临时说明符是否由于对应于指定说明符的软件事件而停止,下次目标停止时都会自动将其删除。如果指定了该选项的 +t 形式,则会删除临时标记。-t 选项优先于 -T 选项。

-T

将事件说明符标记为粘滞。 粘滞说明符不会被 ::delete all:z 删除。通过将对应的说明符 ID 指定为 ::delete 的显式参数,可以将其删除。如果指定了该选项的 +T 形式,则会删除粘滞属性。缺省的一组事件说明符最初都标记为粘滞。

-c

每次目标程序中发生对应软件事件时,执行指定的 cmd 字符串。可以使用 ::events 显示当前的回调字符串。

-n

将命中限制的当前值设置为 count。如果当前未设置任何命中限制,并且 -n 选项未附带 -s-D,则命中限制将被设置为 1。

使用 ::help evset 可以显示此信息的摘要。

flt ::fltbp [+/-dDestT] [-c cmd] [-n count] flt ...

用于跟踪指定的计算机故障。使用 dcmd 前面的可选故障编号或者 dcmd 后面的故障名称或编号的列表(请参见 <sys/fault.h>)来标识故障。-d-D-e-s-t-T-c-n 选项具有与用于 ::evset dcmd 时相同的含义。::fltbp 命令仅适用于用户进程调试。

signal :i

如果目标是实时用户进程,请忽略指定的信号,并允许以透明方式将其传送到目标。将从跟踪的事件列表中删除跟踪指定信号传送的所有事件说明符。缺省情况下,忽略的信号集会初始化为导致进程缺省情况下转储核心的信号集的补充(请参见 signal(3HEAD) 手册页),但 SIGINT 除外,缺省情况下将跟踪此信号。:i 命令仅适用于用户进程调试。

$i

显示调试器忽略的将直接由目标处理的信号的列表。使用 ::events dcmd 可以获取有关跟踪的信号的更多信息。$i 命令仅适用于用户进程调试。

::kill
:k

如果目标是实时用户进程,则强制将其终止。如果目标是调试器使用 ::run 创建的,则在调试器退出时,也将强制终止该目标。::kill 命令仅适用于用户进程调试。

$l

如果目标是用户进程,则列显代表线程的 LWPID。

$L

如果目标是用户进程,则列显该目标中每个 LWP 的 LWPID。

::next [SIG]
:e [SIG]

一次执行目标程序的一条指令,但步过子例程调用。如果将可选的信号名称或编号(请参见 signal(3HEAD) 手册页)指定为参数,则信号会在其恢复执行的过程中立即传送到目标。如果当前没有任何目标程序正在运行,则 ::next 将开始运行新程序,就好像是通过 ::run 执行一样,然后在第一条指令处停止。

::run [args ... ]
:r [args ... ]

用于通过指定的参数开始运行新的目标程序,并附加到该程序。这些参数不是通过 shell 解释的。如果调试器已经在检查实时运行的程序,则它将首先与该程序分离,就好像是通过 ::release 执行一样。

[signal] ::sigbp [+/-dDestT] [-c cmd] [-n count] SIG ...
[signal] :t [+/-dDestT] [-c cmd] [-n count] SIG ...

用于跟踪指定信号的传送。 使用 dcmd 前面的可选信号编号或者 dcmd 后面的信号名称或编号的列表(请参见 signal(3HEAD))来标识信号。 -d-D-e-s-t-T-c-n 选项具有与用于 ::evset dcmd 时相同的含义。 最初会跟踪缺省情况下导致进程转储核心的信号集(请参见 signal(3HEAD))和 SIGINT。::sigbp 命令仅适用于用户进程调试。

::step [branch | over | out] [SIG]
:s SIG
:u SIG

用于执行目标程序的一条指令。如果将可选的信号名称或编号(请参见 signal(3HEAD) 手册页)指定为参数并且目标是用户进程,则信号会在其恢复执行的过程中立即传送到目标。如果指定了可选的 branch 参数,则在用于对处理器控制流进行分支的下一条指令之前,目标程序将继续执行。仅当针对启用了相应处理器特定功能的 x86 系统使用 kmdb 时,::step branch 功能才可用。如果指定了可选的 over 参数,则 ::step 将跳过子例程调用。 ::step over 参数与 ::next dcmd 相同。如果指定了可选的 out 参数,则在代表线程从当前函数返回之前,目标程序将继续执行。如果当前没有任何目标程序正在运行,则 ::step over 将开始运行新程序,就好像是通过 ::run 执行一样,然后在第一条指令处停止。:s dcmd 与 ::step 相同。:u dcmd 与 ::step out 相同。

[syscall] ::sysbp [+/-dDestT] [-io] [-c cmd] [-n count] syscall ...

用于跟踪指定系统调用的进出信息。 使用 dcmd 前面的可选系统调用编号或者 dcmd 后面的系统调用名称或编号的列表(请参见 <sys/syscall.h>)来标识系统调用。 如果指定了 -i 选项(缺省值),则事件说明符会在每个系统调用进入内核时触发。 如果指定了 -o 选项,则事件说明符会在从内核退出时触发。-d-D-e-s-t-T-c-n 选项具有与用于 ::evset dcmd 时相同的含义。::sysbp 命令仅适用于用户进程调试。

addr [,len] ::wp [+/-dDestT] [-rwx] [-ip] [-c cmd] [-n count]
addr [,len]:a [cmd... ]
addr [,len]:p [cmd... ]
addr [,len] :w [cmd... ]

用于在指定的地址上设置监视点。通过在 dcmd 前面指定可选的重复计数,可以设置监视的区域的长度(以字节为单位)。如果未显式设置长度,则缺省长度为 1 个字节。使用 ::wp dcmd 可将监视点配置为由读取(-r 选项)、写入(-w 选项)或执行(-x 选项)访问的任意组合操作触发。 -d-D-e-s-t-T-c-n 选项具有与用于 ::evset dcmd 时相同的含义。可以使用 -i 选项指明应该在 I/O 端口的地址上设置监视点(仅当在 x86 系统上使用 kmdb 时可用)。可以使用 -p 选项指明应该将指定的地址解释为物理地址(仅在使用 kmdb 时可用)。:a dcmd 用于在指定的地址上设置读取访问监视点。:p dcmd 用于在指定的地址上设置执行访问监视点。 :w dcmd 用于在指定的地址上设置写入访问监视点。 :a:p:w dcmd 后面的参数会串联在一起形成回调字符串。 如果此字符串包含元字符,则必须使用引号将此字符串引起来。

:z

用于从跟踪的软件事件列表中删除所有事件说明符。另外,也可以使用 ::delete 删除事件说明符。

exec 交互

如果受控的用户进程成功执行 exec(2),则可通过 ::set -o follow_exec_mode 选项控制调试器的行为,如命令行选项摘要中所述。如果调试器和被调试的进程 (victim process) 具有相同的数据模型,则“停止”和“跟随”模式可确定 MDB 是自动继续执行目标还是返回到 exec 后面的调试器提示符下。如果调试器和被调试的进程具有不同的数据模型,则“跟随”行为导致 MDB 使用相应的数据模型自动地重新执行 MDB 二进制代码并重新附加到该进程,从 exec 返回时仍然被停止。 并非所有在此重新执行中生成的调试器状态都会保留。

如果在 32 位数据模型中被调试的进程对 64 位程序执行 exec,则 MDB 在“停止”模式下将返回到命令提示符,但调试器将无法再检查进程,因为现在它使用的是 64 位数据模型。要恢复调试,请执行 ::release -a dcmd,退出 MDB,然后执行 mdb -p pid 将 64 位调试器重新附加到进程。

如果在 64 位数据模型中被调试的进程执行 32 位程序,则命令提示符中将返回“停止”,但调试器将仅提供有限的功能用于检查新进程。 所有内置 dcmd 都将按照说明的那样运行,但是可装入的 dcmd 则不会,因为它们不会执行结构的数据模型转换。 如前所述,为了恢复完整的调试功能,用户应该释放调试器再将其重新附加到进程。

与作业控制交互

如果将调试器附加到作业控制所停止的用户进程(即,该用户进程响应 SIGTSTP、SIGTTIN 或 SIGTTOU 而停止),则用于继续执行操作的 dcmd 继续执行进程时,可能无法将该进程设置为再次运行。 如果被调试的进程是同一会话的成员(即,该进程与 MDB 共享同一控制终端),则 MDB 会尝试将关联的进程组置于前台,并响应 SIGCONT 继续执行进程,以便将其从作业控制停止中恢复。 MDB 与此类进程分离时,它在退出之前会将进程组恢复到后台。如果被调试的进程不是同一会话的成员,则 MDB 无法安全地将进程组置于前台,因此它将继续执行与调试器有关的进程,但作业控制会继续停止进程。在这种情况下,MDB 将列显一条警告,用户必须从相应的 shell 发出 fg 命令才能恢复进程。

进程的附加和释放

如果 MDB 附加到正在运行的用户进程,则会停止该进程并使其一直处于停止状态,直到应用一个用于继续执行操作的 dcmd 或调试器退出为止。 如果在使用 -p 将调试器附加到某个进程之前或者发出 ::attach:A 命令之前启用了 -o nostop 选项,MDB 将会附加到该进程但不会将其停止。在进程仍然运行的同时,可以按通常那样对其进行检查(尽管结果会不一致),并且可以启用断点或其他跟踪标志。 如果在进程运行的同时执行 :c::cont dcmd,则调试器将等待进程停止。如果没有发生跟踪的软件事件,则用户可以在 :c::cont 之后发送中断字符 (^C),以强制进程停止并将控制返回到调试器。

执行 :R::release:r::run$q::quit dcmd 时,或者调试器因遇到 EOF 或信号而终止时,MDB 将释放当前运行的进程(如果有)。如果进程最初是调试器使用 :r::run 创建的,则在释放该进程时会强制将其终止,就好像是通过 SIGKILL 执行一样。 如果在将 MDB 附加到进程之前该进程已经运行,则在释放该进程时会将其设置为再次运行。使用 ::release -a 选项,可以释放进程并使其保持停止和放弃状态。