Sun Studio 12:使用 dbx 调试程序

第 6 章 设置断点和跟踪

发生事件时,可利用 dbx 停止进程、执行任意命令或打印信息。例如,断点便是最简单的事件。另外,错误、信号、系统调用、dlopen() 调用以及数据更改等都是事件。

跟踪可显示程序中事件的相关信息,如变量值变化。尽管跟踪的行为与断点的行为不同,但跟踪和断点的事件处理程序相似(请参见事件处理程序)。

本章说明如何设置、清除和列出断点和跟踪。有关设置断点和跟踪时可以使用的事件规范的完整信息,请参见设置事件规范。

本章由以下部分组成:

设置断点

在 dbx 中,可以使用下列三个命令设置断点:

stop、when 和 trace 命令都将事件规范(说明断点所基于的事件)当作参数。设置事件规范详细讨论了事件规范。

要设置机器级断点,请使用 stopi、 wheni 和 tracei 命令(请参见第 18 章,在机器指令级调试)。


注 –

调试使用 JavaTM 代码和 C JNI(Java Native Interface,Java 本地接口)代码或 C++ JNI 代码混合编写的应用程序时,可能需要在尚未装入的代码中设置断点。有关在此类代码中设置断点的信息,请参见在 JVM 软件尚未装入的代码上设置断点。


在源代码行设置 stop 断点

可以使用 stop at 命令在行号处设置断点,其中 n 是源代码行号,filename 是可选的程序文件名限定符。


(dbx) stop at filename: n

例如:


(dbx) stop at main.cc:3
:

如果指定的行不是可执行的源代码行,dbx 会在下一个可执行源代码行处设置断点。如果没有可执行源代码行,dbx 会发出错误。

可以使用 file 命令设置当前文件并使用 list 命令列出要在其中停止的函数来确定要停止在那里的行。然后使用 stop at 命令在源代码行设置断点:


(dbx) file t.c
(dbx) list main
10    main(int argc, char *argv[])
11    {
12        char *msg = "hello world\n";
13        printit(msg);
14    }
(dbx) stop at 13

有关指定 at 位置事件的更多信息,请参见at [filename: ]line_number。

在函数中设置 stop 断点

可以使用 stop in 命令在函数中设置断点:


(dbx) stop in function

In Function 断点用于在过程或函数中第一个源代码行的开头处暂停程序执行。

dbx 应能确定引用的是哪个变量或函数,但下列情况除外:

假设有下面一组声明:


int foo(double);
int foo(int);
int bar();
class x {
   int bar();
};

在非成员函数处停止时,可以键入:


stop in foo(int)

在全局 foo(int) 处设置断点。

要在成员函数上设置断点,可以使用以下命令:


stop in x::bar()

如果键入:


stop in foo

dbx 便无法确定所指是全局函数 foo(int) 还是全局函数 foo(double),因而会显示重载菜单以便确认。

如果键入:


stop in `bar

dbx 便无法确定所指是全局函数 bar() 还是成员函数 bar(),因而会显示重载菜单。

有关指定 in function 事件的更多信息,请参见in function。

在 C++ 程序中设置多个断点

可以检查与对不同类成员的调用、对给定类任何成员的调用或对重载的顶级函数的调用有关的问题。可以将关键字(inmember、inclass、infunction 或 inobject)与 stop、when 或 trace 命令一起使用,在 C++ 代码中设置多个断点。

在不同类的成员函数中设置断点

要在特定成员函数的每个对象特定变体(成员函数名相同,类不同)中设置断点,请使用 stop inmember。

例如,如果在几个不同的类中定义了函数 draw,要在每个函数中设置断点,请键入:


(dbx) stop inmember draw

有关指定 inmember 或 inmethod 事件的更多信息,请参见inmember function inmethod function。

在相同类的成员函数中设置断点

要在特定类的所有成员函数中设置断点,请使用 stop inclass 命令。

缺省情况下,断点只插入类中定义的类成员函数中,而不会插入可能从基类继承的类成员函数中。如果还要在从基类继承的函数中插入断点,请指定 -recurse 选项。

要在类 shape 中定义的所有成员函数中设置断点,请键入:


(dbx) stop inclass shape

要在类 shape 中定义的所有成员函数中以及从该类继承的函数中设置断点,请键入:


(dbx) stop inclass shape -recurse

有关指定 inclass 事件的更多信息,请参见inclass classname [-recurse | -norecurse]和stop 命令。

由于 stop inclass 和其他断点选择可能会插入大量断点,因此应确保将 dbx 环境变量 step_events 设置为 on 以加快 step 和 next 命令的执行速度(请参见效率方面的考虑)。

在非成员函数中设置多个断点

要在具有重载名称(名称相同、参数的类型或数量不同)的非成员函数中设置多个断点,请使用 stop infunction 命令。

例如,如果 C++ 程序定义了两个名为 sort() 的函数版本(一个传递 int 类型参数,另一个传递 float 类型参数),要在这两个函数中设置断点,请键入:


(dbx) stop infunction sort [command;]

有关指定 infunction 事件的更多信息,请参见infunction function。

在对象中设置断点

可以设置 In Object 断点来检查应用于特定对象实例的操作。

缺省情况下,在从对象调用时 In Object 断点会在对象的类(包括继承的类)的所有非静态成员函数中暂停程序执行。要只在对象的类而不在继承的类中定义的非静态成员函数中设置断点以暂停程序执行,请指定 -norecurse 选项。

要在对象 foo 的基类中定义的所有非静态成员函数中以及在对象 foo 的继承的类中定义的所有非静态成员函数中设置断点,请键入:


(dbx) stop inobject &foo

要在对象 foo 的类中定义的所有非静态成员函数中设置断点,但不在对象 foo 的继承的类中定义的所有非静态成员函数中设置断点,请键入:


(dbx) stop inobject &foo -norecurse

有关指定 inclass 事件的更多信息,请参见inobject object-expression [-recurse | -norecurse]和stop 命令。

设置数据更改断点

可以在 dbx 中使用数据更改断点来记录变量或表达式的值的更改时间。

访问地址时停止执行

要在访问内存地址时停止执行,请键入:


(dbx) stop access mode address-expression [, byte-size-expression]

mode 指定内存访问模式。可由以下一个或所有字母组成:

r

已读取指定地址处的内存。

w

已写入内存。

x

已执行内存。

mode 还可以包含以下任一项:

a

访问后停止进程(缺省值)。

b

访问前停止进程。

在这两种情况下,程序计数器都将指向违例指令。“之前”和“之后”都具有副作用。

address-expression 是求值结果为地址的任何表达式。如果提供符号表达式,则会自动推导出要监视的区域大小;可以通过指定 byte-size-expression 将其覆盖。也可以使用非符号、无类型地址表达式,在这种情况下,必须提供大小。

在以下示例中,将在读取了内存地址 0x4762 后停止执行。


(dbx) stop access r 0x4762
:

在以下示例中,将在写入变量速度前停止执行:


(dbx) stop access wb &speed

使用 stop access 命令时请记住下列要点:

有关指定访问事件的更多信息,请参见access mode address-expression [, byte-size-expression ]和stop 命令。

变量更改时停止执行

要在指定变量的值更改时停止程序执行,请键入:


(dbx) stop change variable

使用 stop change 命令时请记住下列要点:

有关指定更改事件的更多信息,请参见change variable和stop 命令。

dbx 实现的 stop change 操作是自动单步执行且每一步都检查值。如果库未使用 -g 选项进行编译,则单步执行会跳过库调用。因此,如果控制按以下方式流动,dbx 便不会跟踪嵌套的 user_routine2,因为跟踪会跳过库调用和对 user_routine2 的嵌套调用。


   user_routine calls
      library_routine, which calls
        user_routine2, which changes variable

variable 值的更改是在从库调用返回后便已发生,而不是在 user_routine2 中发生的。

dbx 无法为块局部变量({} 中嵌套的变量)中的更改设置断点。如果尝试在块局部“嵌套”变量中设置断点或跟踪,dbx 会发出错误,说明无法执行此操作。


注 –

与使用 change 事件相比,使用 access 事件监视数据更改的速度更快。access 事件不是自动单步执行程序,而是使用速度快得多的页保护方案。


条件停止执行

要在条件语句的求值结果为 true 时停止程序执行,请键入:


(dbx) stop cond condition

condition 发生时,程序便停止执行。

使用 stop cond 命令时请记住下列要点:

有关指定条件事件的更多信息,请参见cond condition-expression和stop 命令。

在断点上设置过滤器

在 dbx 中,大多数事件管理命令都支持可选的事件过滤器修饰符。最简单的过滤器是指示 dbx 在程序执行到断点或跟踪处理程序处后或出现监视条件后测试条件。

如果相应过滤器条件的求值结果为 true(非 0),则会应用事件命令,且程序在断点处停止执行。如果条件的求值结果为 false (0),dbx 会继续执行程序,就好像从未发生过事件。

要在某一行处或函数中设置包含过滤器的断点,请将可选的 - if condition 修饰符语句添加到 stop 或 trace 命令的末尾。

条件可以是任何有效的表达式(包括函数调用),其返回值是布尔值或输入命令时所用语言表示的整数值。

对于像 in 或 at 这样基于位置的断点,作用域便是断点位置的作用域。否则,条件的作用域是输入时的作用域,而不是事件发生时的作用域。可能必须使用反引号操作符(请参见反引号操作符)来精确指定作用域。

例如,以下这两个过滤器是不一样的:


stop in foo -if a>5
stop cond a>5

前者在 foo 处中断并测试条件。后者自动单步执行并测试条件。

将函数调用的返回值用作过滤器

可以将函数调用用作断点过滤器。在以下示例中,如果字符串 str 中的值是 abcde,则在函数 foo() 中停止执行:


(dbx) stop in foo -if !strcmp(“abcde”,str)

将变量作用域用作过滤器

变量作用域可以用于设置断点过滤器。在以下示例中,当前作用域在 function foo() 中,而 local 是在 main() 中定义的局部变量:


(dbx) stop access w &main`local -if pr(main`local) -in main

将过滤器与条件事件配合使用

新用户有时会将设置条件事件命令(监视类型命令)与使用过滤器混淆。从概念上来说,“监视”会创建在执行每行代码前必须检查的前提条件(在监视的作用域内)。但即便是有条件触发的断点命令也可以连接过滤器。

假设有这样一个示例:


(dbx) stop access w &speed -if speed==fast_enough

此命令指示 dbx 监视变量 speed;如果变量 speed 已写入(“监视”部分),则 -if 过滤器生效。dbx 检查 speed 的新值是否等于 fast_enough。如果不等,程序会继续执行,而“忽略”stop 命令。

在 dbx 语法中,过滤器以命令末尾处的 [-if condition] 形式表示。


stop in function [-if condition]

如果在多线程程序中设置的断点中使用了包含多个函数调用的过滤器,dbx 将在到达断点时停止所有线程的执行,然后对条件求值。如果满足条件且调用了函数, dbx 便会恢复执行调用期间内的所有线程。

例如,可以在多线程应用程序中许多线程调用 lookup() 之处设置以下断点:


(dbx) stop in lookup -if strcmp(name, “troublesome”) == 0

dbx 会在线程 t@1 调用 lookup() 时停止,并对条件求值,然后调用恢复所有线程的 strcmp()。如果在函数调用期间 dbx 在另一个线程中遇到断点,便会发出警告,例如:


event infinite loop causes missed events in the following handlers:
...

Event reentrancy
first event BPT(VID 6m TID 6, PC echo+0x8)
second event BPT*VID 10, TID 10, PC echo+0x8)
the following handlers will miss events:
...

在这种情况下,如果可以确定条件表达式中调用的函数不会抓取互斥锁,则可使用 -resumeone 事件规范修饰符强制 dbx 只恢复在其中遇到断点的第一个线程。例如,可设置以下断点:


(dbx) stop in lookup -resumeone -if strcmp(name, “troublesome”) == 0

在有些情况下,-resumeone 修饰符并不能防止出现问题。例如,它在下列情况下便无能为力:

有关事件修饰符的详细信息,请参见事件规范修饰符。

跟踪执行

跟踪会收集程序中发生情况的相关信息并显示这些信息。程序执行到使用 trace 命令创建的断点处时将停止,此时会发送事件特定的 trace 信息行,然后程序继续执行。

跟踪会显示即将执行的每个源代码行。除最简单的程序以外,此跟踪都会产生大量输出。

一种更加有用的跟踪是应用过滤器显示程序中事件的相关信息。例如,可以跟踪每个函数调用、给定名称的每个成员函数、类中的每个函数或每次从函数的退出。还可以跟踪对变量的更改。

设置跟踪

可以通过在命令行中键入 trace 命令设置跟踪。trace 命令的基本语法如下:


trace event-specification [ modifier ]

有关 trace 命令的完整语法,请参见trace 命令。

跟踪提供的信息取决于与之关联的事件类型(请参见设置事件规范)。

控制跟踪速度

通常,跟踪输出的显示速度太快。可以使用 dbx 环境变量 trace_speed 控制打印每个跟踪后的延迟时间。缺省延迟时间为 0.5 秒。

要设置跟踪期间每个代码行执行间隔时间(秒),请键入:


dbxenv trace_speed number

将跟踪输出定向到文件

可以使用 -file filename 选项将跟踪输出定向到文件。例如,以下命令可将跟踪输出定向到文件 trace1:


(dbx) trace -file trace1

要将跟踪输出还原为标准输出,请使用 - 表示 filename。跟踪输出始终附加到 filename 后面。每当显示 dbx 提示以及应用程序退出时都会刷新。在执行新的运行或连接后继续运行时总会重新打开 filename。

在行中设置 when 断点

when 断点命令接受其他 dbx 命令(如 list),这样您便可以编写自己的 trace 版本。


(dbx) when at 123 {list $lineno;}

when 命令隐含了 cont 命令。上例中,列出当前行的源代码后,程序会继续执行。如果在 list 命令后添加了 stop 命令,程序便不会继续执行。

有关 trace 命令的完整语法,请参见when 命令。有关事件修饰符的详细信息,请参见事件规范修饰符。

在共享库中设置断点

dbx 对使用连接运行时链接程序的编程接口的代码提供了全面的调试支持:调用 dlopen()、dlclose() 及其关联函数的代码。运行时链接程序会在程序执行期间绑定和解除绑定共享库。借助对 dlopen() 和 dlclose() 的调试支持,可以步入函数或在动态共享库的函数中设置断点,如同在程序启动时链接的库中一样。

但下列情况除外。此时,dbx 无法在尚未(例如,通过使用 dlopen())装入的装入对象中设置断点:

可以使用 loadobject 命令(请参见loadobject 命令)将此类装入对象的名称放在预装入列表中。

dbx 会记住使用 dlopen() 装入的装入对象。例如,在刚装入的装入对象中设置的断点会一直保持到执行下一次运行命令时,即便是使用 dlclose() 卸载了该装入对象,并随后使用 dlopen() 再次装入该装入对象。

列出和清除断点

通常,在调试会话期间会设置多个断点或跟踪处理程序。dbx 中有用于列出和清除它们的命令。

列出断点和跟踪

要显示所有活动断点的列表,请使用 status 命令显示 ID 号(用括号括住),以后其他命令可以使用该号。

dbx 将通过关键字 inmember、inclass 和 infunction 设置的多个断点按一组使用一个状态 ID 号的断点来报告。

使用处理程序 ID 号删除特定断点

使用 status 命令列出断点时,dbx 会显示创建每个断点时为其分配的 ID 号。可以使用 delete 命令按 ID 号删除断点,也可以使用关键字 all 来删除程序中当前设置的所有断点。

要按 ID 号删除断点(此例中为 3 和 5),请键入:


(dbx) delete 3 5

要删除 dbx 中当前装入的程序中设置的所有断点,请键入:


(dbx) delete all

有关更多信息,请参见delete 命令。

启用和禁用断点

用来设置断点的每个事件管理命令(stop、trace、when)都会创建一个事件处理程序(请参见事件处理程序)。其中每个命令都会返回一个称为处理程序 ID (hid) 的编号。可将处理程序 ID 用作 handler 命令的参数(请参见handler 命令)来启用或禁用断点。

效率方面的考虑

在所调试程序的执行时间方面,各种事件都有不同程度的开销。但某些事件(如最简单的断点)几乎没有开销。基于单个断点的事件的开销非常小。

多种可能会导致生成数百个断点的断点(如 inclass)仅在创建期间有开销。这是因为 dbx 使用永久性断点,这些断点一直保留在进程中,每次中断时并不会被移除,且每次执行 cont 命令时都会被置入。


注 –

就 step 和 next 而言,缺省情况下,恢复进程前会移除所有断点,完成相应步骤后会立即重新插入这些断点。如果使用大量断点或在多产类中使用多个断点,step 命令和 next 命令的执行速度会显著降低。可使用 dbx step_events 环境变量控制是否移除断点并在每次执行 step 命令或 next 命令后重新插入断点。


速度最慢的事件是利用自动单步执行功能的事件。在单步执行每个源代码行的 trace step 命令中,这可能非常明显。其他一些事件(如 stop change expression 或 trace cond variable)不仅自动单步执行,而且还必须在执行每一步时对表达式或变量求值。

这些事件的速度非常慢,但通常可以使用 -in 修饰符将事件与函数绑定来克服速度慢这一问题。例如:


trace next -in mumble
stop change clobbered_variable -in lookup

请勿使用 trace -in main,这是因为 trace 在 main 调用的函数中也有效。而是在怀疑 lookup() 函数破坏变量的情况下使用它。