Go to main content

Oracle® Solaris 11.4 Linkers and Libraries Guide

Exit Print View

Updated: October 2019
 
 

Runtime Linker Auditing Interface

The rtld-audit interface enables you to access information pertaining to the runtime linking of a process. 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, the audit routines are called by the runtime linker at various stages of process execution. These interfaces enable the audit library to access the following information.

  • The search for dependencies. Search paths can be substituted by the audit library.

  • Information regarding loaded objects.

  • Symbol bindings that occur between loaded objects. These bindings can be altered by the audit library.

  • The lazy binding mechanism that is provided by procedure linkage table entries, allow the auditing of function calls and their return values. See Procedure Linkage Table (Processor-Specific). The arguments to a function and return value of a function can be modified by the audit library.

Some of this information can be obtained by preloading specialized shared objects. However, a preloaded object exists within the same namespace as the objects of a application. This preloading often restricts, or complicates the implementation of the preloaded shared object. The rtld-audit interface offers you a unique namespace in which to execute audit libraries. This namespace ensures that the audit library does not intrude upon the normal bindings that occur within the application.

An example of using the rtld-audit interface is the runtime profiling of shared objects that is described in Profiling Shared Objects.

Establishing a Namespace

When the runtime linker binds an executable with its dependencies, a linked list of link-maps is generated to describe the application. The link-map structure describes each object within the application. The link-map structure is defined in /usr/include/sys/link.h. The symbol search mechanism that is required to bind together the objects of an application traverse this list of link-maps. This link-map list is said to provide the namespace for the applications symbol resolution.

The runtime linker is also described by a link-map. This link-map is maintained on a different list from the list of application objects. The runtime linker therefore resides in its own unique namespace, which prevents the application from seeing, or being able to directly access, any services within the runtime linker. An application can therefore only access the runtime linker through the filters provided by libc.so.1, or libdl.so.1.

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

In addition to these two standard link-map lists, the runtime linker allows the creation of an arbitrary number of additional link-map lists. Each of these additional link-map lists provides a unique namespace. The rtld-audit interface employs its own link-map list on which the audit libraries are maintained. The audit libraries are therefore isolated from the symbol binding requirements of the application. Every rtld-audit support library is assigned a unique new link-map identifier.

An audit library can inspect the application link-map list using dlmopen(3C). When dlmopen() is used with the RTLD_NOLOAD flag, the audit library can query the existence of an object without causing the object to be loaded.

Creating an Audit Library

An audit library is built like any other shared object. However, the audit libraries unique namespace within a process requires some additional care.

  • The library must provide all dependency requirements.

  • The library should not use system interfaces that do not provide for multiple instances of the interface within a process.

If an audit library references external interfaces, then the audit library must define the dependency that provides the interface definition. For example, if the audit library calls printf(3C), then the audit library must define a dependency on libc. See Generating a Shared Object Output File. Because the audit library has a unique name space, symbol references cannot be satisfied by the libc that is present in the application being audited. If an audit library has a dependency on libc, then two versions of libc.so.1 are loaded into the process. One version satisfies the binding requirements of the application link-map list. The other version satisfies the binding requirements of the audit link-map list.

To ensure that audit libraries are built with all dependencies recorded, use the link-editors –z defs option.

Some system interfaces assume that the interfaces are the only instance of their implementation within a process. Examples of such implementations are signals and malloc(3C). Audit libraries should avoid using such interfaces, as doing so can inadvertently alter the behavior of the application.


Note -  An audit library can allocate memory using mapmalloc(3MALLOC), as this allocation method can exist with any allocation scheme normally employed by the application.

Invoking the Auditing Interface

The rtld-audit interface is enabled by one of two means. Each method implies a scope to the objects that are audited.

  • Local auditing is enabled by defining one or more auditors at the time the object is built. See Recording Local Auditors. The audit libraries that are made available at runtime by this method are provided with information regarding the dynamic objects that have requested local auditing.

  • Global auditing is enabled by defining one or more auditors using the environment variable LD_AUDIT. Global auditing can also be enabled for an application by combining a local auditing definition with the –z globalaudit option. See Recording Global Auditors. The audit libraries that are made available at runtime by these methods are provided with information regarding all dynamic objects used by the application.

Both methods of defining auditors employ a string consisting of a colon-separated list of shared objects that are loaded by dlmopen(3C). Each object is loaded onto its own audit link-map list. Each object is searched for audit routines using dlsym(3C). Audit routines that are found are called at various stages during the applications execution.

Secure applications can only obtain audit libraries from trusted directories. By default, the only trusted directories that are known to the runtime linker for 32-bit objects are /lib/secure and /usr/lib/secure. For 64-bit objects, the trusted directories are /lib/secure/64 and /usr/lib/secure/64.


Note -  Auditing can be disabled at runtime by setting the environment variable LD_NOAUDIT to a non-null value.

Recording Local Auditors

Local auditing requirements can be established when an object is built using the link-editor options –p or –P. For example, to audit libfoo.so.1, with the audit library audit.so.1, record the requirement at link-edit time using the –p option.

$ cc -G -o libfoo.so.1 -Wl,-paudit.so.1 -K pic foo.c
$ elfdump -d libfoo.so.1 | grep AUDIT
     [2]  AUDIT             0x96                audit.so.1

At runtime, the existence of this audit identifier results in the audit library being loaded. Information is then passed to the audit library regarding the identifying object.

With this mechanism alone, information such as searching for the identifying object occurs prior to the audit library being loaded. To provide as much auditing information as possible, the existence of an object requiring local auditing is propagated to users of that object. For example, if an application is built with a dependency on libfoo.so.1, then the application is identified to indicate its dependencies require auditing.

$ cc -o main main.c libfoo.so.1
$ elfdump -d main | grep AUDIT
     [4]  DEPAUDIT          0x1be               audit.so.1

The auditing enabled through this mechanism results in the audit library being passed information regarding all of the applications explicit dependencies. This dependency auditing can also be recorded directly when creating an object by using the link-editor's –P option.

$ cc -o main main.c -Wl,-Paudit.so.1
$ elfdump -d main | grep AUDIT
     [3]  DEPAUDIT          0x1b2               audit.so.1

Recording Global Auditors

Global auditing requirements can be established by setting the environment variable LD_AUDIT. For example, this environment variable can be used to audit the application main together with all the dependencies of the application, with the audit library audit.so.1.

$ LD_AUDIT=audit.so.1 main

Global auditing can also be achieved by recording a local auditor in the application, together with the –z globalaudit option. For example, the application main can be built to enable global auditing by using the link-editor's –P option and –z globalaudit option.

$ cc -o main main.c -Wl,-Paudit.so.1 -z globalaudit
$ elfdump -d main | grep AUDIT
     [3]  DEPAUDIT          0x1b2               audit.so.1
    [26]  FLAGS_1       0x1000000              [ GLOBAL-AUDITING ]

The auditing enabled through either of these mechanisms results in the audit library being passed information regarding all of the dynamic objects of the application.

Audit Interface Interactions

Audit routines are provided one or more cookies. A cookie is a data item that describes an individual dynamic object. An initial cookie is provided to the la_objopen() routine when a dynamic object is initially loaded. This cookie is a pointer to the associated Link_map of the loaded dynamic object. However, the la_objopen() routine is free to allocate, and return to the runtime linker, an alternative cookie. This mechanism provides the auditor a means of maintaining their own data with each dynamic object, and receiving this data with all subsequent audit routine calls.

The rtld-audit interface enables multiple audit libraries to be supplied. In this case, the return information from one auditor is passed to the same audit routine of the next auditor. Similarly, a cookie that is established by one auditor is passed to the next auditor. Care should be taken when designing an audit library that expects to coexist with other audit libraries. A safe approach should not alter the bindings, or cookies, that would normally be returned by the runtime linker. Alteration of these data can produce unexpected results from audit libraries that follow. Otherwise, all auditors should be designed to cooperate in safely changing any binding or cookie information.

Audit Interface Functions

The following routines are provided by the rtld-audit interface. The routines are described in their expected order of use.


Note -  References to architecture, or object class specific interfaces are reduced to their generic name to simplify the discussions. For example, a reference to la_symbind32() and la_symbind64() is specified as la_symbind().
la_version()

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

uint_t la_version(uint_t version);

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

If the audit library return is zero, or a version that is greater than the rtld-audit interface the runtime linker supports, the audit library is discarded.

The remaining audit routines are provided one or more cookies. See Audit Interface Interactions.

Following the la_version() call, two calls are made to the la_objopen() routine. The first call provides link-map information for the executable, and the second call provides link-map information for the runtime linker.

la_objopen()

This routine is called when a new object is loaded by the runtime linker.

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

lmp provides the link-map structure that describes the new object. lmid identifies the link-map list to which the object has been added. cookie provides a pointer to an identifier. This identifier is initialized to the objects lmp. This identifier can be reassigned by the audit library to better identify the object to other rtld-audit interface routines.

The la_objopen() routine returns a value that indicates the symbol bindings of interest for this object. The return value is a mask of the following values that are defined in/usr/include/link.h.

  • LA_FLG_BINDTO – Audit symbol bindings to this object.

  • LA_FLG_BINDFROM – Audit symbol bindings from this object.

These values allow an auditor to select the objects to monitor with la_symbind(). A return value of zero indicates that binding information is of no interest for this object.

For example, an auditor can monitor the bindings from libfoo.so to libbar.so. la_objopen() for libfoo.so should return LA_FLG_BINDFROM. la_objopen() for libbar.so should return LA_FLG_BINDTO.

An auditor can monitor all bindings between libfoo.so and libbar.so. la_objopen() for both objects should return LA_FLG_BINDFROM and LA_FLG_BINDTO.

An auditor can monitor all bindings to libbar.so. la_objopen() for libbar.so should return LA_FLG_BINDTO. All la_objopen() calls should return LA_FLG_BINDFROM.

With the auditing version LAV_VERSION5, an la_objopen() call that represents the executable is provided to a local auditor. In this case, the auditor should not return a symbol binding flag, as the auditor may have been loaded too late to monitor any symbol bindings associated with the executable. Any flags that are returned by the auditor are ignored. The la_objopen() call provides the local auditor an initial cookie which is required for any subsequent la_preinit() or la_activity() calls.

la_activity()

This routine informs an auditor that link-map activity is occurring.

void la_activity(uintptr_t *cookie, uint_t flags);

cookie identifies the object heading the link-map. flags indicates the type of activity as defined in /usr/include/link.h.

  • LA_ACT_ADD – Objects are being added to the link-map list.

  • LA_ACT_DELETE – Objects are being deleted from the link-map list.

  • LA_ACT_CONSISTENT – Object activity has been completed.

An LA_ACT_ADD activity is called on process start up, following the la_objopen() calls for the executable and runtime linker, to indicate that new dependencies are being added. This activity is also called for lazy loading and dlopen(3C) events. An LA_ACT_DELETE activity is also called when objects are deleted with dlclose(3C).

Both the LA_ACT_ADD and LA_ACT_DELETE activities are a hint of the events that are expected to follow. There are a number of scenarios where the events that unfold might be different. For example, the addition of new objects can result in some of the new objects being deleted should the objects fail to relocate fully. The deletion of objects can also result in new objects being added should .fini executions result in lazy loading new objects. An LA_ACT_CONSISTENT activity follows any object additions or object deletions, and can be relied upon to indicate that the application link-map list is consistent. Auditors should be careful to verify actual results rather than blindly trusting the LA_ACT_ADD and LA_ACT_DELETE hints.

For auditing versions LAV_VERSION1 through LAV_VERSION4, la_activity() was only called for global auditors. With the auditing version LAV_VERSION5, activity events can be obtained by local auditors. An activity event provides a cookie that represents the application link-map. To prepare for this activity, and allow the auditor to control the content of this cookie, an la_objopen() call is first made to the local auditor. The la_objopen() call provides an initial cookie representing the application link-map. See Audit Interface Interactions.

la_objsearch()

This routine informs an auditor that an object is about to be searched for.

char *la_objsearch(const char *name, uintptr_t *cookie, uint_t flags);

name indicates the file or path name being searched for. cookie identifies the object initiating the search. flags identifies the origin and creation of name as defined in /usr/include/link.h.

  • LA_SER_ORIG – The initial search name. Typically, this name indicates the file name that is recorded as a DT_NEEDED entry, or the argument supplied to dlopen(3C).

  • LA_SER_LIBPATH – The path name has been created from a LD_LIBRARY_PATH component.

  • LA_SER_RUNPATH – The path name has been created from a runpath component.

  • LA_SER_DEFAULT – The path name has been created from a default search path component.

  • LA_SER_CONFIG – The path component originated from a configuration file. See crle(1).

  • LA_SER_SECURE – The path component is specific to secure objects.

The return value indicates the search path name that the runtime linker should continue to process. A value of NULL indicates that this path should be ignored. An audit library that monitors search paths should return name.

la_objfilter()

This routine is called when a filter loads a new filtee. See Shared Objects as Filters.

int la_objfilter(uintptr_t *fltrcook, const char *fltestr,
    uintptr_t *fltecook, uint_t flags);

fltrcook identifies the filter. fltestr points to the filtee string. fltecook identifies the filtee. flags is currently unused. la_objfilter() is called after calls to la_objopen() for both the filter and filtee have been made.

A return value of zero indicates that this filtee should be ignored. An audit library that monitors the use of filters should return a non-zero value.

la_preinit()

This routine is called once after all immediate dependencies have been loaded for the application.

void la_preinit(uintptr_t *cookie);

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

When la_preinit() is called, the process still requires threads initialization, including the creation of any initial thread local storage. See Program Startup. In addition, the initialization sections of all loaded objects still require collecting and sorting prior to their execution. See Initialization and Termination Routines. This function provides a convenient control point to add additional objects to the initial process. These objects can contribute to the initial thread local storage, and initialization of the process.

For auditing versions LAV_VERSION1 through LAV_VERSION4, la_preinit() was only called for global auditors. With the auditing version LAV_VERSION5, a preinit event can be obtained by local auditors. A preinit event provides a cookie that represents the application link-map. To prepare for this preinit, and allow the auditor to control the content of this cookie, an la_objopen() call is first made to the local auditor. The la_objopen() call provides an initial cookie representing the application link-map. See Audit Interface Interactions.

la_callinit()

This routine is called after threads initialization has completed, and all initial thread local storage has been established. In addition, all initialization routines have been collected and sorted ready for execution.

void la_callinit(uintptr_t *cookie);

cookie, and the calling from global or local auditors, is as described for la_preinit().

This interface, added with auditing version LAV_VERSION6, marks the transition to executing application code.

la_callentry()

This routine is called after all initialization routines have been executed.

void la_callentry(uintptr_t *cookie);

cookie, and the calling from global or local auditors, is as described for la_preinit().

This interface, added with auditing version LAV_VERSION6, marks the transition to the applications entry point.

la_symbind()

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

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

sym is a constructed symbol structure, whose sym->st_value indicates the address of the symbol definition being bound. See /usr/include/sys/elf.h. la_symbind32() adjusts the sym->st_name to point to the actual symbol name. la_symbind64() leaves sym->st_name to be the index into the bound objects string table.

ndx indicates the symbol index within the bound object's dynamic symbol table. refcook identifies the object making reference to this symbol. This identifier is the same identifier as passed to the la_objopen() routine that returned LA_FLG_BINDFROM. defcook identifies 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 convey information regarding the binding. This data item can also be used to modify the continued auditing of this procedure linkage table entry. This value is a mask of the symbol binding flags that are defined in /usr/include/link.h.

The following flags can be supplied to la_symbind().

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

  • LA_SYMB_ALTVALUE (LAV_VERSION2) – An alternate value was returned for the symbol value by a previous call to la_symbind().

If la_pltenter() or la_pltexit() routines exist, these routines are called after la_symbind() for procedure linkage table entries. These routines are called each time that the symbol is referenced. See also Audit Interface Limitations.

The following flags can be supplied from la_symbind() to alter this default behavior. These flags are applied as a bitwise-inclusive OR with the value pointed to by the flags argument.

  • LA_SYMB_NOPLTENTER – Do not call the la_pltenter() routine for this symbol.

  • LA_SYMB_NOPLTEXIT – Do not call the la_pltexit() routine for this symbol.

The return value indicates the address to which control should be passed following this call. An audit library that 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.

sym_name, which is applicable for la_symbind64() only, contains the name of the symbol being processed. This name is available in the sym->st_name field for the 32-bit interface.

la_pltenter()

These routines are system specific. These routines are called when a procedure linkage table entry, between two objects that have been tagged for binding notification, is called.

uintptr_t la_sparcv8_pltenter(Elf32_Sym *sym, uint_t ndx,
    uintptr_t *refcook, uintptr_t *defcook,
    La_sparcv8_regs *regs, uint_t *flags);

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

uintptr_t la_i86_pltenter(Elf32_Sym *sym, uint_t ndx,
    uintptr_t *refcook, uintptr_t *defcook,
    La_i86_regs *regs, uint_t *flags);

uintptr_t la_amd64_pltenter(Elf64_Sym *sym, uint_t ndx,
    uintptr_t *refcook, uintptr_t *defcook,
    La_amd64_regs *regs, uint_t *flags, const char *sym_name);

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

For la_sparcv8_pltenter() and la_sparcv9_pltenter(), regs points to the out registers. For la_i86_pltenter(), regs points to the stack and frame registers. For la_amd64_pltenter(), regs points to the stack and frame registers, and the registers used in passing integer arguments. regs are defined in /usr/include/link.h.

flags points to a data item that can convey information regarding the binding. This data item can be used to modify the continued auditing of this procedure linkage table entry. This data item is the same as pointed to by the flags from la_symbind()

The following flags can be supplied from la_pltenter() to alter the present auditing behavior. These flags are applied as a bitwise-inclusive OR with the value pointed to by the flags argument.

  • LA_SYMB_NOPLTENTERla_pltenter() is not be called again for this symbol.

  • LA_SYMB_NOPLTEXITla_pltexit() is 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 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.

la_pltexit()

This routine is called when a procedure linkage table entry, between two objects that have been tagged for binding notification, returns. This routine is called before control reaches the caller.

uintptr_t la_pltexit(Elf32_Sym *sym, uint_t ndx, uintptr_t *refcook,
    uintptr_t *defcook, uintptr_t retval);

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

sym, ndx, refcook, defcook and sym_name provide the same information as passed to la_symbind(). retval is the return code from the bound function. An audit library that monitors symbol binding should return retval. An audit library can intentionally return a different value.


Note -  The la_pltexit() interface is experimental. See Audit Interface Limitations.
la_objclose()

This routine is called after any termination code for an object has been executed and prior to the object being unloaded.

uint_t la_objclose(uintptr_t *cookie);

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

Audit Interface Control Flow

The following sections describe the auditing interface routines and actions an audit library can perform with each interface. The emphasis is on process initialization. These routines are presented in the order they are called in the common case of a global auditor that is provided at process startup.

Auditing interfaces fall into one of two categories, informational, and control.

Informational interfaces provide the audit library information about the executing process, such as object searching, object loading, and symbol bindings. In addition, these interfaces allow the auditor to modify the objects loaded, and to ask for notification of future symbol binding events.

Control interfaces are called to allow the audit library to track the start or end of a phase of activity within the process execution. These interfaces allow the auditor to safely inspect a consistent set of objects, and can even allow new objects to be loaded.

When an auditing library is first loaded, an immediate call is made to the library's la_version() interface. This handshake verifies that the audit library is supportable, and allows the audit library to define the interface version that the library requires from the runtime linker.

An audit library can be established at process startup, either from using LD_AUDIT, or from a local auditing definition within the executable object that starts the process. See Invoking the Auditing Interface. In this scenario, an la_objopen() call, for both the executable object, and the runtime linker, are provided to the audit library.

At this point the process is still in the early stages of construction. The auditor should refrain from performing any actions that might disturb this construction, such as adding additional objects to the process, or exhaustive symbol searches of the process. These actions can result in prematurely loading and relocating objects in an attempt to satisfy a symbol look up.

Dependencies that are immediately loaded at process initialization are each reported to the auditor library's la_objopen() interface. For processes that employ lazy loading, only a few dependencies may be loaded at process initialization. See Lazy Loading of Dynamic Dependencies. Each loaded object is relocated, which results in symbol bindings being established between symbol references and symbol definitions. These bindings are reported to the audit library's la_symbind() interface.

Once all immediate dependencies have been loaded, and relocated, the audit library's la_preinit() interface is called. At this point, the process is still under construction. Threads initialization and initialization routine collection are still pending. However, this interface provides a convenient control point to add additional objects to the initial process.

Once threads initialization is completed, the audit library's la_callinit() interface is called. At this point, all loaded objects are ready to execute, and their initialization routines have been collected and sorted in preparation for execution. See Initialization and Termination Routines. The la_callinit() control point marks the transition to executing application code.

The execution of application code results in function call bindings being established between symbol references and symbol definitions. These bindings are reported to the audit library's la_symbind() and/or la_pltenter() interfaces. With lazy loading, additional objects can be loaded to satisfy symbol references, which are reported to the audit library's la_objopen().

Once all initialization code has been executed, the audit library's la_callentry() interface is called. The la_callentry() control point marks the end of processes initialization, and the transition to the applications entry point, typically start() or main().

As the process continues to execute, more symbol bindings can occur, resulting in la_symbind() and/or la_pltenter() calls. Addition dependencies can be loaded, resulting in la_objopen() calls. New dependencies can also be unloaded, resulting in la_objclose() calls. Any loading or unloading of objects is bound by a pair of la_activity() calls. The first la_activity() hints at the targeted behavior, an object addition or deletion. The second la_activity() indicates that the dependency structure within the process is consistent. Auditors should restrict their inspection of the process to follow a consistent notification.

Audit Interface Example

The following simple example creates an audit library that prints the name of each shared object dependency loaded by the 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: /lib/libc.so.1 loaded
file: /lib/libm.so.2 loaded
file: /usr/lib/locale/en_US/en_US.so.2 loaded
Thur Aug  10 17:03:55 PST 2012

Audit Interface Demonstrations

A number of demonstration applications that use the rtld-audit interface are provided in the pkg:/source/demo/system 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 included in the pkg:/developer/base-developer-utilities package. perfcnt and symbindrep are example programs. These applications are not intended for use in a production environment.

Audit Interface Limitations

Limitations exist within the rtld-audit implementation. Take care to understand these limitation when designing an auditing library.

Exercising Application Code

An audit library receives information as objects are added to a process. At the time the audit library receives such information, the object being monitored might not be ready to execute. For example, an auditor can receive an la_objopen() call for a loaded object. However, the object must load its own dependencies and be relocated before any code within the object can be exercised. An audit library might want to inspect the loaded object by obtaining a handle using dlopen(3C). This handle can then be used to search for interfaces using dlsym(3C). However, interfaces obtained in this manner should not be called unless it is known that the initialization of the destination object has completed.

Use of la_pltexit()

There are some limitations to the use of the la_pltexit() family. These limitations stem from the need to insert an extra stack frame between the caller and callee to provide a la_pltexit() return value. This requirement is not a problem when calling just the la_pltenter() routines, as. In this case, any intervening stack can be cleaned up prior to transferring control to the destination function.

Because of these limitations, la_pltexit() should be considered an experimental interface. When in doubt, avoid the use of the la_pltexit() routines.

Functions That Directly Inspect the Stack

A small number of functions exist that directly inspect the stack or make assumptions of 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 are compromised by the extra stack that is created to support la_pltexit().

The runtime linker cannot detect functions of this type, and thus the audit library creator is responsible for disabling la_pltexit() for such routines.