This section describes the rtld-audit interface that enables a process to access runtime linking information regarding itself. 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 enable the audit library to access:
The search for dependencies. Search paths may be substituted by the audit library.
Information regarding loaded objects.
Symbol bindings that occur between these loaded objects. These bindings can be altered by the audit library.
Exploitation of the lazy binding mechanism provided by procedure linker table entries to allow auditing of function calls and their return values. The arguments to a function and its return value can be modified by the audit library. See Procedure Linkage Table (Processor-Specific).
Some of these facilities can be achieved by preloading specialized shared objects. A preloaded object exists within the same namespace as the objects of a process. This often restricts or complicates the implementation of the preloaded shared object. The rtld-audit interface offers the user a unique namespace in which to execute their audit libraries. This namespace ensures that the audit library does not intrude upon the normal bindings that occur within the process.
When the runtime linker binds a dynamic executable with its dependencies, it generates 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 namespace for process symbol resolution.
The runtime linker itself is also described by a link-map. This link-map is maintained on a different list from that of the application objects. The runtime linker therefore resides 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. The audit libraries are thus isolated from the symbol binding requirements of the application. Inspection of the application link-map list is possible with dlmopen(3DL). When used with the RTLD_NOLOAD flag, dlmopen(3DL) 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.
An audit library is built like any other shared object. Its unique namespace within a process requires some additional care. The namespace:
Must provide all dependency requirements.
Should not use system interfaces that do not provide for multiple instances of the interface within a process.
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 namespace, 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 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 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.
An audit library can allocate memory using mapmalloc(3MALLOC), as this allocation method can exist with any allocation scheme normally employed by the application.
The rtld-audit interface is enabled by one of two means. Each method implies a scope to the objects that are audited.
Global auditing is enabled using the runtime linker environment variable LD_AUDIT
. The audit
libraries made available by this method are provided with information regarding all dynamic objects used by the process.
Local auditing is enabled through dynamic entries recorded within an object at the time it was built. The audit libraries made available by this method are provided with information regarding those dynamic objects identified for auditing.
Either method of invocation consists of a string that contains a colon-separated list of shared objects that are loaded by dlmopen(3DL). Each object is loaded onto its own audit link-map list. Each object is also searched for audit routines using dlsym(3DL). Audit routines that are found are called at various stages during the applications execution.
The rtld-audit interface enables 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. Altering these bindings can produce unexpected results from audit libraries that follow.
Secure applications can only obtain audit libraries from trusted directories. Presently, the only trusted directory known to the runtime linker is /usr/lib/secure for 32–bit objects or /usr/lib/secure/64 for 64–bit objects.
Local auditing requirements can be established when an object is built using the link-editor options -p or -P. If you want to audit the use of a shared object libfoo.so.1, with the audit library audit.so.1, record this requirement at link-edit time using the -p option:
$ cc -G -o libfoo.so.1 -Wl,-paudit.so.1 -Kpic foo.c $ dump -Lv libfoo.so.1 | fgrep AUDIT [3] AUDIT audit.so.1 |
At runtime, the existence of this audit identifier results in the audit library being loaded and information being passed to it regarding the identifying object.
With this mechanism alone, information such as searching for the identifying object has occurred 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 that depends on libfoo.so.1, then the application is identified to indicate its dependencies require auditing:
$ cc -o main main.c libfoo.so.1 $ dump -Lv main | fgrep AUDIT [5] DEPAUDIT audit.so.1 |
The auditing enabled via this mechanism will result in the audit library being loaded and information being passed to it 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 $ dump -Lv main | fgrep AUDIT [5] DEPAUDIT audit.so.1 |
Auditing can be disabled at runtime by setting the environment variable LD_NOAUDIT
to a non-null value.
The following functions are provided by the rtld-audit interface and are described in their expected order of use.
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().
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.
uint_t la_version(uint_t version);
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.
This function 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.
This function 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 – This is the initial search name. Typically this indicates the file name that is recorded as a DT_NEEDED entry, or the argument supplied to dlmopen(3DL).
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 the crle(1) man page).
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 0 indicates that this path should be ignored. An audit library that simply monitors search paths should return name.
This function is called each time 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 modified by the audit library to better identify the object to other rtld-audit interface routines
The la_objopen() function returns a value that indicates the symbol bindings of interest for this object. These values can result in later calls to la_symbind(). 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 the la_symbind() function 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.
This function is called once after all objects have been loaded for the application, but before transfer of control to the application occurs.
void la_preinit(uintptr_t * cookie);
cookie identifies the primary object that started the process, normally the dynamic executable.
This function 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 (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() 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 describes the object making reference to this symbol. This identifier is the same as the one that is 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 convey information regarding the binding and 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:
LA_SYMB_NOPLTENTER – The la_pltenter() function is not be called for this symbol.
LA_SYMB_NOPLTEXIT – The la_pltexit() function is not be called for this symbol.
LA_SYMB_DLSYM – The symbol binding occurred as a result of calling dlsym(3DL).
LA_SYMB_ALTVALUE (LAV_VERSION2) – An alternate value was returned for the symbol value by a previous call to la_symbind().
By default, if the la_pltenter() or la_pltexit() functions exist within the audit library, they are called after la_symbind() 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.
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.
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.
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);
sym, ndx, refcook, defcook and sym_name provide the same information as passed to la_symbind().
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 convey information regarding the binding and 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_symbind(). This value is a mask of the following flags defined in /usr/include/link.h:
LA_SYMB_NOPLTENTER – la_pltenter() is not be called again for this symbol.
LA_SYMB_NOPLTEXIT – la_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 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.
This function is called when a procedure linkage symbol entry between two objects that have been tagged for binding notification returns, but 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 simply monitors symbol binding should return retval. An audit library can intentionally return a different value.
The la_pltexit() interface is experimental. See Audit Interface Limitations.
This function 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 was obtained from a previous la_objopen() and identifies the object. Any return value is presently ignored.
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/lib/locale/en_US/en_US.so.2 loaded Thur Aug 10 17:03:55 PST 2000 |
A number of demonstration applications that use the rtld-audit interface are provided in the SUNWosdem package under /usr/demo/link_audit:
This demo provides tracing of procedure calls between the dynamic objects of a named application.
This demo provides a stack trace for a specified function whenever called by a named application.
This demo traces the amount of time spent in each function for a named application.
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.
There are some limitations regarding 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 means of acquiring the la_pltexit() return value. This requirement 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() should be considered an experimental interface. When in doubt, avoid the use of the la_pltexit() routines.
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 are compromised by the extra stack 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.