dbx 为使用动态链接库、共享库的程序提供全面的调试支持,只要这些库是使用 -g 选项编译的。
本章由以下部分组成:
动态链接程序(亦称 rtld、运行时 ld 或 ld.so)安排将共享对象(装入对象)引入到正在执行的程序中。rtld 在两个主要区域处于活动状态:
程序启动-程序启动时,rtld 先运行,然后动态装入在链接时指定的所有共享对象。它们是预装的共享对象,可能包括 libc.so、libC.so 或 libX.so。使用 ldd(1) 查明程序将装入哪些共享对象。
应用程序请求-应用程序使用函数调用 dlopen(3) 和 dlclose(3) 来动态装入和卸下共享对象或可执行文件。
dbx 使用术语装入对象 来表示共享对象 (.so) 或可执行文件 (a.out)。可以使用 loadobject 命令(请参见loadobject 命令)列出和管理来自装入对象的符号信息。
动态链接程序在称为链接映射的列表中保留有所有装入对象的列表。链接映射保留在正被调试程序的内存中,可通过 librtld_db.so 这一供调试器使用的特殊系统库来间接访问它。
.init 段是一段属于共享对象的代码,装入共享对象时该代码将执行。例如,.init 段由 C++ 运行时系统用于调用 .so 中的所有静态初始化函数。
动态链接程序会先在所有共享对象中映射,从而将它们置于链接映射中。然后,动态链接程序将遍历链接映射并为每个共享对象执行 .init 段。syncrtld 事件(请参见syncrtld)发生在这两个阶段之间。
过程链接表(Procedure linkage table,PLT)是 rtld 为了实现跨共享对象边界调用所使用的结构。例如,对 printf 的调用便会通过这个间接表。可以在通用及处理器特定 SVR4 ABI 参考手册中找到对这一过程的详细说明。
要使 dbx 能够在各 PLT 中处理 step 和 next 命令,它必须记录每个装入对象的 PLT 表。表信息的获得与 rtld 握手同时进行。
要将修复并继续用于通过 dlopen() 装入的共享对象,需要更改这些共享对象的打开方式,这样修复并继续才能正常运行。使用模式 RTLD_NOW|RTLD_GLOBAL 或 RTLD_LAZY|RTLD_GLOBAL。
为了在共享库中设置断点,dbx 需要了解程序将在运行时使用该库,并且 dbx 需要为该库装入符号表。为了确定新装入的程序在运行时将使用哪些库,dbx 会将该程序执行足够长的时间,以便运行时链接程序装入所有启动库。然后,dbx 会读入已装入库的列表并终止进程。库会保持装入状态,这样便可以在重新运行程序进行调试前于库中设置断点。
无论程序是在命令行使用 dbx 命令装入,还是在 dbx 提示符处使用 debug 命令装入,抑或在 IDE 中装入,dbx 都会按相同的步骤装入库。
dbx 会自动检测发生了 dlopen() 还是 dlclose(),然后装入装入对象的符号表。使用 dlopen() 装入共享对象后,即可在其中设置断点,然后像对待程序的任何部分一样进行调试。
如果共享对象是使用 dlclose() 卸下的,dbx 会记住其中设置的断点,并在使用 dlopen() 再次装入该共享对象时替换这些断点,即便应用程序再次运行也是如此。
但如果要在其中设置断点,或导航其函数和源代码,就不必等待使用 dlopen() 装入共享对象。如果知道正被调试的程序将使用 dlopen() 装入的共享对象的名称,可以使用 loadobject -load 命令请求 dbx 预装其符号表:
loadobject -load /usr/java1.1/lib/libjava_g.so |
现在便可在此装入对象被 dlopen() 装入前在其中导航模块和函数及设置断点。装入对象由程序装入后,dbx 即会自动设置断点。
在动态链接库中设置断点受以下限制:
使用 dlopen() 装入的“过滤器”库中的第一个函数被调用后,才能在该库中设置断点。
dlopen() 装入库后,名为 _init() 的初始化例程便会被调用。此例程可能会调用库中的其他例程。在此初始化完成之前,dbx 不能在已装入的库中设置断点。确切地讲,这意味着无法让 dbx 在 dlopen() 装入的库中的 _init() 处停止。