JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Linker and Libraries Guide
search filter icon
search icon

Document Information

Preface

1.  Introduction to the Oracle Solaris Link Editors

2.  Link-Editor

3.  Runtime Linker

4.  Shared Objects

5.  Application Binary Interfaces and Versioning

6.  Support Interfaces

Link-Editor Support Interface

Invoking the Support Interface

32-Bit Environments and 64-Bit Environments

Support Interface Functions

Support Interface Example

Runtime Linker Auditing Interface

Establishing a Namespace

Creating an Audit Library

Invoking the Auditing Interface

Recording Local Auditors

Recording Global Auditors

Audit Interface Interactions

Audit Interface Functions

Audit Interface Example

Audit Interface Demonstrations

Audit Interface Limitations

Exercising Application Code

Use of la_pltexit()

Functions That Directly Inspect the Stack

Runtime Linker Debugger Interface

Interaction Between Controlling and Target Process

Debugger Interface Agents

Debugger Exported Interface

Agent Manipulation Interfaces

Error Handling

Scanning Loadable Objects

Event Notification

Procedure Linkage Table Skipping

Dynamic Object Padding

Debugger Import Interface

7.  Object File Format

8.  Thread-Local Storage

9.  Mapfiles

A.  Link-Editor Quick Reference

B.  Versioning Quick Reference

C.  Establishing Dependencies with Dynamic String Tokens

D.  Direct Bindings

E.  System V Release 4 (Version 1) Mapfiles

F.  Linker and Libraries Updates and New Features

Index

Runtime Linker Debugger Interface

The runtime linker performs many operations including the mapping of objects into memory and the binding of symbols. Debugging programs often need to access information that describes these runtime linker operations as part of analyzing an application. These debugging programs run as a separate process from the application the debugger is analyzing.

This section describes the rtld-debugger interface for monitoring and modifying a dynamically linked application from another process. The architecture of this interface follows the model used in libc_db(3LIB).

When using the rtld-debugger interface, at least two processes are involved.

The most anticipated use of the rtld-debugger interface is when the controlling process is a debugger and its target is a dynamic executable.

The rtld-debugger interface enables the following activities with a target process.

Interaction Between Controlling and Target Process

To be able to inspect and manipulate a target process, the rtld-debugger interface employs an exported interface, an imported interface, and agents for communicating between these interfaces.

The controlling process is linked with the rtld-debugger interface provided by librtld_db.so.1, and makes requests of the interface exported from this library. This interface is defined in /usr/include/rtld_db.h. In turn, librtld_db.so.1 makes requests of the interface imported from the controlling process. This interaction allows the rtld-debugger interface to perform the following.

The imported interface consists of a number of proc_service routines that most debuggers already employ to analyze processes. These routines are described in Debugger Import Interface.

The rtld-debugger interface assumes that the process being analyzed is stopped when requests are made of the rtld-debugger interface. If this halt does not occur, data structures within the runtime linker of the target process might not be in a consistent state for examination.

The flow of information between librtld_db.so.1, the controlling process (debugger) and the target process (dynamic executable) is diagrammed in the following figure.

Figure 6-1 rtld-debugger Information Flow

rtld-debugger information flow.

Note - The rtld-debugger interface is dependent upon the proc_service interface, /usr/include/proc_service.h, which is considered experimental. The rtld-debugger interface might have to track changes in the proc_service interface as it evolves.


A sample implementation of a controlling process that uses the rtld-debugger interface is provided in the SUNWosdem package under /usr/demo/librtld_db. This debugger, rdb, provides an example of using the proc_service imported interface, and shows the required calling sequence for all librtld_db.so.1 exported interfaces. The following sections describe the rtld-debugger interfaces. More detailed information can be obtained by examining the sample debugger.

Debugger Interface Agents

An agent provides an opaque handle that can describe internal interface structures. The agent also provides a mechanism of communication between the exported and imported interfaces. The rtld-debugger interface is intended to be used by a debugger which can manipulate several processes at the same time, these agents are used to identify the process.

struct ps_prochandle

Is an opaque structure that is created by the controlling process to identify the target process that is passed between the exported and imported interface.

struct rd_agent

Is an opaque structure created by the rtld-debugger interface that identifies the target process that is passed between the exported and imported interface.

Debugger Exported Interface

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

Agent Manipulation Interfaces
rd_init()

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

rd_err_e rd_init(int version);

Version RD_VERSION2, added in the Solaris 8 10/00 release, extends the rd_loadobj_t structure. See the rl_flags, rl_bend and rl_dynamic fields in Scanning Loadable Objects.

Version RD_VERSION3, added in the Solaris 8 01/01 release, extends the rd_plt_info_t structure. See the pi_baddr and pi_flags fields in Procedure Linkage Table Skipping.

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

rd_new()

This function creates a new exported interface agent.

rd_agent_t *rd_new(struct ps_prochandle *php);

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_reset()

This function resets the information within the agent based off the same ps_prochandle structure given to rd_new().

rd_err_e rd_reset(struct rd_agent *rdap);

This function is called when a target process is restarted.

rd_delete()

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

void rd_delete(struct rd_agent *rdap);
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.

rd_errstr()

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

char *rd_errstr(rd_err_e rderr);
rd_log()

This function turns logging on (1) or off (0).

void rd_log(const int onoff);

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

You can obtain information for each object maintained on the runtime linkers link-map is achieved by 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;
        psaddt_t        rl_dynamic;
} 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

A pointer to a string that contains the name of the dynamic object.

rl_flags

With revision RD_VERSION2, dynamically loaded relocatable objects are identified with RD_FLG_MEM_OBJECT.

rl_base

The base address of the dynamic object.

rl_data_base

The base address of the data segment of the dynamic object.

rl_lmident

The link-map identifier (see Establishing a Namespace).

rl_refnameaddr

If the dynamic object is a standard filter, then this points to the name of the filtees.

rl_plt_base, rl_plt_size

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

rl_bend

The end address of the object (text + data + bss). With revision RD_VERSION2, a dynamically loaded relocatable object will cause this element to point to the end of the created object, which will include its section headers.

rl_padstart

The base address of the padding before the dynamic object (refer to Dynamic Object Padding).

rl_padend

The base address of the padding after the dynamic object (refer to Dynamic Object Padding).

rl_dynamic

This field, added with RD_VERSION2, provides the base address of the object's dynamic section, which allows reference to such entries as DT_CHECKSUM (see Table 7-32).

The rd_loadobj_iter() routine uses this object data structure to access information from the runtime linker's link-map lists.

rd_loadobj_iter()

This function iterates over all dynamic objects currently loaded in the target process.

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);

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 by means of 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

A controlling process can track certain events that occur within the scope of the runtime linker that. 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.

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.

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;

The following functions track events.

rd_event_enable()

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

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

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


rd_event_addr()

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

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

Depending on the event type, the notification of the controlling process takes place by calling a benign, cheap system call that is identified by notify->u.syscallno, or executing a break point at the address specified by notify->u.bptaddr. The controlling process is responsible for tracing 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;

The rd_state_e values are:

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.

The rd_event_getmsg() function is used to obtain this event state information.

rd_event_getmsg()

This function provides additional information concerning an event.

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

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 enables a controlling process to skip over procedure linkage table entries. When a controlling process, such as a debugger, is asked to step into a function for the first time, the procedure linkage table processing, causes control to be passed to the runtime linker to search for the function definition.

The following interface enables a controlling process to step over the runtime linker's procedure linkage table processing. 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, the process calls the rd_plt_resolution() interface.

rd_plt_resolution()

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

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);

pc represents the first instruction of the procedure linkage table entry. lwpid provides 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;
        psaddr_t        pi_baddr;
        unsigned int    pi_flags;
} rd_plt_info_t;

#define RD_FLG_PI_PLTBOUND     0x0001

The elements of the rd_plt_info_tstructure are:

pi_skip_method

Identifies how the procedure linkage table entry can be traversed. This method is set to one of the rd_skip_e values.

pi_nstep

Identifies how many instructions to step over when RD_RESOLVE_STEP or RD_RESOLVE_TARGET_STEP are returned.

pi_target

Specifies the address at which to set a breakpoint when RD_RESOLVE_TARGET_STEP or RD_RESOLVE_TARGET are returned.

pi_baddr

The procedure linkage table destination address, added with RD_VERSION3. When the RD_FLG_PI_PLTBOUND flag of the pi_flags field is set, this element identifies the resolved (bound) destination address.

pi_flags

A flags field, added with RD_VERSION3. The flag RD_FLG_PI_PLTBOUND identifies the procedure linkage entry as having been resolved (bound) to its destination address, which is available in the pi_baddr field.

The following scenarios are possible from the rd_plt_info_t return values.

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 enables a controlling process to request this padding.

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.

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

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, an area of the virtual address space of the target process, adjacent to any loaded objects, is reserved. These areas can later be used 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.


Debugger Import Interface

The imported interface that a controlling process must provide to librtld_db.so.1 is defined in /usr/include/proc_service.h. A sample implementation of these proc_service functions can be found in the rdb demonstration debugger. The rtld-debugger interface uses only a subset of the proc_service interfaces available. Future versions of the rtld-debugger interface might take advantage of additional proc_service interfaces without creating an incompatible change.

The following interfaces are currently being used by the rtld-debugger interface.

ps_pauxv()

This function returns a pointer to a copy of the auxv vector.

ps_err_e ps_pauxv(const struct ps_prochandle *ph, auxv_t **aux);

Because the auxv vector information is copied to an allocated structure, the pointer remains as long as the ps_prochandle is valid.

ps_pread()

This function reads data from the target process.

ps_err_e ps_pread(const struct ps_prochandle *ph, paddr_t addr,
        char *buf, int size);

From address addr in the target process, size bytes are copied to buf.

ps_pwrite()

This function writes data to the target process.

ps_err_e ps_pwrite(const struct ps_prochandle *ph, paddr_t addr,
        char *buf, int size);

size bytes from buf are copied into the target process at address addr.

ps_plog()

This function is called with additional diagnostic information from the rtld-debugger interface.

void ps_plog(const char *fmt, ...);

The controlling process determines where, or if, to log this diagnostic information. The arguments to ps_plog() follow the printf(3C) format.

ps_pglobal_lookup()

This function searches for the symbol in the target process.

ps_err_e ps_pglobal_lookup(const struct ps_prochandle *ph,
        const char *obj, const char *name, ulong_t *sym_addr);

The symbol named name is searched for within the object named obj within the target process ph. If the symbol is found, the symbol address is stored in sym_addr.

ps_pglobal_sym()

This function searches for the symbol in the target process.

ps_err_e ps_pglobal_sym(const struct ps_prochandle *ph,
        const char *obj, const char *name, ps_sym_t *sym_desc);

The symbol named name is searched for within the object named obj within the target process ph. If the symbol is found, the symbol descriptor is stored in sym_desc.

In the event that the rtld-debugger interface needs to find symbols within the application or runtime linker prior to any link-map creation, the following reserved values for obj are available.

#define PS_OBJ_EXEC ((const char *)0x0)  /* application id */
#define PS_OBJ_LDSO ((const char *)0x1)  /* runtime linker id */

The controlling process can use the procfs file system for these objects, using the following pseudo code.

ioctl(.., PIOCNAUXV, ...)       - obtain AUX vectors
ldsoaddr = auxv[AT_BASE];
ldsofd = ioctl(..., PIOCOPENM, &ldsoaddr);
 
/* process elf information found in ldsofd ... */
 
execfd = ioctl(.., PIOCOPENM, 0);
 
/* process elf information found in execfd ... */

Once the file descriptors are found, the ELF files can be examined for their symbol information by the controlling program.