Initialization processing is repeated for any objects added to the running process with dlopen(3DL).
Because cyclic dependencies often exist, the runtime linker also employs dynamic initialization invocation in an attempt to execute any initialization sections before the code in the object is called. During symbol binding, the runtime linker determines whether the initialization sections of the object being bound to have been called. If not, the runtime linker calls them before returning from the symbol binding procedure. The exact sequence of initialization calls can be observed by setting the LD_DEBUG environment variable to include basic. See "Debugging Library".
Dynamic objects can also provide termination sections. These sections, .fini_array and .fini, are created by the link-editor when a dynamic object is built, and are labeled with the .dynamic tags DT_FINI_ARRAY and DT_FINI respectively. See "Initialization and Termination Sections".
Any termination routines are organized such that they can be recorded by atexit(3C). These routines are called when the process calls exit(2), or when objects are removed from the running process with dlclose(3DL).
The functions whose addresses are contained in the array specified by DT_FINI_ARRAY are executed by the runtime linker in the reverse order in which their addresses appear in the array. If an object contains both DT_FINI and DT_FINI_ARRAY entries, the functions referenced by the DT_FINI_ARRAY entry are processed before the one referenced by the DT_FINI entry for that object.
Any .fini section within the dynamic executable is called from the application itself by the process termination mechanism supplied by the compiler driver. The dynamic executable's .fini section is called first, before its dependency's termination sections are executed.
Starting with the Solaris 2.6 release, termination routines are called in the topological order of dependencies. Prior to the Solaris 2.6 release, termination routines were called in load order.
Although this initialization and termination calling sequence seems quite straightforward, be careful about placing too much emphasis on this sequence. The ordering of objects can be affected by both shared object and application development. See "Dependency Ordering".
Security
Secure processes have some restrictions applied to the evaluation of their dependencies and runpaths to prevent malicious dependency substitution or symbol interposition.
The runtime linker categorizes a process as secure if the user is not a superuser, and either the real user and effective user identifiers are not equal, or the real group and effective group identifiers are not equal. See the getuid(2), geteuid(2), getgid(2) and getegid(2) man pages.
The default trusted directory known to the runtime linker is /usr/lib/secure for 32-bit objects or /usr/lib/secure/64 for 64-bit objects. The utility crle(1) may be used to specify additional trusted directories applicable for secure applications. Administrators who use this technique should ensure that the target directories are suitably protected from malicious intrusion.
If an LD_LIBRARY_PATH family environment variable is in effect for a secure process, then only the trusted directories specified by this variable will be used to augment the runtime linker's search rules. See "Directories Searched by the Runtime Linker".
In a secure process, any runpath specifications provided by the application or any of its dependencies will be used, provided they are full path names (that is, the path name starts with a `/').
In a secure process, the expansion of the $ORIGIN string is allowed only if it expands to a trusted directory. See "Security".
In a secure process, LD_SIGNAL is ignored.
Additional objects can be loaded with a secure process using the LD_PRELOAD or LD_AUDIT environment variables. These objects must be specified as full path names or simple file names. Full path names are restricted to known trusted directories. Simple file names, in which no `/' appears in the name, are located subject to the search path restrictions previously described. These files will therefore resolve only to known trusted directories.
In a secure process, any dependencies that consist of simple file names will be processed using the path name restrictions previously described. Dependencies that are expressed as full or relative path names will be used as provided. Therefore, the developer of a secure process should ensure that the target directory referenced as a full or relative path name dependency is suitably protected from malicious intrusion.
When creating a secure process, do not use relative path names to express dependencies or to construct dlopen(3DL) path names. This restriction should be applied to the application and to all dependencies.
Runtime Linking Programming Interface
Dependencies specified during the link-edit of an application are processed by the runtime linker during process initialization. In addition to this mechanism, the application can extend its address space during its execution by binding to additional objects. The application can request the same services of the runtime linker that are used to process the dependencies specified during the link-edit of the application.
This delayed object binding has several advantages:
By processing an object when it is required rather than during the initialization of an application, startup time can be greatly reduced. In fact, the object might not be required if its services are not needed during a particular run of the application, such as for help or debugging information.
The application can choose between several different objects, depending on the exact services required, such as for a networking protocol.
Any objects added to the process address space during execution can be freed after use.
An application can use the following typical scenario to access an additional shared object.
A shared object is located and added to the address space of a running application using dlopen(3DL). Any dependencies that this shared object has are located and added at this time.
The added shared object and its dependencies are relocated. Any initialization sections within these objects are called.
The application locates symbols within the added objects using dlsym(3DL). The application can then reference the data or call the functions defined by these new symbols.
After the application has finished with the objects, the address space can be freed using dlclose(3DL). Any termination sections within the objects being freed will be called at this time.
Any error conditions that occur as a result of using these runtime linker interface routines can be displayed using dlerror(3DL).
The services of the runtime linker are defined in the header file dlfcn.h and are made available to an application by the shared object libdl.so.1. In the following example, the file main.c can make reference to any of the dlopen(3DL) family of routines, and the application prog can bind to these routines at runtime.
$ cc -o prog main.c -ldl |
Loading Additional Objects
Additional objects can be added to a running process's address space using dlopen(3DL). This function takes a path name and a binding mode as arguments, and returns a handle to the application. This handle can be used to locate symbols for use by the application using dlsym(3DL).
If the path name is specified as a simple file name, one with no `/' in the name, then the runtime linker will use a set of rules to generate an appropriate path name. Path names that contain a `/' will be used as provided.
These search path rules are exactly the same as are used to locate any initial dependencies. See "Directories Searched by the Runtime Linker". For example, if the file main.c contains the following code fragment:
#include <stdio.h> #include <dlfcn.h> main(int argc, char ** argv) { void * handle; ..... if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); exit (1); } ..... |
then to locate the shared object foo.so.1, the runtime linker will use any LD_LIBRARY_PATH definition present at process initialization, followed by any runpath specified during the link-edit of prog. Finally, the runtime linker will use the default location /usr/lib for 32-bit objects, and /usr/lib/64 for 64-bit objects.
If the path name is specified as:
if ((handle = dlopen("./foo.so.1", RTLD_LAZY)) == NULL) { |
then the runtime linker will search for the file only in the current working directory of the process.
Note - Any shared object specified using dlopen(3DL) should be referenced by its versioned file name. For more information on versioning, see "Coordination of Versioned Filenames".
If the required object cannot be located, dlopen(3DL) will return a NULL handle. In this case dlerror(3DL) can be used to display the true reason for the failure. For example:
$ cc -o prog main.c -ldl $ prog dlopen: ld.so.1: prog: fatal: foo.so.1: open failed: No such \ file or directory |
If the object being added by dlopen(3DL) has dependencies on other objects, they too will be brought into the process's address space. This process will continue until all the dependencies of the specified object are loaded. This dependency tree is referred to as a group.
If the object specified by dlopen(3DL), or any of its dependencies, are already part of the process image, then the objects will not be processed any further. A valid handle will still be returned to the application. This mechanism prevents the same object from being loaded more than once, and enables an application to obtain a handle to itself. For example, if the previous main.c example contained the following dlopen() call:
if ((handle = dlopen((const char *)0, RTLD_LAZY)) == NULL) { |
then the handle returned from dlopen(3DL) can be used to locate symbols within the application itself, within any of the dependencies loaded as part of the process's initialization, or within any objects added to the process's address space, using a dlopen(3DL) that specified the RTLD_GLOBAL flag.