The runtime linker is responsible for searching for symbols that are required by objects at runtime. Typically, users become familiar with the default search model that is applied to a dynamic executable and its dependencies, and to the objects obtained through dlopen(3C). However, more complex flavors of symbol lookup can result because of the symbol attributes of an object, or through specific binding requirements.
Two attributes of an object affect symbol lookup. The first attribute is the requesting object's symbol search scope. The second attribute is the symbol visibility offered by each object within the process.
These attributes can be applied as defaults at the time the object is loaded. These attributes can also be supplied as specific modes to dlopen(3C). In some cases, these attributes can be recorded within the object at the time the object is built.
An object can define a world search scope, and/or a group search scope.
The object can search for symbols in any object of the same group. The dependency tree created from an object obtained with dlopen(3C), or from an object built using the link-editor's -B group option, forms a unique group.
An object can define that any of the object's exported symbols are globally visible or locally visible.
The runtime symbol search can also be dictated by a symbols visibility. Symbols assigned the STV_SINGLETON visibility are not affected by any symbol search scope. All references to a singleton symbol are bound to the first occurrence of a singleton definition within the process. See Table 7–20.
The simplest form of symbol lookup is outlined in the next section Default Symbol Lookup. Typically, symbol attributes are exploited by various forms of dlopen(3C). These scenarios are discussed in Symbol Lookup.
An alternative model for symbol lookup is provided when a dynamic object employes direct bindings. This model directs the runtime linker to search for a symbol directly in the object that provided the symbol at link-edit time. See Appendix D, Direct Bindings.
A dynamic executable and all the dependencies loaded with the executable are assigned world search scope, and global symbol visibility. A default symbol lookup for a dynamic executable or for any of the dependencies loaded with the executable, results in a search of each object. The runtime linker starts with the dynamic executable, and progresses through each dependency in the same order in which the objects were loaded.
ldd(1) lists the dependencies of a dynamic executable in the order in which the dependencies are loaded. For example, suppose the dynamic executable prog specifies libfoo.so.1 and libbar.so.1 as its dependencies.
$ ldd prog libfoo.so.1 => /home/me/lib/libfoo.so.1 libbar.so.1 => /home/me/lib/libbar.so.1
Should the symbol bar be required to perform a relocation, the runtime linker first looks for bar in the dynamic executable prog If the symbol is not found, the runtime linker then searches in the shared object /home/me/lib/libfoo.so.1, and finally in the shared object /home/me/lib/libbar.so.1.
Symbol lookup can be an expensive operation, especially when the size of symbol names increases and the number of dependencies increases. This aspect of performance is discussed in more detail in Performance Considerations. See Appendix D, Direct Bindings for an alternative lookup model.
The default relocation processing model also provides for a transition into a lazy loading environment. If a symbol can not be found in the presently loaded objects, any pending lazy loaded objects are processed in an attempt to locate the symbol. This loading compensates for objects that have not fully defined their dependencies. However, this compensation can undermine the advantages of a lazy loading.
By default, the runtime linker searches for a symbol first in the dynamic executable and then in each dependency. With this model, the first occurrence of the required symbol satisfies the search. Therefore, if more than one instance of the same symbol exists, the first instance interposes on all others.
An overview of how symbol resolution is affected by interposition is provided in Simple Resolutions. A mechanism for changing symbol visibility, and hence reducing the chance of accidental interposition is provided in Reducing Symbol Scope.
Symbols assigned the STV_SINGLETON visibility provide a form of interposition. All references to a singleton symbol are bound to the first occurrence of a singleton definition within the process. See Table 7–20.
Interposition can be enforced, on a per-object basis, if an object is explicitly identified as an interposer. Any object loaded using the environment variable LD_PRELOAD or created with the link-editor's -z interpose option, is identified as an interposer. When the runtime linker searches for a symbol, any object identified as an interposer is searched after the application, but before any other dependencies.
The use of all of the interfaces offered by an interposer can only be guaranteed if the interposer is loaded before any process relocation has occurred. Interposers provided using the environment variable LD_PRELOAD, or established as non-lazy loaded dependencies of the application, are loaded before relocation processing starts. Interposers that are brought into a process after relocation has started are demoted to normal dependencies. Interposers can be demoted if the interposer is lazy loaded, or loaded as a consequence of using dlopen(3C). The former category can be detected using ldd(1).
$ ldd -Lr prog libc.so.1 => /lib/libc.so.1 foo.so.2 => ./foo.so.2 libmapmalloc.so.1 => /usr/lib/libmapmalloc.so.1 loading after relocation has started: interposition request \ (DF_1_INTERPOSE) ignored: /usr/lib/libmapmalloc.so.1
If the link-editor encounters an explicitly defined interposer while processing dependencies for lazy loading, the interposer is recorded as a non-lazy loadable dependency.
Individual symbols within a dynamic executable can be defined as interposers using the INTERPOSE mapfile keyword. This mechanism is more selective that using the -z interpose option, and provides better insulation over adverse interposition that can occur as dependencies evolve. See Defining Explicit Interposition.