After the runtime linker has loaded all the dependencies required by an application, the linker processes each object and performs all necessary relocations.
During the link-editing of an object, any relocation information supplied with the input relocatable objects is applied to the output file. However, when creating a dynamic object, many of the relocations cannot be completed at link-edit time. These relocations require logical addresses that are known only when the objects are loaded into memory. In these cases, the link-editor generates new relocation records as part of the output file image. The runtime linker must then process these new relocation records.
For a more detailed description of the many relocation types, see Relocation Entries. Two basic types of relocation exist.
The relocation records for an object can be displayed by using elfdump(1). In the following example, the file libbar.so.1 contains two relocation records that indicate that the global offset table, or .got section, must be updated.
$ elfdump -r libbar.so.1 Relocation Section: .rel.got: type offset section symbol R_SPARC_RELATIVE 0x10438 .rel.got R_SPARC_GLOB_DAT 0x1043c .rel.got foo
The first relocation is a simple relative relocation that can be seen from the relocation type and that no symbol is referenced. This relocation needs to use the base address at which the object is loaded into memory to update the associated .got offset.
The second relocation requires the address of the symbol foo. To complete this relocation, the runtime linker must locate this symbol from either the executable or from one of its dependencies.
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 an 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 Figure 34, Table 34, ELF Symbol Visibility.
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 employs 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 Direct Bindings.
An executable and all the dependencies loaded with the executable are assigned world search scope, and global symbol visibility. A default symbol lookup for an executable or for any of the dependencies loaded with the executable, results in a search of each object. The runtime linker starts with the executable, and progresses through each dependency in the same order in which the objects were loaded.
ldd(1) lists the dependencies of an executable in the order in which the dependencies are loaded. For example, suppose the 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 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.
The default relocation processing model also provides for a transition into a lazy loading environment. If a symbol can not be found in the currently 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 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.
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
Individual symbols within an 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.
Relocations can be separated into two types dependent upon when the relocation is performed. This distinction arises due to the type of reference being made to the relocated offset.
An immediate reference refers to a relocation that must be determined immediately when an object is loaded. These references are typically to data items used by the object code, pointers to functions, and even calls to functions made from position-dependent shared objects. These relocations cannot provide the runtime linker with knowledge of when the relocated item is referenced. Therefore, all immediate relocations must be carried out when an object is loaded, and before the application gains, or regains, control.
A lazy reference refers to a relocation that can be determined as an object executes. These references are typically calls to global functions made from position-independent shared objects and position-independent executables, or calls to external functions made from a dynamic executable. During the compilation and link-editing of any dynamic module that provide these references, the associated function calls become calls to a procedure linkage table entry. These entries make up the .plt section. Each procedure linkage table entry becomes a lazy reference with an associated relocation.
As part of the first call to a procedure linkage table entry, control is passed to the runtime linker. The runtime linker looks up the required symbol and rewrites the entry information in the associated object. Future calls to this procedure linkage table entry go directly to the function. This mechanism enables relocations of this type to be deferred until the first instance of a function is called. This process is sometimes referred to as lazy binding.
The runtime linker default mode is to perform lazy binding whenever procedure linkage table relocations are provided. This default can be overridden by setting the environment variable LD_BIND_NOW to any non-null value. This environment variable setting causes the runtime linker to perform both immediate reference and lazy reference relocations when an object is loaded. These relocations are performed before the application gains, or regains, control. For example, all relocations within the file prog together within its dependencies are processed under the following environment variable. These relocations are processed before control is transferred to the application.
$ LD_BIND_NOW=1 prog
Objects can also be accessed with dlopen(3C) with the mode defined as RTLD_NOW. Objects can also be built using the link-editor's –z now option to indicate that the object requires complete relocation processing at the time the object is loaded. This relocation requirement is also propagated to any dependencies of the marked object at runtime.
However, a dynamic executable is typically created from position dependent code, which might not indicate that a procedure linkage table entry is required. Because a dynamic executable has a fixed location, the link-editor can create a procedure linkage table entry when a reference is bound to an external function definition. This procedure linkage table entry creation occurs regardless of the original relocation records.
The most common relocation error occurs when a symbol cannot be found. This condition results in an appropriate runtime linker error message together with the termination of the application. In the following example, the symbol bar, which is referenced in the file libfoo.so.1, cannot be located.
$ ldd prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libm.so.2 => /lib/libm.so.2 $ prog ld.so.1: prog: fatal: relocation error: file ./libfoo.so.1: \ symbol bar: referenced symbol not found
During the link-edit of an executable, any potential relocation errors of this sort are flagged as fatal undefined symbols. See Generating an Executable Output File for examples. However, a runtime relocation error can occur if a dependency located at runtime is incompatible with the original dependency referenced as part of the link-edit. In the previous example, prog was built against a version of the shared object libbar.so.1 that contained a symbol definition for bar.
The use of the –z nodefs option during a link-edit suppresses the validation of an objects runtime relocation requirements. This suppression can also lead to runtime relocation errors.
If a relocation error occurs because a symbol used as an immediate reference cannot be found, the error condition occurs immediately during process initialization. With the default mode of lazy binding, if a symbol used as a lazy reference cannot be found, the error condition occurs 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 object can be validated using ldd(1).
When the –d option is specified with ldd(1), every dependency is printed and all immediate reference relocations are processed. If a reference cannot be resolved, a diagnostic message is produced. From the previous example, the –d option would result in the following error diagnostic.
$ ldd -d prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libm.so.2 => /lib/libm.so.2 symbol not found: bar (./libfoo.so.1)
When the –r option is specified with ldd(1), all immediate reference and lazy reference relocations are processed. If either type of relocation cannot be resolved, a diagnostic message is produced.