Linker and Libraries Guide

Chapter 6 Support Interfaces

Overview

The link-editors provide a number of support interfaces that allow for the monitoring, and in some cases modification, of link-editor and runtime linker processing. These interfaces typically require a more advanced understanding of link-editing concepts than has been described in previous chapters. The following interfaces are described in this chapter:

Link-Editor Support Interface

As described in Chapter 2, Link-Editor, the link-editor performs many operations including the opening of files and the concatenation of sections from these files. Monitoring, and sometimes modifying, these operations can often be beneficial to components of a compilation system.

This section describes the supported interface for input file inspection, and to some degree, input file data modification of those files that compose a link-edit. This interface is referred to as the ld-support interface. Two applications that employ this interface are the link-editor itself, which uses it to process debugging information within relocatable objects, and the make(1) utility, which uses it to save state information.

The ld-support interface is composed of a support library that offers one or more support interface routines. This library is loaded as part of the link-edit process, and any support routines found are called at various stages of link-editing.

You should be familiar with the elf(3E) structures and file format when using this interface.

Invoking the Support Interface

The link-editor accepts one or more support libraries provided by either the SGS_SUPPORT environment variable or with the link-editor's -S option. The environment variable consists of a colon separated list of support libraries:


$ SGS_SUPPORT=./support.so.1:libldstab.so.1 cc ...

The -S option specifies a single support library. Multiple -S options can be specified:


$ ld -S ./support.so.1 -S libldstab.so.1 ...

A support library is a shared object. The link-editor performs a dlopen(3X) on each shared object, in the order they are specified. If both the environment variable and -S option are encountered, then the shared objects specified with the environment variable are processed first. Each support library is then searched, using dlsym(3X), for any support interface routines. These support routines are then called at various stages of link-editing.


Note -

By default, the Solaris support library libldstab.so.1 is used by the link-editor to process, and compact, compiler-generated debugging information supplied within input relocatable objects. This default processing is suppressed if you invoke the link-editor with any support libraries specified using the -S option. If the default processing of libldstab.so.1 is required in addition to your support library services, then libldstab.so.1 should be explicitly added to the list of support libraries supplied to the link-editor.



Note -

In Solaris 7, ld is a 64-bit program when the 64-bit kernel is running, and a 32-bit program when the 32-bit kernel is running. The interface description below shows that the support interface for 64-bit ELF objects is similar to the 32-bit support interface, but all of the entry points end in a 64 suffix, for example ld_start() and ld_start64(). This allows both implementations of the support interface to reside in a single shared object libldstab.so.1 of each class, 32-bit and 64-bit.


Support Interface Functions

All ld-support interfaces are defined in the header file link.h. All interface arguments are basic C types or ELF types. The ELF data types can be examined with the ELF access library libelf (see elf(3E) for a description of libelf contents). The following interface functions are provided by the ld-support interface, and are described in their expected order of use:


void ld_start(const char * name, const Elf32_Half type,
        const char * caller);
	void ld_start64(const char * name, const Elf64_Half type,
        const char * caller);
ld_start()

This function is called after initial validation of the link-editor command line, and indicates the start of input file processing.

name is the output filename being created. type is the output file type, which is either ET_DYN, ET_REL, or ET_EXEC (as defined in sys/elf.h). caller is the application calling the interface, which is normally /usr/ccs/bin/ld.


void ld_file(const char * name, const Elf_Kind kind, int flags,
        Elf * elf);
void ld_file64(const char * name, const Elf_Kind kind, int flags,
        Elf * elf);
ld_file()

This function is called for each input file before any processing of the files data is carried out.

name is the input file about to be processed. kind indicates the input file type, which is either ELF_K_AR, or ELF_K_ELF (as defined in libelf.h). flags indicates how the link-editor obtained the file, and can be one or more of the following definitions:

  • LD_SUP_DERIVED -- The file name was not explicitly named on the command- line. It was either derived from a -l expansion, or it identifies an extracted archive member.

  • LD_SUP_INHERITED -- The file was obtained as a dependency of a command-line shared object.

  • LD_SUP_EXTRACTED -- The file was extracted from an archive.

If no flags values are specified then the input file has been explicitly named on the command-line. elf is a pointer to the files ELF descriptor.


void ld_section(const char * name, Elf32_Shdr * shdr,
        Elf32_Word sndx, Elf_Data * data, Elf * elf);
void ld_section64(const char * name, Elf64_Shdr * shdr,
        Elf64_Word sndx, Elf_Data * data, Elf * elf);
ld_section()

This function is called for each section of the input file before any processing of the section data is carried out.

name is the input section name. shdr is a pointer to the associated section header. sndx is the section index within the input file. data is a pointer to the associated data buffer. elf is a pointer to the files ELF descriptor.

Modification of the data is permitted by reallocating the data itself and reassigning the Elf_Data buffer's d_buf pointer. Any modification to the data should ensure the correct setting of the Elf_Data buffers d_size element. For input sections that will become part of the output image, setting the d_size element to zero will effectively remove the data from the output image.


Note -

Any sections that are stripped by use of the link-editor's -s option are not reported to ld_section().



void ld_atexit(int status);
void ld_atexit64(int status);
ld_atexit()

This function is called on completion of the link-edit.

status is the exit(2) code that will be returned by the link-editor and is either EXIT_FAILURE or EXIT_SUCCESS (as defined in stdlib.h).

Support Interface Example

The following example creates a support library that prints the section name (see Table 7-16) of any relocatable object file processed as part of a link-edit.


$ cat support.c
#include        <link.h>
 
static int      indent = 0;
 
void
ld_start(const char * name, const Elf32_Half type,
    const char * caller)
{
        (void) printf("output image: %s\n", name);
}
 
void
ld_file(const char * name, const Elf_Kind kind, int flags,
    Elf * elf)
{
        if (flags & LD_SUP_EXTRACTED)
                indent = 4;
        else
                indent = 2;
 
        (void) printf("%*sfile: %s\n", indent, "", name);
}
 
void
ld_section(const char * name, Elf32_Shdr * shdr, Elf32_Word sndx,
    Elf_Data * data, Elf * elf)
{
        Elf32_Ehdr *    ehdr = elf32_getehdr(elf);
 
        if (ehdr->e_type == ET_REL)
                (void) printf("%*s   section [%ld]: %s\n", indent,
                    "", sndx, name);
}

This support library is dependent upon libelf to provide the ELF access function elf32_getehdr(3E) used to determine the input file type. The support library is built using:


$ cc -o support.so.1 -G -K pic support.c -lelf -lc

The following example shows the section diagnostics resulting from the construction of a trivial application from a relocatable object and a local archive library. The invocation of the support library, in addition to default debugging information processing, is brought about by the -S option usage:


$ LD_OPTIONS=-S./support.so.1:libldstab.so.1 cc -o prog \
main.c -L. -lfoo
output image: prog
  file: /opt/COMPILER/crti.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: /opt/COMPILER/crt1.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: /opt/COMPILER/values-xt.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: main.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: ./libfoo.a
    file: ./libfoo.a(foo.o)
       section [1]: .shstrtab
       section [2]: .text
       .......
  file: /usr/lib/libc.so
  file: /opt/COMPILER/crtn.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: /usr/lib/libdl.so.1

Note -

The number of sections displayed in this example have been reduced to simplify the output. Also, the files included by the compiler driver can vary.


Runtime Linker Auditing Interface

As described in Chapter 3, Runtime Linker, the runtime linker performs many operations including the mapping of objects into memory and the binding of symbols. Monitoring, and sometimes modifying these operations from within the same process, can enable powerful process monitoring tools.

This section describes the supported interface for a process to access runtime linking information regarding itself. This interface is referred to as the rtld-audit interface. One example of the use of this mechanism is the runtime profiling of shared objects described in "Profiling Shared Objects".

The rtld-audit interface is implemented as an audit library that offers one or more auditing interface routines. If this library is loaded as part of a process, then the audit routines are called by the runtime linker at various stages of process execution. These interfaces allow the audit library to access:

Some of these facilities can be achieved by preloading specialized shared objects (see "Loading Additional Objects"). However, a preloaded object exists within the same name-space as the objects of a process, and this often restricts, or complicates the implementation of the preloaded shared object. The rtld-audit interface offers the user a unique name-space in which to execute their audit libraries, that ensures the audit library does not intrude upon the normal bindings that occur within the process.

Establishing a Name-space

When the runtime linker binds a dynamic executable with its dependencies, it builds a linked list of link-maps to describe the process. The link-map structure describes each object within the process and is defined in/usr/include/sys/link.h. The symbol search mechanism required to bind objects of an application traverses this list of link-maps. This link-map list is said to provide the name-space for process symbol resolution.

The runtime linker itself is also described by a link-map, and this link-map is maintained on a different list from that of the application objects. This results in the runtime linker residing in its own unique name space, which prevents any direct binding of the application to services within the runtime linker. (An application can only call upon the public services of the runtime linker by the filter libdl.so.1).

The rtld-audit interface employs its own link-map list on which it maintains any audit libraries. This results in the audit libraries being isolated from the symbol binding requirements of the application. Inspection of the application link-map list is possible with dlmopen(3X), which, when used with the RTLD_NOLOAD flag, allows the audit library to query an object's existence without causing its loading.

Two identifiers are defined in /usr/include/link.h to define the application and runtime linker link-map lists:


#define LM_ID_BASE      0     /* application link-map list */
#define LM_ID_LDSO      1     /* runtime linker link-map list */

Each rtld-audit support library is assigned a unique free link-map identifier.

Building an Audit Library

An audit library is built like any other shared object; however, its unique name-space within a process requires some additional care:

If the audit library calls printf(3C), then the audit library must define a dependency on libc (see "Generating a Shared Object"). Because the audit library has a unique name space, symbol references cannot be satisfied by the libc present in the application being audited. If an audit library has a dependency on libc, then two versions of libc.so.1 will be loaded into the process. One satisfies the binding requirements of the application link-map list, and the other satisfies the binding requirements of the audit link-map list.

To ensure audit libraries are built with all dependencies recorded, the link-editors -z defs option should be used (see "Generating a Shared Object").

Some system interfaces exist assuming they are the only instance of their implementation within a process, for example, threads, signals and malloc(3C). Audit libraries should avoid using such interfaces, as doing so can inadvertently alter the behavior of the application.


Note -

The allocation of memory using mapmalloc(3X) by an audit library is acceptable, as this can exist with any allocation scheme normally employed by the application.


Invoking the Auditing Interface

The rtld_audit interface is enabled using the runtime linker environment variable LD_AUDIT. This environment variable is set to a colon-separated list of shared objects that will be loaded by dlopen(3X). Each object is loaded onto its own audit link-map list, and each object is searched for audit routines using dlsym(3X). Audit routines that are found will be called at various stages during the applications execution.

The rtld_audit interface allows for multiple audit libraries to be supplied. Audit libraries that expect to be employed in this fashion should not alter the bindings that would normally be returned by the runtime linker; doing so can produce unexpected results from audit libraries that follow.

Secure applications (see "Security") can only obtain audit libraries from trusted directories. Presently, the only trusted directories available for audit libraries are /usr/lib and /usr/ccs/lib for 32-bit executables, or /usr/lib/sparcv9 for 64-bit SPARCV9 executables.

Audit Interface Functions

The following functions are provided by the rtld-audit interface and are described in their expected order of use:


uint_t la_version(uint_t version);
la_version()

This function provides the initial handshake between the runtime linker and the audit library. This interface must be provided by the audit library for it to be loaded.

The runtime linker calls this interface with the highest version of the rtld-audit interface it is capable of supporting. The audit library can verify that this version is sufficient for its use, and return the version it expects to use. This version is normally LAV_CURRENT, which is defined in /usr/include/link.h.

If the audit library returns a version of zero, or a value greater than the rtld-audit interface the runtime linker supports, the audit library will not be used.


uint_t la_objopen(Link_map * lmp, Lmid_t lmid, uintptr_t * cookie);
la_objopen()

This function is called each time a new object is loaded by the runtime linker.

lmp provides the link-map structure describing the new object. lmid identifies the link-map list to which the object has been added (see "Establishing a Name-space"). cookie provides a pointer to an identifer. This identifier is initialized to the objects lmp, but can be modified by the audit library to better identify the object to other rtld-audit interface routines

This function returns a value indicating the symbol bindings of interest for this object, which will result in later calls to la_symbind32(). The return value is a mask of the following values defined in/usr/include/link.h:

  • LA_FLG_BINDTO -- audit symbol bindings to this object

  • LA_FLG_BINDFROM -- audit symbol bindings from this object

See la_symbind() for more details on the use of these two flags.

A return value of zero indicates that binding information is of no interest for this object.


void la_preinit(uintptr_t * cookie);
la_preinit()

This function is called once after all objects have been loaded for the application, but before transfer of control to the application occurs.

cookie identifies the primary object that started the process, normally the dynamic executable.


uintptr_t la_symbind32(Elf32_Sym * sym, uint_t ndx,
        uintptr_t * refcook, uintptr_t * defcook, uint_t * flags);
 
uintptr_t la_symbind64(Elf64_Sym * sym, uint_t ndx,
        uintptr_t * refcook, uintptr_t * defcook, uint_t * flags,
	        const char * sym_name);
la_symbind32(), la_symbind64()

This function is called when a binding occurs between two objects that have been tagged for binding notification (see la_objopen()).

sym is a constructed symbol structure (see /usr/include/sys/elf.h), whose sym->st_value indicates the address of the symbol definition being bound. la_symbind32() has the sym->st_name adjusted to point to the actual symbol name, while la_symbind64() does not adjust sym->st_name and it is an index into the string table.

ndx indicates the symbol index within the bound object's dynamic symbol table. refcook describes the object making reference to this symbol. This identifier is the same as passed to the la_objopen() that returned LA_FLG_BINDFROM. defcook describes the object defining this symbol. This identifier is the same as passed to the la_objopen() that returned LA_FLG_BINDTO.

flags points to a data item that can be used to modify the continued auditing of procedure linkage table symbol entries. This value is a mask of the following flags defined in /usr/include/link.h:

sym_name point to the string name for the symbol being bound (for la_symbind64 only).

  • LA_SYMB_NOPLTENTER -- The la_*_pltenter() function will not be called for this symbol.

  • LA_SYMB_NOPLTEXIT -- The la_pltexit() function will not be called for this symbol.

  • LA_SYMB_DLSYM -- The symbol binding occurred as a result of calling dlsym(3X).

    • LA_SYMB_ALTVALUE (LAV_VERSION2) -- An alternate value was returned for the symbol value by a previous call to la_symbind32/la_symbind64.

By default, if the la_*_pltenter() or la_pltexit() functions exist within the audit library, these will be called (after la_symbind32()) for procedure linkage table symbols each time the symbol is referenced (see also "Audit Interface Limitations").

The return value indicates the address to which control should be passed following this call. An audit library that simply monitors symbol binding should return the value of sym->st_value so that control is passed to the bound symbol definition. An audit library can intentionally redirect a symbol binding by returning a different value.

The sym_name parameter to la_symbind64() contains the name of the symbol being processed, which is available in the sym->st_name field from the 32-bit interfaces.


uint_t la_sparcv8_pltenter(Elf32_Sym * sym, uint_t ndx,
        uintptr_t * refcook, uintptr_t * defcook,
        La_sparcv8_regs * regs, uint_t * flags);
 
uint_t la_sparcv9_pltenter(Elf64_Sym * sym, uint_t ndx,
        uintptr_t * refcook, uintptr_t * defcook,
        La_sparcv9_regs * regs, uint_t * flags,
        const char * sym_name);
 
uint_t la_i86_pltenter(Elf32_Sym * sym, uint_t ndx,
        uintptr_t * refcook, uintptr_t * defcook,
        La_i86_regs * regs, uint_t * flags);
la_sparcv8_pltenter(), la_i86_pltenter(), la_sparcv9_pltenter()

These functions are called on a SPARC and x86 system respectively, when a procedure linkage symbol entry between two objects that have been tagged for binding notification is called (see la_objopen() and la_symbind32()).

sym, ndx, refcook, defcook and sym_name provide the same information as passed to la_symbind32()/la_symbind64().

regs points to the out registers on a SPARC system, and the stack and frame registers on a x86 system, as defined in /usr/include/link.h.

flags points to a data item that can be used to modify the continuing auditing of this procedure linkage table entry. This data item is the same as pointed to by the flags from la_symbind32(). This value is a mask of the following flags defined in /usr/include/link.h:

  • LA_SYMB_NOPLTENTER -- The la_sparcv8_pltenter() or la_i86_pltenter() function will not be called subsequently for this symbol.

  • LA_SYMB_NOPLTEXIT -- The la_pltexit() function will not be called for this symbol.

The return value indicates the address to which control should be passed following this call. An audit library that simply monitors symbol binding should return the value of sym->st_value so that control is passed to the bound symbol definition. An audit library can intentionally redirect a symbol binding by returning a different value.

The sym_name parameter to la_sparcv9_pltenter() contains the name of the symbol being processed, which is available in the sym->st_name field from the 32-bit interfaces.


uint_t la_pltexit(Elf32_Sym * sym, uint_t ndx, uintptr_t * refcook,
        uintptr_t * defcook, uintptr_t retval);
uint_t la_pltexit64(Elf64_Sym * sym, uint_t ndx, uintptr_t * refcook,
        uintptr_t * defcook, uintptr_t retval, const char * sym_name);
la_pltexit()

This function is called when a procedure linkage symbol entry between two objects that have been tagged for binding notification (see la_objopen() and la_symbind32()) returns, but before control reaches the caller.

sym, ndx, refcook and defcook provide the same information as passed to la_symbind32(). retval is the return code from the bound function. The sym_name parameter to la_pltexit64() contains the name of the symbol being processed, and is available from the sym->st_name field in the 32-bit implementation.

An audit library that simply monitors symbol binding should return retval. An audit library can intentionally return a different value.


Note -

This interface function is experimental; see "Audit Interface Limitations".



uint_t la_objclose(uintptr_t * cookie);
la_objclose()

This function is called after any termination code for an object has been executed (see "Initialization and Termination Routines") and prior to the object being unloaded.

cookie was obtained from a previous la_objopen() and identifies the object. Any return value is presently ignored.

Audit Interface Example

The following simple example creates an audit library that prints the name of each shared object dependency loaded by the dynamic executable date(1):


$ cat audit.c
#include        <link.h>
#include        <stdio.h>
 
uint_t
la_version(uint_t version)
{
        return (LAV_CURRENT);
}
 
uint_t
la_objopen(Link_map * lmp, Lmid_t lmid, uintptr_t * cookie)
{
        if (lmid == LM_ID_BASE)
                (void) printf("file: %s loaded\n", lmp->l_name);
        return (0);
}
$ cc -o audit.so.1 -G -K pic -z defs audit.c -lmapmalloc -lc
$ LD_AUDIT=./audit.so.1 date
file: date loaded
file: /usr/lib/libc.so.1 loaded
file: /usr/lib/libdl.so.1 loaded
file: /usr/locale/en_US/en_US.so.1 loaded
Fri Mar  8 10:03:55 PST 1997

Audit Interface Demonstrations

A number of demonstration applications that use the rtld-audit interface are provided in the SUNWosdem package under /usr/demo/link_audit:

sotruss

This demo provides tracing of procedure calls between the dynamic objects of a named application.

whocalls

This demo provides a stack trace for a specified function whenever called by a named application.

perfcnt

This demo traces the amount of time spent in each function for a named application.

symbindrep

This demo reports all symbol bindings performed to load a named application.

sotruss(1) and whocalls(1) are also included in the SUNWtoo package. perfcnt and symbindrep are example programs only and are not intended for use in a production environment.

Audit Interface Limitations

There are some limitations regarding the use of the la_pltexit() and ld_pltexit64() functions. These limitations stem from the need to insert an extra stack frame between the caller and callee to provide a means of acquiring the la_pltexit() return value. This is not a problem when calling just the la_*_pltenter() routines, as any intervening stack can be cleaned up prior to transferring control to the destination function.

Because of these limitations, la_pltexit() and la_pltexit64() should be considered experimental interfaces, that might change in future releases to better address these limitations. When in doubt, the use of la_pltexit() should be avoided.

Functions That Directly Inspect the Stack

A small number of functions exist that directly inspect the stack or make assumptions regarding its state. Some examples of these functions are the setjmp(3C) family, vfork(2), and any function that returns a structure (not a pointer to a structure). These functions will be compromised by the extra stack created to support la_pltexit().

The runtime linker cannot detect functions of this type, and thus it becomes the responsibility of the audit library creator to disable la_pltexit() for such routines.

Runtime Linker Debugger Interface

As described in Chapter 3, Runtime Linker 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 to the application they are analyzing.

This section describes the supported interface for monitoring and modifying a dynamically linked application from another process. This interface is referred to as the rtld-debugger interface. The architecture of this interface follows the model used in libthread_db(3T).

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

The most anticipated use for rtld-debugger is that the controlling process is a debugger and its target is a dynamic executable.

The rtld-debugger interface enables the following for 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:

The imported interface consists of a number of proc_service routines (which are described in "Debugger Import Interface"), which most debuggers already employ to analyze processes.

The rtld-debugger interface assumes that the process being analyzed is stopped when requests are made of the rtld-debugger interface. If this is not the case, 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 below:

Figure 6-1 rtld-debugger information flow

Graphic


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, and 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, and provides a mechanism of communication between the exported and imported interfaces. As 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;
struct ps_prochandle

An opaque structure created by the controlling process to identify the target process which is passed between the exported and imported interface.


struct rd_agent;
struct rd_agent

An opaque structure created by the rtld-debugger interface identifying the target process which 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, 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.


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_err_e ps_pauxv(const struct ps_prochandle * ph, auxv_t ** aux);
ps_pauxv()

This function returns a pointer to a copy of the auxv vector. As the auxv vector information is copied to an allocated structure, the life time of this pointer is as long as the prochandle is valid.


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

This function reads size bytes from the target process at address addr and copies them into buf.


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

This function writes size bytes from buf into the target process at address addr.


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

This function is called with additional diagnostic information from the rtld-debugger interface. It is up to the controlling process to decide where (or if) to log this diagnostic information. The arguments to ps_plog() follow the printf(3S) format.


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

This function searches for the symbol named name within the object named obj within the target process ph. If the symbol is found address of the symbol is stored in sym_addr.


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

This function searches for the symbol named name within the object named obj within the target process ph. If the symbol is found, the descriptor sym is filled in.

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 */

One mechanism the controlling process can use to find the symbol table for these objects is through the procfs file system 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.