Linker and Libraries Guide

Debugger Exported Interface

This section describes the various interfaces exported by the /usr/lib/librtld_db.so.1 audit library, and is broken down into functional groups.

Agent Manipulation


rd_err_e rd_init(int version);
rd_init()

This function establishes the rtld-debugger version requirements. The current version is defined by RD_VERSION.

If the version requirement of the controlling process is greater than the rtld-debugger interface available, then RD_NOCAPAB is returned.


rd_agent_t * rd_new(struct ps_prochandle * php);
rd_new()

This function creates a new exported interface agent. php is a cookie created by the controlling process to identify the target process. This cookie is used by the imported interface offered by the controlling process to maintain context, and is opaque to the rtld-debugger interface.


rd_err_e rd_reset(struct rd_agent * rdap);
rd_reset()

This function resets the information within the agent based off the same ps_prochandle structure given to rd_new(). This function is called when a target process is restarted.


void rd_delete(struct rd_agent * rdap);
rd_delete()

This function deletes an agent and frees any state associated with it.

Error Handling

The following error states can be returned by the rtld-debugger interface (defined in rtld_db.h):


typedef enum {
        RD_ERR,
        RD_OK,
        RD_NOCAPAB,
        RD_DBERR,
        RD_NOBASE,
        RD_NODYNAM,
        RD_NOMAPS
} rd_err_e;

The following interfaces can be used to gather the error information:


char * rd_errstr(rd_err_e rderr);
rd_errstr()

This function returns a descriptive error string describing the error code rderr.


void rd_log(const int onoff);
rd_log()

This function turns logging on (1) or off (0). When logging is turned on, the imported interface function ps_plog() provided by the controlling process, is called with more detailed diagnostic information.

Scanning Loadable Objects

Obtaining information for each object maintained on the runtime linkers link-map (see "Establishing a Name-space") is achieved using the following structure (defined in rtld_db.h):


typedef struct rd_loadobj {
        psaddr_t        rl_nameaddr;
        unsigned        rl_flags;
        psaddr_t        rl_base;
        psaddr_t        rl_data_base;
        unsigned        rl_lmident;
        psaddr_t        rl_refnameaddr;
        psaddr_t        rl_plt_base;
        unsigned        rl_plt_size;
        psaddr_t        rl_bend;
        psaddr_t        rl_padstart;
        psaddr_t        rl_padend;
} rd_loadobj_t;

Notice that all addresses given in this structure, including string pointers, are addresses in the target process and not in the address space of the controlling process itself:

rl_nameaddr

Pointer to a string which contains the name of the dynamic object.

rl_flags

Reserved for future use.

rl_base

Base address of dynamic object.

rl_data_base

Base address of data segment of dynamic object.

rl_lmident

The link-map identifier (see "Establishing a Name-space").

rl_refnameaddr

If the dynamic object is a filter (see "Shared Objects as Filters"), then this points to the name of the filtee(s).

rl_plt_base, rl_plt_size

These elements are present for backward compatibility and are currently unused.

rl_bend

End address of object (text + data + bss).

rl_padstart

Base address of padding before dynamic object (refer to "Dynamic Object Padding").

rl_padend

Base address of padding after dynamic object (refer to "Dynamic Object Padding").

The following routine uses this object data structure to access information from the runtime linkers link-map lists:


typedef int rl_iter_f(const rd_loadobj_t *, void *);
 
rd_err_e rd_loadobj_iter(rd_agent_t * rap, rl_iter_f * cb,
        void * clnt_data);
rd_loadobj_iter()

This function iterates over all dynamic objects currently loaded in the target process. On each iteration the imported function specified by cb is called. clnt_data can be used to pass data to the cb call. Information about each object is returned via a pointer to a volatile (stack allocated) rd_loadobj_t structure.

Return codes from the cb routine are examined by rd_loadobj_iter() and have the following meaning:

  • 1 -- continue processing link-maps.

  • 0 -- stop processing link-maps and return control to the controlling process.

rd_loadobj_iter() returns RD_OK on success. A return of RD_NOMAPS indicates the runtime linker has not yet loaded the initial link-maps.

Event Notification

There are certain events that occur within the scope of the runtime linker that a controlling process can track. These events are:

RD_PREINIT

The runtime linker has loaded and relocated all the dynamic objects and is about to start calling the .init sections of each object loaded (see "Initialization and Termination Routines").

RD_POSTINIT

The runtime linker has finished calling all of the .init sections and is about to transfer control to the primary executable.

RD_DLACTIVITY

The runtime linker has been invoked to either load or unload a dynamic object (see "Loading Additional Objects").

These events can be monitored using the following interface (defined in sys/link.h and rtld_db.h):


typedef enum {
        RD_NONE = 0,
        RD_PREINIT,
        RD_POSTINIT,
        RD_DLACTIVITY
} rd_event_e;
 
/*
 * ways that the event notification can take place:
 */
typedef enum {
        RD_NOTIFY_BPT,
        RD_NOTIFY_AUTOBPT,
        RD_NOTIFY_SYSCALL
} rd_notify_e;
 
/*
 * information on ways that the event notification can take place:
 */
typedef struct rd_notify {
        rd_notify_e     type;
        union {
                psaddr_t        bptaddr;
                long            syscallno;
        } u;
} rd_notify_t;

rderr_e rd_event_enable(struct rd_agent * rdap, int onoff);
rd_event_enable()

This function enables (1) or disables (0) event monitoring.


Note -

Presently, for performance reasons, the runtime linker ignores event disabling. The controlling process should not assume that a given break-point will not be reached because of the last call to this routine.



rderr_e rd_event_addr(rd_agent_t * rdap, rd_event_e event,
        rd_notify_t * notify);
rd_event_addr()

This function specifies how the controlling program will be notified of a given event.

Depending on the event type, the notification of the controlling process will take place by calling a benign, cheap system call which is identified by notify->u.syscallno, or executing a break point at the address specified by notify->u.bptaddr. It is the responsibility of the controlling process to trace the system call or place the actual break-point.

When an event has occurred, additional information can be obtained by this interface (defined in rtld_db.h):


typedef enum {
        RD_NOSTATE = 0,
        RD_CONSISTENT,
        RD_ADD,
        RD_DELETE
} rd_state_e;
 
typedef struct rd_event_msg {
        rd_event_e      type;
        union {
                rd_state_e      state;
        } u;
} rd_event_msg_t;

rd_state_e values have the following meaning:

RD_NOSTATE

There is no additional state information available.

RD_CONSISTANT

The link-maps are in a stable state and can be examined.

RD_ADD

A dynamic object is in the process of being loaded and the link-maps are not in a stable state. They should not be examined until the RD_CONSISTANT state is reached.

RD_DELETE

A dynamic object is in the process of being deleted and the link-maps are not in a stable state. They should not be examined until the RD_CONSISTANT state is reached.


rderr_e rd_event_getmsg(struct rd_agent * rdap,
        rd_event_msg_t * msg);
rd_event_getmsg()

This function provides additional information concerning an event.

The following table shows the possible state for each of the different event types:

RD_PREINIT

RD_POSTINIT

RD_DLACTIVITY

RD_NOSTATE

RD_NOSTATE

RD_CONSISTANT

 

 

RD_ADD

 

 

RD_DELETE

Procedure Linkage Table Skipping

The rtld-debugger interface offers the ability to help skip over procedure linkage table entries (refer to "Procedure Linkage Table (Processor-Specific)"). When a controlling process, such as a debugger, is asked to step into a function for the first time, they often wish to skip the actual procedure linkage table processing, as this results in control being passed to the runtime linker to search for the function definition.

The following interface allows a controlling process to step over the runtime linker's procedure linkage table processing. It is assumed that the controlling process can determine when a procedure linkage table entry is encountered, based on external information provided in the ELF file.

Once a target process has stepped into a procedure linkage table entry, it calls the following interface:


rd_err_e rd_plt_resolution(rd_agent_t * rdap, paddr_t pc,
        lwpid_t lwpid, paddr_t plt_base, rd_plt_info_t * rpi);
rd_plt_resolution()

This function returns the resolution state of the current procedure linkage table entry and information on how to skip it.

pc represents the first instruction of the procedure linkage table entry. lwpid privides the lwp identifier and plt_base provides the base address of the procedure linkage table. These three variables provide information sufficient for various architectures to process the procedure linkage table.

rpi provides detailed information regarding the procedure linkage table entry as defined in the following data structure (defined in rtld_db.h):


typedef enum {
    RD_RESOLVE_NONE,
    RD_RESOLVE_STEP,
    RD_RESOLVE_TARGET,
    RD_RESOLVE_TARGET_STEP
} rd_skip_e;
 
typedef struct rd_plt_info {
        rd_skip_e       pi_skip_method;
        long            pi_nstep;
        psaddr_t        pi_target;
} rd_plt_info_t;

The following scenarios are possible from the rd_plt_info_t return values:


Note -

Future implementations might employ RD_RESOLVE_TARGET as a means of setting a break point directly in the target function; however, this capability is not yet available in this version of the rtld-debugger interface.


Dynamic Object Padding

The default behavior of the runtime linker relies on the operating system to load dynamic objects where they can be most efficiently referenced. Some controlling processes benefit from the existence of padding around the objects loaded into memory of the target process. This interface allows a controlling process to request this padding.


rd_err_e rd_objpad_enable(struct rd_agent * rdap, size_t padsize);
rd_objpad_enable()

This function enables or disables the padding of any subsequently loaded objects with the target process. Padding occurs on both sides of the loaded object.

padsize specifies the size of the padding, in bytes, to be preserved both before and after any objects loaded into memory. This padding is reserved as a memory mapping using mmap(2) with PROT_NONE permissions and the MAP_NORESERVE flag. Effectively the runtime linker reserves areas of the virtual address space of the target process adjacent to any mapped objects. These areas can later be utilized by the controlling process.

A padsize of 0 disables any object padding for later objects.


Note -

Reservations obtained using mmap(2) from /dev/zero with MAP_NORESERVE can be reported using the proc(1) facilities and by referring to the link-map information provided in rd_loadobj_t.