Relocation Errors
The most common relocation error occurs when a symbol cannot be found. This condition results in an appropriate runtime linker error message and the termination of the application. For example:
$ ldd prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /usr/lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libdl.so.1 => /usr/lib/libdl.so.1 $ prog ld.so.1: prog: fatal: relocation error: file ./libfoo.so.1: \ symbol bar: referenced symbol not found |
The symbol bar, which is referenced in the file libfoo.so.1, cannot be located.
During the link-edit of a dynamic executable, any potential relocation errors of this sort are flagged as fatal undefined symbols. See "Generating an Executable Output File" for examples. This runtime relocation error can occur if the link-edit of main used a different version of the shared object libbar.so.1 that contained a symbol definition for bar, or if the -z nodefs option was used as part of the link-edit.
If a relocation error of this type occurs because a symbol used as an immediate reference cannot be located, the error condition will occur immediately during process initialization. Because of the default mode of lazy binding, if a symbol used as a lazy reference cannot be found, the error condition will occur after the application has gained control. This latter case can take minutes or months, or might never occur, depending on the execution paths exercised throughout the code.
To guard against errors of this kind, the relocation requirements of any dynamic executable or shared object can be validated using ldd(1).
When the -d option is specified with ldd(1), all dependencies will be printed and all immediate reference relocations will be processed. If a reference cannot be resolved, a diagnostic message is produced. From the previous example this option would result in:
$ ldd -d prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /usr/lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libdl.so.1 => /usr/lib/libdl.so.1 symbol not found: bar (./libfoo.so.1) |
When the -r option is specified with ldd(1), all immediate and lazy reference relocations will be processed. If either type of relocation cannot be resolved, a diagnostic message is produced.
Loading Additional Objects
The runtime linker provides an additional level of flexibility by enabling you to introduce new objects during process initialization.
The environment variable LD_PRELOAD can be initialized to a shared object or relocatable object file name, or a string of file names separated by white space. These objects are loaded after the dynamic executable and before any dependencies. These objects are assigned world search scope, and global symbol visibility.
$ LD_PRELOAD=./newstuff.so.1 prog |
The dynamic executable prog is loaded, followed by the shared object newstuff.so.1, and then by the dependencies defined within prog.
The order in which these objects are processed can be displayed using ldd(1):
$ LD_PRELOAD=./newstuff.so.1 ldd prog ./newstuff.so.1 => ./newstuff.so libc.so.1 => /usr/lib/libc.so.1 |
In another example the preloading is a little more complex and time consuming.
$ LD_PRELOAD="./foo.o ./bar.o" prog |
The runtime linker first link-edits the relocatable objects foo.o and bar.o to generate a shared object that is maintained in memory. This memory image is then inserted between the dynamic executable and its dependencies in the same manner as the shared object newstuff.so.1 was preloaded in the previous example. Again, the order in which these objects are processed can be displayed with ldd(1):
$ LD_PRELOAD="./foo.o ./bar.o" ldd prog ./foo.o => ./foo.o ./bar.o => ./bar.o libc.so.1 => /usr/lib/libc.so.1 |
These mechanisms of inserting an object after a dynamic executable take the concept of interposition to another level. You can use these mechanisms to experiment with a new implementation of a function that resides in a standard shared object. If you preload an object containing this function, the object will interpose on the original. Thus the old functionality can be completely hidden with the new preloaded version.
Another use of preloading is to augment a function that resides in a standard shared object. The intention is to interpose the new symbol on the original, enabling the new function to carry out some additional processing while calling through to the original function. This mechanism requires either a symbol alias that is to be associated with the original function or the ability to look up the original symbol's address.
Lazy Loading of Dynamic Dependencies
The default model for loading a dynamic dependency is to load it into memory and examine it for any additional dependencies. If any exist they in turn are immediately loaded. This cycle continues until the full dependency tree is exhausted, at which point all inter-object references (relocations) are resolved.
Under this default model, all dependencies of an application are loaded into memory, and all data relocations are performed, regardless of whether the code in these dependencies will actually be referenced by the application during its execution.
Under a lazy loading model, any dependencies that are labeled for lazy loading will be loaded only when explicitly referenced. By taking advantage of a function call's lazy binding the loading of a dependency is delayed until it is first referenced. In fact, objects that are never referenced will never be loaded.
A relocation reference can be immediate or lazy. Because immediate references must be resolved when an object is initialized, any dependency that satisfies this reference must be immediately loaded. Therefore, identifying such a dependency as lazy loadable has little effect. See "When Relocations Are Performed". Immediate references between dynamic objects are generally discouraged.
An example of lazy loading is the link-editor itself, which references a debugging library, liblddbg.so.4. Because debugging is only called upon infrequently, loading this library every time the link-editor is invoked is unnecessary and expensive. By indicating that this library can be lazily loaded, the expense of processing can be moved to those invocations that ask for debugging output.
The alternate method of achieving this lazy loading model is to use dlopen() and dlsym() to load and bind to a dependency when needed. This is ideal if the number of references (through dlsym()) is small, or the dependency name or location is not known at link-edit time. For more complex interactions with known dependencies, coding to normal symbol references and designating the dependency to be lazily loaded is simpler.
Designating an object to be lazily or normally loaded is done through the link-editor options -z lazyload and -z nolazyload respectfully. These options are position-dependent on the link-edit command line. Any dependency found following the option takes on the loading attribute specified by the option.
The following simple program has a dependency on libdebug.so.1. The dynamic section (.dynamic), shows libdebug.so.1 is marked for lazy loading. The symbol information section (.SUNW_syminfo), shows the symbol reference that will trigger libdebug.so.1 loading.
$ cc -o prog prog.c -L. -zlazyload -ldebug -znolazyload -R'$ORIGIN' $ elfdump -d prog Dynamic Section: .dynamic index tag value [0] POSFLAG_1 0x1 [ LAZY ] [1] NEEDED 0x123 libdebug.so.1 [2] NEEDED 0x131 libc.so.1 [3] RUNPATH 0x13b $ORIGIN ... $ elfdump -y prog Syminfo section: .SUNW_syminfo index flgs boundto symbol ... [52] DL [1] libdebug.so.1 debug |
The POSFLAG_1 with the value of LAZY designates that the following NEEDED entry, libdebug.so.1, should be lazily loaded. Because libc.so.1 has no preceding LAZY flag it will be loaded at the initial startup of the program.
Note - Lazy loading can be disabled at runtime by setting the environment variable LD_NOLAZYLOAD to a non-null value.
Initialization and Termination Routines
Before transferring control to the application, the runtime linker processes any initialization sections found in the application and its dependencies. The .preinit_array, .init_array, and .init sections, are created by the link-editor when a dynamic object is built. These sections are labeled with the .dynamic tags DT_PREINIT_ARRAY, DT_INIT_ARRAY and DT_INIT respectively. See "Initialization and Termination Sections".
The functions whose addresses are contained in the arrays specified by DT_PREINIT_ARRAY and DT_INIT_ARRAY are executed by the runtime linker in the same order in which their addresses appear in the array. If an object contains both DT_INIT and DT_INIT_ARRAY entries, the function referenced by the DT_INIT entry is processed before functions referenced by the DT_INIT_ARRAY entry for that object.
A dynamic executable may provide pre-initialization functions in the .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.
Note - Any DT_INIT section within the dynamic executable is called from the application itself by the process startup mechanism supplied by the compiler driver. The dynamic executable's DT_INIT section is called last, after all its dependencies initialization sections are executed.
Prior to the Solaris 2.6 release, any initialization routines from dependencies were called in reverse load order, which is the reverse order of the dependencies displayed with ldd(1). Starting with the Solaris 2.6 release, the runtime linker constructs a dependency-ordered list of initialization routines from the dependencies that have been loaded. This list is built from the dependency relationship expressed by each object, in addition to any bindings that occur outside of the expressed dependencies.
The initialization sections are executed in the reverse topological order of the dependencies. If any cyclic dependencies are found, the objects that form the cycle cannot be topologically sorted. Thus, the initialization sections of those sections are executed in their reverse 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 |
Caution - 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 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.