Linker and Libraries Guide

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.