![]() |
![]() |
| ||||||||
When Relocations Are PerformedRelocations can be distinguish by when they are performed. This distinction arises due to the type of reference being made to the relocated offset, and is either: 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, 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 a relocation associated with it. Procedure linkage table entries are constructed so that when they are first called, control is passed to the runtime linker. The runtime linker looks up the required symbol and rewrites information in the associated object so that any 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's 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 and lazy reference relocations when an object is loaded, and before the application gains, or regains, control. For example, setting the environment variable as follows means that all relocations within the file prog and within its dependencies, will be processed before control is transferred to the application.
Objects can also be accessed with dlopen(3DL) with the mode defined as RTLD_NOW. Objects can also be built using the link-editor's -z now option to indicate that they require complete relocation processing at the time they are loaded. This relocation requirement is also propagated to any dependencies of the marked object at runtime. Note - Although the preceding examples of immediate and lazy references are typical, the creation of procedure linkage table entries is ultimately controlled by the relocation information provided by the relocatable object files used as input to a link-edit. Relocation records such as R_SPARC_WPLT30 and R_386_PLT32 instruct the link-editor to create a procedure linkage table entry are common for position-independent code. However, as a dynamic executable has a fixed location, external function references that can be determined at link-edit time can be converted to procedure linkage table entries regardless of the original relocation records. Relocation ErrorsThe 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:
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:
When the -r option is specified with ldd(1), all immediate and lazy reference relocations are processed. If either type of relocation cannot be resolved, a diagnostic message is produced. Loading Additional ObjectsThe 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.
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):
In another example the preloading is a little more complex and time consuming.
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):
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 interposes 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 DependenciesWhen a dynamic object is loaded into memory, it is examined for any additional dependencies. By default, if any dependencies exist they are immediately loaded. This cycle continues until the full dependency tree is exhausted. At which point all inter-object references, specified by relocations, are resolved. Under this default model, all the dependencies of an application are loaded into memory, and all data relocations are performed. These operations are performed regardless of whether the code in these dependencies is referenced by the application during its execution. Under a lazy loading model, any dependencies that are labeled for lazy loading are 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 are never 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. Lazy loading is used by the link-editor itself, which references a debugging library, liblddbg. 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 it can be moved to those invocations that ask for debugging output. The alternate method of achieving a 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 dlsym() references 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. An object is designated as lazily or normally loaded 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. By default, the -z nolazyload option is in effect. 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 triggers libdebug.so.1 loading.
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 is loaded at the initial startup of the program. The use of lazy loading can require a precise declaration of dependencies and runpaths through out the objects used by an application. For example, suppose two objects, libA.so and libB.so, both make reference to symbols in libX.so. libA.so declares it has a dependency on libX.so, but libB.so does not. Typically, when libA.so and libB.so are used together, libB.so can reference libX.so because libA.so made it available. But, if libA.so declares libX.so to be lazy loaded, it is possible that libX.so may not be loaded when libB.so makes reference to it. A similar failure can occur if libB.so declares libX.so as a dependency but fails to provide a runpath necessary to locate it. Regardless of lazy loading, it is recommended that dynamic objects declare all their dependencies and how to locate them. With lazy loading, this dependency information becomes even more important. Note - Lazy loading can be disabled at runtime by setting the environment variable LD_NOLAZYLOAD to a non-null value. | ||||||||
| ||||||||