Chapter 3 Runtime Linker

As part of the initialization and execution of a dynamic executable, an interpreter is called to complete the binding of the
application to its dependencies. In the Solaris OS, this interpreter is referred to as the runtime linker.

During the link-editing of a dynamic executable, a special .interp section, together with an associated program header,
are created. This section contains a path name specifying the program's interpreter. The default name supplied by the link-editor is the name of the runtime linker: /usr/lib/ld.so.1 for
a 32–bit executable and /usr/lib/64/ld.so.1 for a 64–bit executable.

Note –

ld.so.1 is a special case of a shared object. Here, a version number of 1 is used. However, later Solaris OS releases might provide higher version numbers.

During the process of executing a dynamic object, the kernel loads the file and reads the program header information. See Program Header.
From this information, the kernel locates the name of the required interpreter. The kernel loads, and transfers control to this interpreter, passing sufficient information to enable the interpreter
to continue executing the application.

In addition to initializing an application, the runtime linker provides services that enable the application to extend its address space. This process involves loading additional objects and
binding to symbols provided by these objects.

The runtime linker performs the following actions.

Analyzes the executable's dynamic information section (.dynamic)
and determines what dependencies are required.

Locates and loads these dependencies, analyzing their dynamic information sections to determine if any additional dependencies are required.

Performs any necessary relocations to bind these objects in preparation for process execution.

Calls any initialization functions provided by the dependencies.

Passes control to the application.

Can be called upon during the application's execution, to perform any delayed function binding.

Can be called upon by the application
to acquire additional objects with dlopen(3C), and
bind to symbols within these objects with dlsym(3C).

Shared Object Dependencies

When the runtime linker creates the memory segments for a program, the dependencies tell what shared objects are needed to supply the program's services. By repeatedly connecting referenced
shared objects and their dependencies, the runtime linker generates a complete process image.

Note –

Even when a shared object is referenced multiple times in the dependency list, the runtime linker connects the object only once to the process.

Locating Shared Object Dependencies

When linking a dynamic executable, one or more shared objects are explicitly referenced. These objects are recorded as dependencies within the dynamic executable.

The runtime linker uses this dependency information to locate, and load, the associated objects. These dependencies are processed in the same order as the dependencies were referenced during
the link-edit of the executable.

Once all the dynamic executable's dependencies are loaded, each dependency is inspected, in the order the dependency is loaded, to locate any additional dependencies. This process continues
until all dependencies are located and loaded. This technique results in a breadth-first ordering of all dependencies.

Directories Searched by the Runtime Linker

The runtime linker looks in two default locations for dependencies. When processing 32–bit objects, the
default locations are /lib and /usr/lib. When processing 64–bit objects, the default locations are /lib/64 and /usr/lib/64. Any dependency specified as a simple file name is prefixed with these default directory names. The resulting path name is used to locate the actual file.

The dependencies of a dynamic executable or shared object can be displayed using ldd(1). For example, the file /usr/bin/cat has the following dependencies:

The file /usr/bin/cat has a dependency, or needs, the files libc.so.1 and libm.so.2.

The dependencies
recorded in an object can be inspected using elfdump(1).
Use this command to display the file's .dynamic section, and look for entries that have a NEEDED tag. In the following example, the dependency libm.so.2,
displayed in the previous ldd(1) example, is not recorded
in the file /usr/bin/cat. ldd(1) shows
the total dependencies of the specified file, and libm.so.2 is actually a dependency of /lib/libc.so.1.

In the previous elfdump(1) example, the dependencies
are expressed as simple file names. In other words, there is no `/' in the name. The use of a simple file name requires the runtime linker to generate the path name from a set of default search
rules. File names that contain an embedded `/', are used as provided.

Frequently, dependencies are distributed in directories other than /lib and /usr/lib, or /lib/64 and /usr/lib/64.
If a dynamic executable or shared object needs to locate dependencies in another directory, the runtime linker must explicitly be told to search this directory.

You can specify additional search path, on a per-object basis, by recording a runpath during
the link-edit of an object. See Directories Searched by the Runtime Linker for details on recording this information.

A runpath recording can be displayed using elfdump(1). Reference the .dynamic entry that has the RUNPATH tag. In the following example, prog has a dependency on libfoo.so.1. The runtime linker must search directories /home/me/lib and /home/you/lib before it looks in the default location.

Another
way to add to the runtime linker's search path is to set one of the LD_LIBRARY_PATH family of environment variables. This environment variable, which is analyzed once at process startup,
can be set to a colon-separated list of directories. These directories are searched by the runtime linker before any runpath specification or default directory.

These environment variables are well suited to debugging purposes, such as forcing an application to bind to a local dependency. In the following example, the file prog from
the previous example is bound to libfoo.so.1, found in the present working directory.

$ LD_LIBRARY_PATH=. prog

Although useful as a temporary mechanism of influencing the runtime linker's search path, the use of LD_LIBRARY_PATH is strongly discouraged in production software. Any dynamic
executables that can reference this environment variable will have their search paths augmented. This augmentation can result in an overall degradation in performance. Also, as pointed out in Using an Environment Variable and Directories Searched by the Runtime Linker, LD_LIBRARY_PATH affects
the link-editor.

Environmental search paths can result in a 64–bit executable searching a path that contains a 32–bit library that matches the name being looked for. Or, the other way around. The
runtime linker rejects the mismatched 32–bit library and continues its search looking for a valid 64–bit match. If no match is found, an error message is generated. This rejection can
be observed in detail by setting the LD_DEBUG environment variable to include the files token. See Debugging Library.

Configuring the Default Search Paths

The default search paths used by the runtime linker are /lib and /usr/lib for
32–bit application. For 64–bit applications, the default search paths are /lib/64 and /usr/lib/64. These search paths can be administered using
a runtime configuration file created by the crle(1) utility.
This file is often a useful aid for establishing search paths for applications that have not been built with the correct runpaths.

A configuration file can be constructed in the default location /var/ld/ld.config, for 32–bit applications, or /var/ld/64/ld.config, for 64–bit
applications. This file affects all applications of the respective type on a system. Configuration files can also be created in other locations, and the runtime linker's LD_CONFIG environment
variable used to select these files. This latter method is useful for testing a configuration file before installing the file in the default location.

Dynamic String Tokens

The runtime linker allows for the expansion of various dynamic string tokens. These tokens are applicable for filter, runpath and dependency definitions.

Relocation Processing

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 executable or shared
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.

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.

The first relocation is a simple relative relocation that can be seen from its relocation type and the 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 dynamic executable
or from one of its dependencies.

Relocation Symbol Lookup

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.

world

The object can search for symbols in any
other global object within the process.

group

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.

global

The object's exported symbols can be referenced from any object that has world search scope.

local

The object's exported symbols can be referenced only from other objects that make up the same group.

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 Direct Bindings.

Default Symbol Lookup

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.

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.

Note –

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 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.

Runtime Interposition

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.

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).

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.

Direct Bindings

An object that uses direct bindings maintains the relationship between a symbol reference and the dependency that provided the definition. The runtime linker uses this information to
search directly for the symbol in the associated object, rather than carry out the default symbol search model. Direct binding information can only be established to dependencies specified with
the link-edit. Therefore, use of the -z defs option is recommended.

The direct binding of a symbol reference to a symbol definition can be established with one of the following mechanisms.

With the -B direct option. This option establishes direct bindings between the object being built and all of the objects dependencies. This option also establishes
direct bindings between any symbol reference and symbol definition within the object being built.

The use of -B direct also enables lazy loading. This enabling is equivalent
to adding the option -z lazyload to the front of the link-edit command line. See Lazy Loading of Dynamic Dependencies.

With the -z direct option. This option establishes direct bindings from the object being built to any dependencies that follow the option on the command line. This
option can be used together with the -z nodirect option, to toggle the use of direct bindings between dependencies. This option does not establish direct bindings between any symbol
reference and symbol definition within the object being built.

Direct binding can significantly reduce the symbol lookup overhead incurred by a dynamic process that has many symbolic relocations and many dependencies. This model also enables multiple
symbols of the same name to be located from different objects that have been bound to directly.

Note –

Direct bindings can be disabled at runtime by setting the environment variable LD_NODIRECT to a non-null value.

The default symbol search model allows all references to a symbol to bind to one definition. Direct binding circumvents implicit interposition symbols, as direct bindings bypass the default
search model. However, any object explicitly identified as an interposer is searched before the object that supplies the symbol definition. Explicit interposers include objects loaded using the
environment variable LD_PRELOAD, or objects created with the link-editor's -z interpose option. See Runtime Interposition.

Some interfaces exist to provide alternative implementations of a default technology. These interfaces expect their implementation to be the only instance of that technology within a process.
An example is the malloc(3C) family. There are various malloc() family implementations, and each family expects to be the only implementation used within a process. The direct binding to an interface within such a family should be avoided,
otherwise more than one instance of the technology can be referenced by the same process. For example, one dependency within a process can directly bind against libc.so.1, while
another dependency directly binds against libmapmalloc.so.1. The potential for inconsistent use of two different implementations of malloc() and free() is
error prone.

Objects that provide interfaces that expect to be single-instance within a process, should prevent any direct binding to their interfaces. An interface can be labelled to prevent any caller
from directly binding to the interface with one of the following mechanisms.

With the -B nodirect option. This option prohibits the direct binding to all interfaces offered by the object.

Non-direct labelling prevents any symbol reference from directly binding to an implementation. The symbol search to satisfy the reference uses the default symbol search model. Non-direct labelling
has been employed to build the various malloc() family implementations that are provided with the Solaris OS.

Note –

The NODIRECTmapfile keyword can be combined with the command line options -B direct or -z direct. Symbols that
are not explicitly defined NODIRECT follow the command line directive. Similarly, the DIRECTmapfile keyword can be combined with the
command line option -B nodirect. Symbols that are not explicitly defined DIRECT follow the command line directive.

When Relocations Are Performed

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

A lazy reference

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 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'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 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.

Note –

The preceding examples of immediate references and lazy references are typical. However, 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. These relocations are common for position-independent code.

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.

Relocation Errors

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.

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. 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 executable or shared 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.

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.

Loading Additional Objects

The runtime linker provides an additional level of flexibility by enabling you to introduce new objects during process initialization by using the environment variable LD_PRELOAD. This environment variable
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.

In the following example, the dynamic executable prog is loaded, followed by the shared object newstuff.so.1. The dependencies defined within prog are then loaded.

$ LD_PRELOAD=./newstuff.so.1 prog

The order in which these objects are processed can be displayed using ldd(1).

In the following 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).

These mechanisms of inserting an object after a dynamic executable provide for interposition. 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 original 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 interposition of the new symbol on the original symbol enables the new function to carry out
additional processing. The new function can also call through to the original function. This mechanism typically obtains the original symbol's address using dlsym(3C) with the special handle RTLD_NEXT.

Lazy Loading of Dynamic Dependencies

When a dynamic object is loaded into memory, the object is examined for any additional dependencies. By default, any dependencies that exist are immediately loaded. This cycle continues until
the full dependency tree is exhausted. Finally, all inter-object data references that are specified by relocations, are resolved. 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 the lazy binding of a function call, the loading
of a dependency is delayed until the function is first referenced. As a result, 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-editors reference to a debugging library, liblddbg. As debugging is only called upon infrequently, loading this library every time that
the link-editor is invoked is unnecessary and expensive. By indicating that this library can be lazily loaded, the expense of processing the library is 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 model is ideal
if the number of dlsym() references is small. This model also works well if 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 that follows 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. As libelf.so.1 has no preceding LAZY flag, this library is loaded at the initial startup of the program.

Note –

libc.so.1 has special system requirements, that require the file not be lazy loaded. If -z lazyload is in effect when libc.so.1 is
processed, the flag is effectively ignored.

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 libX.so as a dependency,
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 this dependency available. But, if libA.so declares libX.so to be lazy loaded, it is possible that libX.so might
not be loaded when libB.so makes reference to this dependency. A similar failure can occur if libB.so declares libX.so as a dependency
but fails to provide a runpath necessary to locate the dependency.

Regardless of lazy loading, dynamic objects should declare all their dependencies and how to locate the dependencies. 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.

This code can be simplified if the object that supplies the required interfaces satisfies the following conditions.

The object can be established as a dependency at link-edit time.

The object is always available.

By exploiting lazy loading, the same deferred loading of libbar.so.1 can be achieved. In this case, the reference to the function bar1() results in
lazy loading the associated dependency. In addition, the use of standard function calls provides for compiler, or lint(1) validation.

However, this model fails if the
object that provides the required interfaces is not always available. In this case, the ability to test for the existence of the dependency, without having to know the dependencies name, is desirable.
A means of testing for the availability of a dependency that satisfies a function reference is required.

dlsym(3C) with the RTLD_PROBE handle
can be used to verify the existence, and loading of a dependency. For example, a reference to bar1() can verify that the lazy dependency that was established at link-edit time
is available. This test can be used to control the reference to functions provided by the dependency in the same manner as dlopen(3C) had been used.

This technique provides for safe deferred loading of recorded dependencies, together with standard function call use.

Note –

The special handle RTLD_DEFAULT provides a mechanism that is similar to using RTLD_PROBE. However, the use of RTLD_DEFAULT can
result in pending lazy loaded objects being processed in an attempt to locate a symbol that does not exist. This loading compensates for objects that have not fully defined their dependencies. However,
this compensation can undermine the advantages of a lazy loading.

The use of the -z defs option to build any objects that employ lazy loading, is recommended.

Initialization and Termination Routines

Dynamic objects can supply code that provides for runtime initialization and termination processing. The initialization code of a dynamic object is executed once each time the dynamic object
is loaded in a process. The termination code of a dynamic object is executed once each time the dynamic object is unloaded from a process or at process termination.

Before transferring control to an application, the runtime linker processes
any initialization sections found in the application and any loaded dependencies. If new dynamic objects are loaded during process execution, their initialization sections are processed as part
of loading the object. The initialization sections .preinitarray, .initarray, 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 .preinitarray and .initarray 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 .initarray sections, the .init section is processed before the functions defined by the .initarray section for that object.

A dynamic executable can provide pre-initialization functions in a .preinitarray 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 .init section within the dynamic executable is called from the application 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 .finiarray and .fini are created by the link-editor when a dynamic
object is built.

Any termination sections are passed to atexit(3C). These termination routines are called when the
process calls exit(2). Termination sections are also called
when objects are removed from the running process with dlclose(3C).

The runtime linker executes functions whose addresses are contained in the .finiarray 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 .finiarray sections,
the functions defined by the .finiarray section are processed before the .fini section for that object.

Note –

Any .fini section within the dynamic executable is called from the application 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.

Initialization and Termination Order

To determine the order of executing initialization and termination code within a process at runtime is a complex procedure that involves dependency analysis. This procedure has evolved substantially
from the original inception of initialization and termination sections. This procedure attempts to fulfill the expectations of modern languages and current programming techniques. However, scenarios
can exist, where user expectations are hard to meet. Flexible, predictable runtime behavior can be achieved by understanding these scenarios together with limiting the content of initialization
code and termination code.

The goal of an initialization section is to execute a small piece of code before any other code within the same object is referenced. The goal of a termination section is to execute a small
piece of code after an object has finished executing. Self contained initialization sections and termination sections can easily satisfy these requirements.

However, initialization sections are typically more complex and make reference to external interfaces that are provided by other objects. Therefore, a dependency is established where the initialization
section of one object must be executed before references are made from other objects. Applications can establish an extensive dependency hierarchy. In addition, dependencies can creating cycles
within their hierarchies. The situation can be further complicated by initialization sections that load additional objects, or change the relocation mode of objects that are already loaded. These
issues have resulted in various sorting and execution techniques that attempt to satisfy the original goal of these sections.

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.

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.

Caution –

Prior
to the Solaris 8 10/00 release, the environment variable LD_BREADTH could be set to a non-null value. This setting forced the runtime linker to execute initialization
and termination sections in pre-Solaris 2.6 release 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 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 sections are called in the topological order of the dependencies. The termination
sections of any cyclic dependencies are executed in their load order.

A static analysis of the initialization order of an object's dependencies
can be obtained by using ldd(1) with the -i option.
For example, the following dynamic executable and its dependencies exhibit a cyclic dependency.

The previous analysis resulted solely from the topological sorting of the explicit dependency relationships. However, objects are frequently created that do not define their required dependencies.
For this reason, symbol bindings are also incorporated as part of dependency analysis. The incorporation of symbol bindings with explicit dependencies can help produce a more accurate dependency
relationship. A more accurate static analysis of initialization order can be obtained by using ldd(1) with the -i and -d options.

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 might still be pending. These bindings can extend the dependency relationships so far established. A static analysis of the initialization order
that incorporates all symbol binding can be obtained by using ldd(1) with
the -i and -r options.

In practice, most applications use lazy binding. Therefore, the dependency analysis achieved before computing the initialization order follows the static analysis using ldd-id. However, because this dependency analysis can be incomplete, and because cyclic dependencies can exist, the runtime linker provides for dynamic initialization.

Dynamic initialization attempts to execute the initialization section of an object before any functions in the same object are called. During lazy symbol binding, the runtime linker determines
whether the initialization section of the object being bound to has been called. If not, the runtime linker executes the initialization section 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 init. See Debugging Library. Extensive runtime initialization information and termination information can be captured by adding the debugging token detail.
This information includes dependency listings, topological processing, and the identification of cyclic dependencies.

Dynamic initialization is only available when processing lazy references. This dynamic
initialization is circumvented by the following.

The initialization techniques that have been described so far might still be insufficient to cope with some dynamic activities. Initialization sections can load additional objects, either
explicitly using dlopen(3C), or implicitly through
lazy loading and the use of filters. Initialization sections can also promote the relocations of existing objects. Objects that have been loaded to employ lazy binding have these bindings resolved
if the same object is referenced using dlopen(3C) with
the mode RTLD_NOW. This relocation promotion effectively suppresses the dynamic initialization capability that is available when resolving a function call dynamically.

Whenever new objects are loaded, or existing objects have their relocations promoted, a topological sort of these objects is initiated. Effectively, the original initialization execution is
suspended while the new initialization requirements are established and the associated initialization sections executed. This model attempts to insure that the newly referenced objects are suitably
initialized for the original initialization section to use. However, this parallization can be the cause of unwanted recursion.

While processing objects that employ lazy binding, the runtime linker can detect certain levels of recursion. This recursion can be displayed by setting LD_DEBUG=init. For example,
the execution of the initialization section of foo.so.1 might result in calling another object. If this object then references an interface in foo.so.1 then
a cycle is created. The runtime linker can detect this recursion as part of binding the lazy function reference to foo.so.1.

Recursion that occurs through references that have already been relocated can not be detected by the runtime linker.

Recursion can be expensive and problematic. Reduce the number of external references and dynamic loading activities that can be triggered by an initialization section so as to eliminate recursion.

Initialization processing is repeated for any objects that are added to the running process with dlopen(3C). Termination processing is also carried out for any objects that are unloaded from the process as a result of a call to dlclose(3C).

The preceding sections describe the various techniques that are 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 make initialization processing
and termination processing that is predictable, while 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. 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.

Security

Secure processes have some restrictions applied to
the evaluation of their dependencies and runpaths to prevent malicious dependency substitution or symbol interposition.

The runtime linker categorizes a process as secure if the issetugid(2) system call returns true for the process.

For 32–bit objects, the default trusted directories that are known to the runtime linker are /lib/secure and /usr/lib/secure. For 64–bit objects, the default trusted directories that are known to the runtime linker are /lib/secure/64 and /usr/lib/secure/64. The utility crle(1) can
be used to specify additional trusted directories that are applicable for secure applications. Administrators who use this technique should ensure that the target directories are suitably protected
from malicious intrusion.

If an LD_LIBRARY_PATH family environment variable is in effect for a secure process, only the trusted directories specified by this variable
are used to augment the runtime linker's search rules. See Directories Searched by the Runtime Linker.

In a secure process, any runpath specifications provided by the application
or any of its dependencies are used. However, the runpath must be a full path name, that is, the path name must start with a `/'.

In a secure process, the expansion of the $ORIGIN string is allowed only if the string expands to a trusted directory. See Security. However, should a $ORIGIN expansion match a directory that has already provided dependencies, then the directory is implicitly secure.
This directory can be used to provide additional dependencies.

In a secure process, LD_CONFIG is ignored. A secure process
uses the default configuration file, if the configuration file exists. See crle(1).

In a secure
process, LD_SIGNAL is ignored.

Additional objects can be loaded
with a secure process using the LD_PRELOAD or LD_AUDIT environment variables. These objects must be specified as full path names or simple file names. Full path names
are restricted to known trusted directories. Simple file names, in which no `/' appears in the name, are located subject to the search path restrictions previously described. Simple file names resolve
only to known trusted directories.

In a secure process, any dependencies that consist of simple file names are processed using the path name restrictions previously described. Dependencies expressed as full path names or relative
path names are used as is. Therefore, the developer of a secure process should ensure that the target directory referenced as one of these dependencies is suitably protected from malicious intrusion.

When creating a secure process, do not use relative path names to express dependencies or to construct dlopen(3C) path names. This restriction applies to the application and to all dependencies.

Runtime Linking Programming Interface

Dependencies specified during the link-edit of an application are processed by the runtime linker during process initialization. In addition to this mechanism, the application can extend its
address space during its execution by binding to additional objects. The application effectively uses the same services of the runtime linker that are used to process the applications standard dependencies.

Delayed object binding has several advantages.

By processing an object when the object is required rather than during the initialization of an application, startup time can be greatly reduced. If the services provided by an object
are not needed during a particular run of the application, the object is not required. This scenario can occur for objects that provide help or debugging information.

The application can choose between several different objects, depending on the exact services required, such as for a networking protocol.

Any objects added to the process address space during execution can be freed after use.

An application can use the following typical scenario to access an additional shared object.

A shared object is located and added to the address space of a running application using dlopen(3C). Any dependencies of this shared object are located
and added at this time.

The added shared object and its dependencies are relocated. Any initialization sections within these objects are called.

The application locates symbols within the added objects using dlsym(3C). The application can then reference the data or call the functions defined by these new symbols.

After the application has finished with the objects, the address space can be freed using dlclose(3C). Any termination sections that exist within the
objects that are being freed are called at this time.

Any error conditions that occur as a result of using the runtime linker interface routines can be displayed
using dlerror(3C).

The services of the runtime linker are defined in the header file dlfcn.h and are made available
to an application by the shared object libc.so.1. In the following example, the file main.c can make reference to any of the dlopen(3C) family of routines, and the application prog can bind
to these routines at runtime.

$ cc -o prog main.c

Note –

In previous releases of the Solaris OS, the dynamic linking interfaces were made available by the shared object libdl.so.1. libdl.so.1 remains
available to support any existing dependencies. However, the dynamic linking interfaces offered by libdl.so.1 are now available from libc.so.1. Linking
with -ldl is no longer necessary.

Loading Additional Objects

Additional objects can be added to a running process's address space by using dlopen(3C). This function takes a path name and a binding mode as arguments, and returns
a handle to the application. This handle can be used to locate symbols for use by the application using dlsym(3C).

If the path name is specified as a simple file name, one with no `/' in the name, then the runtime linker uses a set of rules to generate an appropriate path name. Path
names that contain a `/' are used as provided.

To locate the shared object foo.so.1, the runtime linker uses any LD_LIBRARY_PATH definition that is present at process initialization. Next, the runtime linker uses any runpath specified during the link-edit of prog. Finally, the
runtime linker uses the default locations /lib and /usr/lib for 32–bit objects, or /lib/64 and /usr/lib/64 for
64–bit objects.

If the path name is specified as:

if ((handle = dlopen("./foo.so.1", RTLD_LAZY)) == NULL) {

then the runtime linker searches for the file only in the current working directory of the process.

If the object
being added by dlopen(3C) has dependencies on other
objects, they too are brought into the process's address space. This process continues until all the dependencies of the specified object are loaded. This dependency tree is referred to as a group.

If the object specified by dlopen(3C), or any of its dependencies, are already part of the process image, then
the objects are not processed any further. A valid handle is returned to the application. This mechanism prevents the same object from being loaded more than once, and enables an application to
obtain a handle to itself. For example, from the previous example, main.c can contain the following dlopen() call.

if ((handle = dlopen(0, RTLD_LAZY)) == NULL) {

The handle returned from this dlopen(3C) can
be used to locate symbols within the application itself, within any of the dependencies loaded as part of the process's initialization, or within any objects added to the process's address space,
using a dlopen(3C) that specified the RTLD_GLOBAL flag.

Relocation Processing

After locating and loading any objects, the runtime linker must process each object and perform any necessary relocations. Any objects that are brought into the process's address space with dlopen(3C) must also be relocated in the same manner.

For simple applications this process is straightforward. However, for users who have more complex applications with many dlopen(3C) calls involving many objects, possibly with common dependencies, this process can be quite important.

Relocations can be categorized according to when they occur. The default behavior of the runtime linker is to process all immediate
reference relocations at initialization and all lazy references during process execution, a mechanism commonly referred to as lazy binding.

This same mechanism is applied to any objects added with dlopen(3C) when the mode is defined as RTLD_LAZY.
An alternative is to require all relocations of an object to be performed immediately when the object is added. You can use a mode of RTLD_NOW, or record this requirement in the
object when it is built using the link-editor's -z now option. This relocation requirement is propagated to any dependencies of the object being opened.

Relocations can also be categorized into non-symbolic and symbolic. The remainder of this section covers issues regarding symbolic relocations, regardless of when these relocations occur,
with a focus on some of the subtleties of symbol lookup.

Symbol Lookup

If an object acquired by dlopen(3C) refers to a global symbol, the runtime linker must
locate this symbol from the pool of objects that make up the process. In the absence of direct binding, a default symbol search model is applied to objects obtained by dlopen().
However, the mode of a dlopen() together with the attributes of the objects that make up the process, provide for alternative symbol search models.

Objects that required direct binding, although maintaining all the attributes described later, search for symbols directly in the associated dependency. See Direct Bindings.

Default Symbol Lookup Model

For each object added by a basic dlopen(3C), the runtime linker first looks for the symbol in the dynamic executable. The runtime
linker then looks in each of the objects provided during the initialization of the process. If the symbol is still not found, the runtime linker continues the search. The runtime linker next looks
in the object acquired through the dlopen(3C) and in
any of its dependencies.

The default symbol lookup model provides for transitioning 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.

In the following example, the dynamic executable prog and the shared object B.so.1 have the following dependencies.

$ ldd prog
A.so.1 => ./A.so.1
$ ldd B.so.1
C.so.1 => ./C.so.1

If prog acquires the shared object B.so.1 by dlopen(3C), then any symbol required to relocate the shared objects B.so.1 and C.so.1 will first be looked for in prog, followed by A.so.1, followed by B.so.1, and finally in C.so.1. In this simple case, think of the shared objects acquired
through the dlopen(3C) as if they had been added to
the end of the original link-edit of the application. For example, the objects referenced in the previous listing can be expressed diagrammatically as shown in the following figure.

Figure 3–1 A Single dlopen() Request

Any symbol lookup required by the objects acquired from the dlopen(3C), that is shown as shaded blocks, proceeds from the dynamic executable prog through to the final shared object C.so.1.

This symbol lookup is established by the attributes assigned to the objects as they were loaded. Recall that the dynamic executable and all the dependencies loaded with the executable are
assigned global symbol visibility, and that the new objects are assigned world symbol search scope. Therefore, the new objects are able to look for symbols in the original objects. The new objects
also form a unique group in which each object has local symbol visibility. Therefore, each object within the group can look for symbols within the other group members.

These new objects do not affect the normal symbol lookup required by either the application or the applications initial dependencies. For example, if A.so.1 requires a
function relocation after the previous dlopen(3C) has
occurred, the runtime linker's normal search for the relocation symbol is to look in prog and then A.so.1. The runtime linker does not follow through and
look in B.so.1 or C.so.1.

This symbol lookup is again a result of the attributes assigned to the objects as they were loaded. The world symbol search scope is assigned to the dynamic executable and all the dependencies
loaded with it. This scope does not allow them to look for symbols in the new objects that only offer local symbol visibility.

These symbol search and symbol visibility attributes maintain associations between objects. These associations are based on their introduction into the process address space, and on any dependency
relationship between the objects. Assigning the objects associated with a given dlopen(3C) to a unique group ensures that only objects associated with the same dlopen(3C) are allowed to look up symbols within themselves and their related dependencies.

This concept of defining associations between objects becomes more clear in applications that carry out more than one dlopen(3C). For example, suppose the shared object D.so.1 has the following dependency.

$ ldd D.so.1
E.so.1 => ./E.so.1

and the prog application used dlopen(3C) to load this shared object in addition to the shared object B.so.1. The following figure illustrates the symbol lookup releationship between the objects.

Figure 3–2 Multiple dlopen() Requests

Suppose that both B.so.1 and D.so.1 contain a definition for the symbol foo, and both C.so.1 and E.so.1 contain a relocation that requires this symbol. Because of the association of objects to a unique group, C.so.1 is bound to the definition in B.so.1,
and E.so.1 is bound to the definition in D.so.1. This mechanism is intended to provide the most intuitive binding of objects that are obtained from multiple
calls to dlopen(3C).

When objects are used in the scenarios that have so far been described, the
order in which each dlopen(3C) occurs has no effect
on the resulting symbol binding. However, when objects have common dependencies, the resultant bindings can be affected by the order in which the dlopen(3C) calls are made.

In the following example, the shared objects O.so.1 and P.so.1 have the same common dependency.

$ ldd O.so.1
Z.so.1 => ./Z.so.1
$ ldd P.so.1
Z.so.1 => ./Z.so.1

In this example, the prog application will dlopen(3C) each of these shared objects. Because the shared object Z.so.1 is a common dependency of both O.so.1 and P.so.1, Z.so.1 is assigned to both of the groups that are associated with the two dlopen(3C) calls. This relationship is shown in the following figure.

Figure 3–3 Multiple dlopen() Requests With A Common Dependency

Z.so.1 is available for both O.so.1 and P.so.1 to look up symbols. More importantly, as far as dlopen(3C) ordering is concerned, Z.so.1 is also be able to look
up symbols in both O.so.1 and P.so.1.

Therefore, if both O.so.1 and P.so.1 contain a definition for the symbol foo, which is required for a Z.so.1 relocation,
the actual binding that occurs is unpredictable because it is affected by the order of the dlopen(3C) calls. If the functionality of symbol foo differs between the two shared objects in which it is defined, the overall outcome of
executing code within Z.so.1 might vary depending on the application's dlopen(3C) ordering.

Defining a Global Object

The default assignment of local symbol visibility to the objects obtained by a dlopen(3C) can be promoted to global by augmenting the mode argument with the RTLD_GLOBAL flag. Under this mode, any objects obtained through
a dlopen(3C) can be used by any other objects with
world symbol search scope to locate symbols.

In addition, any object obtained by dlopen(3C) with the RTLD_GLOBAL flag is available for symbol lookup using dlopen() with a path name whose value is 0.

Note –

If a member of a group has local symbol visibility, and is referenced by another group requiring global symbol visibility, the object's visibility becomes a concatenation of both local
and global. This promotion of attributes remains even if the global group reference is later removed.

Isolating a Group

The default assignment of world symbol search scope to the objects obtained by a dlopen(3C) can be reduced to group by augmenting the mode argument with the RTLD_GROUP flag. Under this mode, any objects obtained through
a dlopen(3C) will only be allowed to look for symbols
within their own group.

Using the link-editor's -B group option, you can assign the group
symbol search scope to objects when they are built.

Note –

If a member of a group, has group search capability, and is referenced by another group requiring world search capability, the object's search capability becomes a concatenation of both
group and world. This promotion of attributes remains even if the world group reference is later removed.

Object Hierarchies

If an initial object is obtained from a dlopen(3C),
and uses dlopen() to open a secondary object, both objects are assigned to a unique group. This situation can prevent either object from locating symbols from the other.

In some implementations the initial object has to export symbols for the relocation of the secondary object. This requirement can be satisfied by one of two mechanisms.

Making the initial object an explicit dependency of the second object.

If the initial object is an explicit dependency of the secondary object, the initial object is assigned to the secondary objects' group. The initial object is therefore able to provide symbols
for the secondary objects' relocation.

If many objects can use dlopen(3C) to open
the secondary object, and each of these initial objects must export the same symbols to satisfy the secondary objects' relocation, then the secondary object cannot be assigned an explicit dependency.
In this case, the dlopen(3C) mode of the secondary
object can be augmented with the RTLD_PARENT flag. This flag causes the propagation of the secondary objects' group to the initial object in the same manner as an explicit dependency
would do.

There is one small difference between these two techniques. If you specify an explicit dependency, the dependency itself becomes part of the secondary objects' dlopen(3C) dependency tree, and thus becomes available for
symbol lookup with dlsym(3C). If you obtain the secondary
object with RTLD_PARENT, the initial object does not become available for symbol lookup with dlsym(3C).

When a secondary object is obtained by dlopen(3C) from
an initial object with global symbol visibility, the RTLD_PARENT mode is both redundant and harmless. This case commonly occurs when dlopen(3C) is called from an application or from one of the dependencies of the application.

Obtaining New Symbols

A process can obtain the address of a specific symbol using dlsym(3C). This function takes a handle and a symbol name, and returns the address
of the symbol to the caller. The handle directs the search for the symbol in the following manner.

A handle can be returned from a dlopen(3C) of a named object. The handle enables symbols to be obtained from the named object and the objects that define its dependency tree. A handle returned using the mode RTLD_FIRST, enables symbols to be obtained only from the named object.

A handle can be returned from a dlopen(3C) of a path name whose value is 0. The handle enables symbols to be obtained from the initiating object of the associated link-map and the objects that define
its dependency tree. Typically, the initiating object is the dynamic executable. This handle also enables symbols to be obtained from any object obtained by a dlopen(3C) with the RTLD_GLOBAL mode, on the associated link-map.
A handle returned using the mode RTLD_FIRST, enables symbols to be obtained only from the initiating object of the associated link-map.

The special handle RTLD_DEFAULT,
and RTLD_PROBE enable symbols to be obtained from the initiating object of the associated link-map and objects that define its dependency tree. This handle also enables symbols
to be obtained from any object obtained by a dlopen(3C) that
belongs to the same group as the caller. Use of RTLD_DEFAULT, or RTLD_PROBE follows the same model as used to resolve a symbolic relocation from the calling
object.

The special handle RTLD_NEXT enables symbols to be obtained from the next associated object on the callers link-map list.

In the following example, which is probably the most common, an application adds additional objects to its address space. The application then uses dlsym(3C) to locate function or data symbols. The application then uses these symbols
to call upon services that are provided in these new objects. The file main.c contains the following code.

The symbols foo and bar are searched for in the file foo.so.1, followed by any dependencies that are associated with this file. The
function foo is then called with the single argument bar as part of the return() statement.

The application prog, built using the previous file main.c, contains the following dependencies.

$ ldd prog
libc.so.1 => /lib/libc.so.1

If the file name specified in the dlopen(3C) had
the value 0, the symbols foo and bar are searched for in prog, followed by /lib/libc.so.1.

The handle indicates the root at which to start a symbol search. From this root, the search mechanism follows the same model as described in Relocation Symbol Lookup.

If the required symbol cannot be located, dlsym(3C) returns a NULL value. In this
case, dlerror(3C) can be used to indicate the true
reason for the failure. In the following example, the application prog is unable to locate the symbol bar.

$ prog
dlsym: ld.so.1: main: fatal: bar: can't find symbol

Testing for Functionality

The special handles RTLD_DEFAULT, and RTLD_PROBE enable an application to test for the existence of another symbol. The symbol search follows the same
model as used to relocate the calling object. See Default Symbol Lookup Model. For example, the application prog can contain the following
code fragment.

In this case, foo is searched for in prog, followed by /lib/libc.so.1. If this code fragment was contained in the file B.so.1 from the example that is shown in Figure 3–1, then the search for foo continues into B.so.1 and
then C.so.1.

This mechanism provides a robust and flexible alternative to the use of undefined weak references, as discussed in Weak Symbols.

Using Interposition

The special handle RTLD_NEXT enables an application to locate the next symbol in a symbol scope. For example, the
application prog can contain the following code fragment.

In this case, foo is searched for in the shared objects associated with prog, which in this case is /lib/libc.so.1. If this code
fragment was contained in the file B.so.1 from the example that is shown in Figure 3–1, then foo is
searched for in C.so.1 only.

Use of RTLD_NEXT provides a means to exploit symbol interposition. For example, a function within an object can be interposed upon by a preceding object, which can then
augment the processing of the original function. For example, the following code fragment can be placed in the shared object malloc.so.1.

malloc.so.1 can be interposed before the system library /lib/libc.so.1 where malloc(3C) usually resides. Any calls to malloc() are now interposed upon before the original function
is called to complete the allocation.

Users of any interposition technique must be careful to handle any possibility of recursion. The previous example formats the diagnostic message using sprintf(3C), instead of using printf(3C) directly, to avoid any recursion caused by printf(3C) possibly using malloc(3C).

The use of RTLD_NEXT within a dynamic executable or preloaded object, provides a predictable interposition technique. Be careful when using this technique in a generic object
dependency, as the actual load order of objects is not always predictable.

Debugging Aids

A debugging library and a debugging mdb(1) module
are provided with the Solaris OS link editors. The debugging library enables you to trace the runtime linking process in more detail. The mdb(1) module enables interactive process debugging.

Debugging Library

The debugging library helps you to understand and debug the execution of applications and their dependencies. The type of information that is displayed by using this library is expected to
remain constant. However, the exact format of the information might change slightly from release to release.

Some of the debugging output might be unfamiliar to users who do not have an intimate knowledge of the runtime linker. However, many aspects might be of general interest to you.

Debugging is enabled
by using the environment variable LD_DEBUG. All debugging output is prefixed with the process identifier and by default is directed to the standard error. This environment variable
must be augmented with one or more tokens to indicate the type of debugging that is required.

The tokens that are available with LD_DEBUG can be displayed by using LD_DEBUG=help. Any dynamic executable can be used to solicit this information, as the process
terminates following the display of the information.

The
environment variable LD_DEBUG_OUTPUT can be used to specify an output file for use instead of the standard error. The process identifier is added as a suffix to the output file.

The debugging of secure applications is not allowed.

One of the most useful debugging options is to display the symbol bindings that occur at runtime. The following example uses a very trivial dynamic executable that has a dependency on two
local shared objects.

The symbol bar, which is required by an immediate relocation, is bound before the application gains control.
Whereas the symbol foo, which is required by a lazy relocation, is bound after the application gains control on the first call to the function. This relocation
demonstrates the default mode of lazy binding. If the environment variable LD_BIND_NOW is set, all symbol bindings occur before the application gains control.

By setting LD_DEBUG=bindings,detail, additional information regarding the real and relative addresses of the actual binding locations is provided.

You can use LD_DEBUG to display the various search paths used. For example, the search path mechanism used to locate any dependencies can be displayed by setting LD_DEBUG=libs.

The runpath recorded in the application prog affects the search for the two dependencies foo.so.1 and bar.so.1.

In a similar manner, the search paths of each symbol lookup can be displayed by setting LD_DEBUG=symbols. A combination of symbols and bindings produces
a complete picture of the symbol relocation process.

In the previous example, the symbol bar is not searched for in the application prog. This omission of a data reference lookup is due to an optimization
used when processing copy relocations. See Copy Relocations for more details of this relocation type.

Debugger Module

The debugger module provides a set of dcmds and walkers that can be loaded under mdb(1). This module can be used to inspect various internal data structures of the runtime linker. Much of the debugging
information requires familiarity with the internals of the runtime linker. These internals can change from release to release. However, some elements of these data structures reveal the basic components
of a dynamically linked process and can aid general debugging.

The following examples show some simple scenarios of using mdb(1) with the debugger module.

mdb(1) is also very useful for setting deferred break
points. In this example, a break point on the function foo() might be useful. However, until the dlopen(3C) of foo.so.1 occurs, this symbol isn't known to the debugger. A deferred break point instructs the debugger to
set a real breakpoint when the dynamic object is loaded.

The dependencies of a handle are a list of link-maps that represent the objects of the handle that can satisfy a dlsym(3C) request. In this case, the dependencies are foo.so.1 and bar.so.1.

Note –

The previous examples provide a basic guide to the debugger module capabilities, but the exact commands, usage, and output can change from release to release. Refer to the usage and
help information from mdb(1) for the exact capabilities that are available on your system.