Sun Studio 12:使用 dbx 调试程序

第 4 章 查看和导航到代码

每当正在调试的程序停止时,dbx 都会打印与停止位置关联的源代码行。在每个程序停止位置,dbx 会将当前函数的值重置为程序在其中停止执行的函数。在程序开始运行前及程序停止运行时,您可以移动或导航到程序中其他地方的函数和文件。

本章说明 dbx 如何导航到代码以及如何查找函数和符号。还说明如何使用命令导航到代码或查找标识符、类型和类的声明。

本章由以下部分组成

导航到代码

当程序停止时,您可以导航到程序中其他地方的代码。可导航到任何函数或文件,只要它们是程序的一部分。导航会设置当前作用域(请参见程序作用域)。这对于确定要在何时以及在哪一源代码行设置 stop at 断点非常有用。

导航到文件

可以导航到 dbx 将其识别为程序一部分的任何文件(即使模块或文件未使用 -g 选项进行编译也是如此)。要导航到文件:


(dbx) file filename

使用不带参数的 file 命令将回显当前导航的文件的文件名。


(dbx) file

如果不指定行号,dbx 会从文件的第一行开始显示文件。


(dbx) file filename ; list line_number

有关在源代码行中设置 "stop at" 断点的信息,请参见在源代码行设置 stop 断点

导航到函数

可以使用 func 命令导航到函数。要导航到函数,请键入命令 func,后跟函数名。例如:


(dbx) func adjust_speed

func 命令本身会回显当前函数。

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

从 C++ 二义函数名称列表中选择

如果尝试导航到具有二义名称或重载函数名称的 C++ 成员函数,则会显示一个列表,其中列出了所有具有重载名称的函数。键入要导航的函数的号码。如果您知道函数所属的具体类,则可以键入类名和函数名。例如:


(dbx) func block::block

在多个具体值中进行选择

如果可从同一作用域级别访问多个符号,则 dbx 会打印一条报告二义性的消息。


(dbx) func main
(dbx) which C::foo
More than one identifier ’foo’.
Select one of the following:
 0) Cancel
 1) ”a.out”t.cc”C::foo(int)
 2) ”a.out”t.cc”C::foo()
>1
”a.out”t.cc”C::foo(int)

which 命令的上下文中,从具体值列表中进行选择不会影响 dbx 或程序的状态。无论选择哪个具体值,dbx 都会回显名称。

打印源码列表

可使用 list 命令打印文件或函数的源码列表。在文件中导航后,list 会从第一行开始打印 number 行。在函数中导航后,list 会打印其代码行。

有关 list 命令的详细信息,请参见list 命令

在调用栈中移动以导航到代码

如果存在活动进程,另一种导航到代码的方法是“在调用栈中移动”,即执行栈命令查看调用栈中当前存在的函数,这些函数代表当前处于活动状态的所有例程。在栈中移动会使当前函数和文件在您每次显示栈函数时发生变化。停止位置被视为位于栈的“底部”,因此,要离开该位置,请使用 up 命令,即向 mainbegin 函数方向移动。使用 down 命令可向当前帧方向移动。

有关在调用栈中移动的更多信息,请参见栈中移动和返回起始位置

程序位置的类型

dbx 使用三个全局位置来跟踪您正在检查的程序的各部分:

程序作用域

作用域是按变量或函数的可见性定义的程序子集。如果某个符号的名称在给定执行点是可见的,则称该符号“在作用域内”。在 C 语言中,函数可以具有全局或文件静态作用域;变量可以具有全局、文件静态、函数或块作用域。

反映当前作用域的变量

以下变量总是反映当前线程或 LWP 的当前程序计数器,而且不受更改访问作用域的各种命令的影响:

$scope

当前程序计数器的作用域

$lineno

当前行号

$func

当前函数

$class

$func 所属的类

$file

当前源文件

$loadobj

当前装入对象

访问作用域

使用 dbx 检查程序的各种元素时,需要修改访问作用域。dbx 在表达式求值期间使用访问作用域来实现解析二义符号等目的。例如,如果键入以下命令,dbx 会使用访问作用域来确定要打印哪个 i


(dbx) print i

每个线程和 LWP 都有自己的访问作用域。在线程间切换时,每个线程都会记住其访问作用域。

访问作用域的组件

访问作用域的某些组件在以下预定义的 ksh 变量中是可见的:

$vscope

语言作用域

$vloadobj

当前访问装入对象

$vfile

当前访问文件

$vlineno

当前访问行号

$vclass

C++ 类

当前访问作用域的所有组件相互间保持兼容。例如,如果您访问不包含函数的文件,则当前访问源文件会更新为新的文件名,并且当前访问函数会更新为 NULL

更改访问作用域

下列命令是更改访问作用域的最常用方法:

debug 命令和 attach 命令可设置初始访问作用域。

遇到断点时,dbx 会将访问作用域设置为当前位置。如果将 stack_find_source 环境变量(请参见设置 dbx 环境变量)设置为 ON,则 dbx 会尝试查找并激活有源代码的栈帧。

使用 up 命令(请参见up 命令)、down 命令(请参见down 命令)、frame number 命令(请参见frame 命令)或 pop 命令(请参见pop 命令)更改当前栈帧时,dbx 根据新的栈帧中的程序计数设置访问作用域。

仅当使用 list functionlist file 命令时,list 命令(请参见list 命令)使用的行号位置才会更改访问作用域。在设置访问作用域后,list 命令的行号位置会设置为访问作用域的第一个行号。以后使用 list 命令时,list 命令的当前行号位置会更新,但只要是列出当前文件中的代码行,访问作用域就不会更改。例如,如果键入以下内容,dbx 会列出 my_func 源的开头,并将访问作用域更改为 my_func


(dbx) list my_func

如果键入以下内容,dbx 会列出当前源文件的第 127 行,但不会更改访问作用域。


(dbx) list 127

使用 file 命令或 func 命令更改当前文件或当前函数时,访问作用域也会相应更新。

使用作用域转换操作符限定符号

使用 funcfile 命令时,可能需要使用作用域转换操作符来限定作为目标给出的函数的名称。

dbx 提供了三个用于限定符号的作用域转换操作符:反引号操作符 ()、C++ 双冒号操作符 (::) 和块局部操作符 (:lineno)。应单独使用它们,在某些情况下可以一起使用。

除了在代码中导航时限定文件名和函数名外,打印和显示作用域外变量和表达式以及显示类型和类声明时还必须限定符号名(使用 whatis 命令)。在所有情况下,符号限定规则都是相同的;本节介绍所有类型的符号名限定的规则。

反引号操作符

使用反引号字符 (`) 可查找全局作用域变量或函数:


(dbx) print `item

一个程序可以在两个不同的文件(或编译模块)中使用同一函数名。在这种情况下,还必须将函数名限定到 dbx,以便它记录您要导航的函数。要按相应的文件名限定函数名,请使用通用反引号 (`) 作用域转换操作符。


(dbx) func`file_name`function_name

C++ 双冒号作用域转换操作符

使用双冒号操作符 (::) 可以用以下名称限定具有全局作用域的 C++ 成员函数、顶级函数或变量:

可能需要限定重载函数名。如果不限定它,则 dbx 会显示一个重载列表,以便您从中选择要导航的函数。如果您知道函数类名,则可以将其与双冒号作用域转换操作符一起使用来限定名称。


(dbx) func class::function_name (args)

例如,如果 hand 是类名,而 draw 是函数名,请键入:


(dbx) func hand::draw

块局部操作符

使用块局部操作符 (:line_number) 可专门引用嵌套块中的变量。如果有遮蔽参数或成员名的局部变量,或如果有几个块,其中每个块都有其自己的局部变量版本,可能需要这样做。line_number 是相关变量所对应的块内第一行代码的号码。当 dbx 使用块局部操作符限定局部变量时,dbx 会使用第一个代码块的行号,但您可以在 dbx 表达式中使用作用域内的任意行号。

在下例中,块局部操作符 (:230) 与反引号操作符配合使用。


(dbx) stop in `animate.o`change_glyph:230`item

下例显示了当函数中有多个具体值时,dbx 如何对使用块局部操作符限定的变量名求值。


(dbx) list 1,$
    1   #include <stddef.h>
    2
    3   int main(int argc, char** argv) {
    4
    5   int i=1;
    6
    7       {
    8            int i=2;
    9            {
   10                   int j=4;
   11                   int i=3;
   12                   printf("hello");
   13            }
   14            printf("world\n");
   15       }
   16       printf("hi\n");
   17   }
   18
(dbx) whereis i
variable: `a.out`t.c`main`I
variable: `a.out`t.c`main:8`I
variable: `a.out`t.c`main:10`I
(dbx) stop at 12 ; run
...
(dbx) print i
i = 3
(dbx) which i
`a.out`t.c`main:10`I
(dbx) print `main:7`I
`a.out`t.c`main`I = 1
(dbx) print `main:8`I
`a.out`t.c`main:8`I = 2
(dbx) print `main:10`I
`a.out`t.c`main:10`I = 3
(dbx) print `main:14`I
`a.out`t.c`main:8`I = 2
(dbx) print `main:15`I
`a.out`t.c`main`I = 1

链接程序名

dbx 提供了一种按链接程序名(在 C++ 中为损坏名)查找符号的特殊语法。使用 #(磅符号)字符作为符号名的前缀(在任何 $(美元符号)字符前使用 ksh 转义符 \(反斜线)),如以下示例中所示:


(dbx) stop in #.mul
(dbx) whatis #\$FEcopyPc
(dbx) print `foo.c`#staticvar

查找符号

在程序中,同一名称可能会引用不同类型的程序实体,并可能会在许多作用域中出现。dbx whereis 命令会列出全限定名称,即该名称的所有符号的位置。如果在表达式中给出该名称,则 dbx which 命令会告知 dbx 将使用符号的哪个具体值(请参见which 命令)。

打印符号具体值列表

要打印指定符号的所有具体值的列表,请使用 whereis symbol,其中 symbol 可以是用户定义的任何标识符。例如:


(dbx) whereis table
forward: `Blocks`block_draw.cc`table
function: `Blocks`block.cc`table::table(char*, int, int, const point&)
class: `Blocks`block.cc`table
class: `Blocks`main.cc`table
variable:       `libc.so.1`hsearch.c`table

输出内容包括程序在其中定义 symbol 的可装入对象的名称,以及每个对象的实体类型:类、函数或变量。

由于 dbx 符号表中的信息是在需要时才读入,因此 whereis 命令只记录已装入符号的具体值。随着调试会话越来越长,具体值列表也会增长(请参见目标文件和可执行文件中的调试信息)。

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

确定 dbx 使用哪个符号

如果在表达式中指定了名称(没有完全限定),则 which 命令会告知 dbx 使用哪个具有给定名称的符号。例如:


(dbx) func
wedge::wedge(char*, int, int, const point&, load_bearing_block*)
(dbx) which draw
`block_draw.cc`wedge::draw(unsigned long)

如果指定的符号名不在局部作用域中,则 which 命令会沿着作用域转换搜索路径搜索该符号的第一个具体值。如果 which 找到该名称,则它会报告全限定名称。

如果搜索操作在搜索路径中的任何位置找到了 symbol 的处于同一作用域级别的多个具体值,则 dbx 会在命令窗格中打印一条消息,报告这种不明确情况。


(dbx) which fid
More than one identifier ’fid’.
Select one of the following:
 0) Cancel
 1) `example`file1.c`fid
 2) `example`file2.c`fid

dbx 会显示重载信息,并列出二义符号名。在 which 命令的上下文中,从具体值列表中进行选择不会影响 dbx 或程序的状态。无论选择哪个具体值,dbx 都会回显名称。

如果使 symbol(本例中为 block)成为必须对 symbol 运行的某个命令(例如,print 命令)的参数,则 which 命令会让您预览将发生的事情。如果有二义名,重载显示列表会指明 dbx 尚未记录它会使用两个或更多名称中的哪一个具体值。dbx 会列出可能的值,等待您从中选择一个。有关 which 命令的更多信息,请参见which 命令

作用域转换搜索路径

当您发出包含表达式的调试命令时,将按以下顺序查找表达式中的符号。dbx 会按编译器在当前访问作用域中进行操作的同样方式解析这些符号。

  1. 在使用当前访问作用域的当前函数的作用域内(请参见访问作用域)。如果程序在嵌套块中停止,则 dbx 会在该块内搜索,然后在所有封装块的作用域中搜索。

  2. 仅限于 C++:当前函数的类及其基类的类成员。

  3. 仅限于 C++:当前名字空间。

  4. 当前函数的参数。

  5. 立即封装模块,通常为包含当前函数的文件。

  6. 供此共享库或可执行文件专用的符号。可使用链接程序作用域来创建这些符号。

  7. 主程序的全局符号,然后为共享库的全局符号。

  8. 如果上述搜索都不成功,则 dbx 会假定您正在引用其他文件中的专用或文件静态变量或函数。dbx 可选择根据 dbxenv 设置 scope_look_aside 的值在每个编译单元中搜索文件静态符号。

无论 dbx 使用符号的哪个具体值,它都会先沿此搜索路径查找。如果 dbx 找不到符号,它会报告错误。

放宽作用域查找规则

要为静态符号和 C++ 成员函数放宽作用域查找规则,请将 dbx 环境变量 scope_look_aside 设置为 on:

dbxenv scope_look_aside on

或者,使用“双反引号”前缀:

stop in ``func4            func4 可以是静态的,也可以不在作用域中。

如果将 dbx 环境变量 scope_look_aside 设置为 on,则 dbx 会查找:

which 命令会告知您 dbx 选择哪个符号。如果有二义名,重载显示列表会指明 dbx 尚未确定它会使用两个或更多名称中的哪一个具体值。dbx 会列出可能的值,等待您从中选择一个。

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

查看变量、成员、类型和类

whatis 命令可打印标识符、结构、类型和 C++ 类的声明或定义,或者表达式类型。您可以查找的标识符包括变量、函数、字段、数组和枚举常量。

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

查找变量、成员和函数的定义

要打印输出标识符的声明,请键入:


(dbx) whatis identifier

根据需要使用文件和函数信息来限定标识符名。

对于 C++ 程序,whatis identifier 会列出函数模板实例化。可使用 whatis -t identifier 显示模板定义。请参见查找类型和类的定义

对于 Java 程序,whatis identifier 会列出类的声明、当前类中的方法、当前帧中的局部变量或当前类中的字段。

要打印成员函数,请键入:


(dbx) whatis block::draw
void block::draw(unsigned long pw);
(dbx) whatis table::draw
void table::draw(unsigned long pw);
(dbx) whatis block::pos
class point *block::pos();
(dbx) whatis table::pos
class point *block::pos();
:

要打印数据成员,请键入:


(dbx) whatis block::movable
int movable;

对于变量,whatis 会输出变量的类型。


(dbx) whatis the_table
class table *the_table;
.

对于字段,whatis 会给出字段的类型。


(dbx) whatis the_table->draw
void table::draw(unsigned long pw);

当程序在成员函数中停止时,可以查找 this 指针。


(dbx) stop in brick::draw
(dbx) cont
(dbx) where 1
brick::draw(this = 0x48870, pw = 374752), line 124 in
     "block_draw.cc"
(dbx) whatis this
class brick *this;

查找类型和类的定义

whatis 命令的 -t 选项可显示类型的定义。对于 C++,whatis -t 显示的列表包括模板定义和类模板实例化。

打印类型或 C++ 类的声明,请键入:


(dbx) whatis -t type_or_class_name

要查看继承成员,可以在 whatis 命令中使用 -r 选项(表示“递归”),该选项显示指定类的声明以及它从基类继承的成员。


(dbx) whatis -t -r  class_name

whatis -r 查询的输出可能会很长,具体取决于类分层结构以及类的大小。输出以从最原始的类继承的成员列表开头。插入的注释行将成员列表分到其各自的父类中。

以下是两个示例,它们使用了类 table,它是父类 load_bearing_block 的子类,而该父类又是 block 的子类。

如果不使用 -rwhatis 会报告在类 table 中声明的成员:


(dbx) whatis -t class table
class table : public load_bearing_block {
public:
    table::table(char *name, int w, int h, const class point &pos);
    virtual char *table::type();
    virtual void table::draw(unsigned long pw);
};

以下是对子类使用 whatis -r 以查看它继承的成员时的结果:


(dbx) whatis -t -r class table
class table : public load_bearing_block {
public:
  /* from base class table::load_bearing_block::block */
  block::block();
  block::block(char *name, int w, int h, const class point &pos, class load_bearing_block *blk);
    virtual char *block::type();
    char *block::name();
    int block::is_movable();
//  deleted several members from example protected:
    char *nm;
    int movable;
    int width;
    int height;
    class point  position;
    class load_bearing_block *supported_by;
    Panel_item panel_item;
    /* from base class table::load_bearing_block */
public:
    load_bearing_block::load_bearing_block();
    load_bearing_block::load_bearing_block(char *name, int w, int h,
        const class point &pos, class load_bearing_block *blk);
    virtual int load_bearing_block::is_load_bearing();
    virtual class list *load_bearing_block::supported_blocks();
    void load_bearing_block::add_supported_block(class block &b);
    void load_bearing_block::remove_supported_block(class block &b);
    virtual void load_bearing_block::print_supported_blocks();
    virtual void load_bearing_block::clear_top();
    virtual void load_bearing_block::put_on(class block &object);
    class point load_bearing_block::get_space(class block &object);
    class point load_bearing_block::find_space(class block &object);
    class point load_bearing_block::make_space(class block &object);
protected:
    class list *support_for;
    /* from class table */
public:
    table::table(char *name, int w, int h, const class point &pos);
    virtual char *table::type();
    virtual void table::draw(unsigned long pw);
};

目标文件和可执行文件中的调试信息

通常,您希望使用 -g 选项来编译源文件,以使程序的可调试性更好。-g 选项会使编译器将调试信息(采用 stabs 或 Dwarf 格式)与程序的代码和数据一起记录到目标文件中。

需要调试信息时,dbx 会根据需要解析和装入每个目标文件(模块)的调试信息。可以使用 module 命令让 dbx 装入任何特定模块或所有模块的调试信息。另请参见查找源文件和目标文件

目标文件装入

将目标 (.o) 文件链接到一起后,链接程序可选择只将摘要信息存储到生成的装入对象中。dbx 可以在运行时使用此摘要信息从目标文件本身(而不是可执行文件)装入其余调试信息。生成的可执行文件占用的磁盘资源较小,但要求在 dbx 运行时能够使用目标文件。

使用 -xs 选项编译目标文件可覆盖此要求,从而使这些目标文件的所有调试信息在链接时都被放入可执行文件中。

如果使用目标文件创建归档库(.a 文件),并且在程序中使用归档库,则 dbx 会根据需要从归档库中提取目标文件。此时不需要原始目标文件。

将所有调试信息放入可执行文件的唯一缺点是会占用更多磁盘空间。由于运行时调试信息并未装入到进程映像中,因此程序运行速度不会降低。

使用 stabs(调试信息的缺省格式)时的缺省行为是使编译器只将摘要信息放入可执行文件中。

DWARF 格式尚不支持目标文件装入。


注 –

记录相同的信息时,使用 DWARF 格式要比使用 stabs 格式紧凑得多。但是,由于将全部信息都复制到可执行文件中,因此 DWARF 信息所占的空间看上去要比 stabs 信息所占的空间大。


列出模块的调试信息

module 命令及其选项有助于在调试会话期间跟踪程序模块。可使用 module 命令读入一个模块或所有模块的调试信息。一般情况下,dbx 会根据需要自动并且延后读入模块的调试信息。

读入模块 name 的调试信息,请键入:


(dbx) module [-f] [-q] name

读入所有模块的调试信息,请键入:


(dbx) module [-f] [-q] -a

其中:

-a

指定所有模块。

-f

强制读取调试信息,即使该文件比可执行文件新也是如此。

-q

指定安静模式。

-v

指定详细模式,在该模式下可打印语言、文件名等信息。这是缺省设置。

打印当前模块的名称,请键入:


(dbx) module

列出模块

modules 命令通过列出模块名称来帮助您跟踪模块。

要列出包含已读入 dbx 的调试信息的模块的名称,请键入:


(dbx) modules [-v] -read

要列出所有程序模块(不管它们是否包含调试信息)的名称,请键入:


(dbx) modules [-v]

要列出包含调试信息的所有程序模块,请键入:


(dbx) modules [-v] -debug

其中:

-v 

指定冗余模式,在该模式下会打印语言、文件名等信息。 

查找源文件和目标文件

dbx 必须知道与程序关联的源代码文件和目标代码文件的位置。目标文件的缺省目录是上次链接程序时这些文件所在的目录。源文件的缺省目录是上次编译时它们所在的目录。如果移动了源文件或目标文件,或者将它们复制到新位置,则必须重新链接程序,在调试前更改到新位置,或者使用 pathmap 命令。

dbx 有时使用目标文件装入附加调试信息。当 dbx 显示源代码时,会使用源文件。

如果在编译和链接程序后移动了源文件或目标文件,则可将其新位置添加到搜索路径中。pathmap 命令可创建从文件系统的当前视图到可执行映像中的名称的映射。该映射应用于源路径和目标文件路径。

要建立从目录 from 到目录 to 的新映射:


(dbx) pathmap [-c] from to

如果使用 -c,该映射还将应用于当前工作目录。

pathmap 命令还可用于处理在不同主机上具有不同基路径的自动挂载或显式 NFS 挂载的文件系统。因为当前工作目录在自动挂载的文件系统中不准确,所以在尝试解决由自动挂载程序引起的问题时,请使用 -c

缺省情况下,存在 /tmp_mnt/ 的映射。

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