Before transferring control to an application, the runtime linker processes any initialization sections found in the application and any loaded dependencies. The initialization sections .preinit_array, .init_array, and .init are created by the link-editor when a dynamic object is built.
The runtime linker executes functions whose addresses are contained in the .preinit_array and .init_array sections. These functions are executed in the same order in which their addresses appear in the array. The runtime linker executes an .init section as an individual function. If an object contains both .init and .init_array sections, the .init section is processed before the functions defined by the .init_array section for that object.
A dynamic executable may provide pre-initialization functions in a .preinit_array section. These functions are executed after the runtime linker has built the process image and performed relocations but before any other initialization functions. Pre-initialization functions are not permitted in shared objects.
Any .init section within the dynamic executable is called from the application itself by the process startup mechanism supplied by the compiler driver. The .init section within the dynamic executable is called last, after all dependency initialization sections are executed.
Dynamic objects can also provide termination sections. The termination sections .fini_array and .fini are created by the link-editor when a dynamic object is built.
Any termination sections 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 runtime linker executes functions whose addresses are contained in the .fini_array section. These functions are executed in the reverse order in which their addresses appear in the array. The runtime linker executes a .fini section as an individual function. If an object contains both .fini and .fini_array sections, the functions defined by the .fini_array section are processed before the .fini section 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 .fini section of the dynamic executable is called first, before all dependency termination sections are executed.
For more information regarding the creation of initialization and termination sections by the link-editor see Initialization and Termination Sections.
To determine the order of executing initialization and termination code within a process at runtime is a complex issue involving dependency analysis. This process has evolved substantially from the original inception of initialization and termination sections. This process attempts to fulfill the expectations of modern languages and current programming techniques. However, scenarios can exist, where user expectations are hard to meet. Understanding these scenarios, and limiting the content of initialization and termination code can provide both flexible and predictable runtime behavior.
Prior to the Solaris 2.6 release, dependency initialization routines were called in reverse load order, which is the reverse order of the dependencies displayed with ldd(1). Similarly, dependency termination routines were called in load order. However, as dependency hierarchies became more complex, this simple ordering approach became inadequate.
Starting with the Solaris 2.6 release, the runtime linker constructs a topologically sorted list of objects that have been loaded. This list is built from the dependency relationship expressed by each object, together with any symbol bindings that occur outside of the expressed dependencies.
Initialization sections are executed in the reverse topological order of the dependencies. If cyclic dependencies are found, the objects that form the cycle cannot be topologically sorted. The initialization sections of any cyclic dependencies are executed in their reverse load order. Similarly, termination routines are called in the topological order of dependencies and any cyclic dependencies are executed in their load order.
Use ldd(1) with the -i option to display the initialization order of an object's dependencies. For example, the following dynamic executable and its dependencies exhibit a cyclic dependency:
$ dump -Lv B.so.1 | grep NEEDED [1] NEEDED C.so.1 $ dump -Lv C.so.1 | grep NEEDED [1] NEEDED B.so.1 $ dump -Lv main | grep NEEDED [1] NEEDED A.so.1 [2] NEEDED B.so.1 [3] NEEDED libc.so.1 $ ldd -i main A.so.1 => ./A.so.1 B.so.1 => ./B.so.1 libc.so.1 => /usr/lib/libc.so.1 C.so.1 => ./C.so.1 libdl.so.1 => /usr/lib/libdl.so.1 cyclic dependencies detected, group[1]: ./libC.so.1 ./libB.so.1 init object=/usr/lib/libc.so.1 init object=./A.so.1 init object=./C.so.1 - cyclic group [1], referenced by: ./B.so.1 init object=./B.so.1 - cyclic group [1], referenced by: ./C.so.1 |
Prior to Solaris 8 10/00, the environment variable LD_BREADTH
could be set to a non-null value to force the runtime linker to execute
initialization and termination sections in pre-Solaris 2.6 order. This functionality has since been disabled, as the initialization dependencies of many applications have become complex and mandate
topological sorting. Any LD_BREADTH
setting is now silently ignored.
Initialization processing is repeated for any objects added to the running process with dlopen(3DL). Termination processing is also carried out for any objects unloaded from the process as a result of a call to dlclose(3DL).
Symbol bindings are incorporated as part of dependency analysis because many shared objects exist that do not express their dependencies accurately. Incorporating symbol bindings can therefore help produce a more accurate dependency relationship. However, the addition of symbol binding information to objects that do not express all their dependencies, may still be insufficient to determine an objects complete dependencies. The most common model of loading objects uses lazy binding. With this model, only immediate reference symbol bindings are processed before initialization processing. Symbol bindings from lazy references may still be pending, and may extend the dependency relationships so far established.
As the dependency analysis of an object may be incomplete, and as cyclic dependencies often exist, the runtime linker also provides for dynamic initialization. This initialization attempts to execute any initialization sections before any functions in the same object are called. During lazy 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.
Dynamic initialization can not be revealed with ldd(1). However,
the exact sequence of initialization calls can be observed at runtime by setting the LD_DEBUG
environment variable to include the token basic.
See Debugging Library.
Dynamic initialization is only available when processing lazy references. Use of the environment variable LD_BIND_NOW
, objects built with the -z
now option, or objects referenced by dlopen(3DL) with
mode RTLD_NOW, circumvent any dynamic initialization.
Objects that are pending initialization, and are referenced through dlopen(3DL), will be initialized prior to returning control from this function.
The preceding sections describe the various techniques employed to execute initialization and termination sections in a manner that attempts to meet user expectations. However, coding style and link-editing practices should also be employed to simplify the initialization and termination relationships between dependencies. This simplification helps keep initialization and termination processing predictable, and less prone to any side affects of unexpected dependency ordering.
Keep the content of initialization and termination sections to a minimum. Avoid global constructors by initializing objects at runtime. Reduce the dependency of initialization and termination code on other dependencies. Explicitly define the dependency requirements of all dynamic objects. See Generating a Shared Object Output File. Do not express dependencies that are not required. See Shared Object Processing. Avoid cyclic dependencies. Do not depend on the order of an initialization or termination sequence. The ordering of objects can be affected by both shared object and application development. See Dependency Ordering.