Debugging a Program With dbx

Chapter 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:

Basic Concepts

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 load object to refer to a shared object (.so) or executable (a.out).

Link Map

The dynamic linker maintains a list of all loaded objects in a list called a link map, which is maintained in the debugee's memory, and is indirectly accessed through librtld_db.so, a special system library for rtld debugging.

Startup Sequence and .init Sections

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 occurs between these two phases.

Procedure Linkage Tables (PLT)

PLTs are structures used by the rtld to facilitate calls across shared object boundaries. For instance, the call to printf goes via 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.

Debugging Support for Preloaded Shared Objects

To put breakpoints in preloaded shared objects, the address of the routines has to be known to dbx. For dbx to know the address of the routines, it must know the shared object base address. Doing something as simple as:


stop in
run

requires special consideration by dbx. Whenever you load a new program, dbx automatically executes the program up to the point where rtld has completed construction of the link map. dbx then reads the link map and stores the base addresses. After that, the process is killed and you see the prompt. These dbx tasks are conducted silently.

At this point, the symbol table for libc.so is available as well as its base load address. Therefore, the address of printf is known.

The activity of dbx waiting for rtld to construct the link map and accessing the head of the link map is known as the rtld handshake. The event syncrtld occurs when rtld is done with the link map and dbx has read all of the symbol tables.

Fix and Continue

Using fix and continue with shared objects requires a change in how they are opened in order for fix and continue to work correctly. Use mode RTLD_NOW|RTLD_GLOBAL or RTLD_LAZY|RTLD_GLOBAL.

Setting a Breakpoint in a Dynamically Linked Library

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 like any part of your program.

If a shared object is unloaded via dlclose(), dbx remembers the breakpoints placed in it and re-places them if the shared object is again dlopened, even if the application is run again. (Versions of dbx prior to 5.0 would instead mark the breakpoint as '(defunct)', and it had to be deleted and re-placed by the user.)

However, you need not wait for the loading of a shared object with dlopen()in order to place a breakpoint in it, (or navigate its functions and source code for that matter). If you know the name of the shared object that the debugee will be dlopening you can arrange for dbx to preload its symbol table into dbx by using:

 loadobjects -p /usr/java1.1/lib/libjava_g.so

You can now navigate the modules and functions in this loadobject and place breakpoints in it before it has ever been loaded with dlopen(). Once it is loaded dbx automatically places the breakpoints.

Setting a breakpoint in a dynamically linked library is subject to the following limitations:

  1. You cannot set a breakpoint in a dlopen 'ed "filter" library until the first function in it is called.

  2. When a library is loaded by dlopen(), an initialization routine named _init() is called. This routine may call other routines in the library. dbx cannot place breakpoints in the loaded library until after this initialization is completed. In specific terms, this means you cannot have dbx stop at _init() in a library loaded by dlopen.