|C H A P T E R 20|
Debugging Shared Libraries
dbx provides full debugging support for programs that use dynamically-linked, shared libraries, provided that the libraries are compiled using the -g option.
This chapter is organized into the following sections:
The dynamic linker, also known as rtld, Runtime ld, or ld.so, arranges to bring shared objects (load objects) into an executing application. There are two primary areas where rtld is active:
dbx uses the term loadobject to refer to a shared object (.so) or executable (a.out). You can use the loadobject command (see loadobject Command) to list and manage symbolic information from loadobjects.
The dynamic linker maintains a list of all loaded objects in a list called a link map. The link map is maintained in the memory of the program being debugged, and is indirectly accessed through librtld_db.so, a special system library for use by debuggers.
A .init section is a piece of code belonging to a shared object that is executed when the shared object is loaded. For example, the .init section is used by the C++ runtime system to call all static initializers in a .so.
The dynamic linker first maps in all the shared objects, putting them on the link map. Then, the dynamic linker traverses the link map and executes the .init section for each shared object. The syncrtld event (see syncrtld) occurs between these two phases.
Procedure linkage tables (PLTs) are structures used by the rtld to facilitate calls across shared object boundaries. For instance, calls to printf go through this indirect table. The details of how this is done can be found in the generic and processor specific SVR4 ABI reference manuals.
For dbx to handle step and next commands across PLTs, it has to keep track of the PLT table of each load object. The table information is acquired at the same time as the rtld handshake.
Using fix and continue with shared objects loaded with dlopen() requires a change in how they are opened for fix and continue to work correctly. Use mode RTLD_NOW|RTLD_GLOBAL or RTLD_LAZY|RTLD_GLOBAL.
To set a breakpoint in a shared library, dbx needs to know that a program will use that library when it runs, and dbx needs to load the symbol table for the library. To determine which libraries a newly-loaded program will use when it runs, dbx executes the program just long enough for the runtime linker to load all of the starting libraries. dbx then reads the list of loaded libraries and kills the process. The libraries remain loaded and you can set breakpoints in them before rerunning the program for debugging.
dbx follows the same procedure for loading the libraries whether the program is loaded from the command line with the dbx command, from the dbx prompt with the debug command, or from the dbx Debugger in the IDE.
dbx automatically detects that a dlopen() or a dlclose() has occurred and loads the symbol table of the loaded object. Once a shared object has been loaded with dlopen() you can place breakpoints in it and debug it as you would any part of your program.
If a shared object is unloaded using dlclose(), dbx remembers the breakpoints placed in it and replaces them if the shared object is again loaded with dlopen(), even if the application is run again.
However, you do not need to wait for the loading of a shared object with dlopen() to place a breakpoint in it, or to navigate its functions and source code. If you know the name of the shared object that the program being debugged will be loading with dlopen(), you can request that dbx preload its symbol table by using the loadobject -load command:
You can now navigate the modules and functions in this loadobject and place breakpoints in it before it has been loaded with dlopen(). Once the loadobject is loaded by your program, dbx automatically places the breakpoints.
Setting a breakpoint in a dynamically linked library is subject to the following limitations: