The dynamic linker/loader (commonly referred to as the loader) is responsible for creating a dynamic executable's process image and placing it into system memory so that it can execute. The loader's functions include finding and mapping shared libraries, completing symbol resolution, and finalizing program addresses.
To accomplish these functions, the loader requires information on external symbols and shared libraries. The linker prepares this dynamic loading information for shared objects only. The dynamic loader then uses this information to create and map the process image. The dynamic information consists of the sections highlighted in Figure 14-1.
Figure 14-1: Dynamic Object File Sections
These sections are mapped with the text segment, except for the
.got
section, which contains the GOT (Global Offset Table).
The
GOT is part of the data segment because it must be written into when addresses
are updated.
The function of each dynamic section can be summarized as follows:
The
.dynamic
section serves as a header
for the dynamic information.
The
.dynsym
section contains the dynamic
symbol table.
The
.dynstr
section contains the names
of dynamic symbols and shared library dependencies.
The
.hash
section holds a hash table to
provide quick access into the dynamic symbol table.
The
.msym
table contains supplemental symbolic
information, including pre-computed hash values and dynamic relocation indices.
The
.liblist
section stores dependency
information.
The
.conflict
section contains a list of
multiply-defined symbol names that must be resolved at load time.
The
.rel.dyn
section contains dynamic relocation
entries.
The
.got
section contains one or more tables
of 64-bit run-time addresses.
This chapter covers the dynamic sections and related topics.
The actions
of the system dynamic loader are explained in detail.
Related material is
available in the
Programmer's Guide
and
loader(5)14.1 New or Changed Dynamic Loading Information Features
Tru64 UNIX V5.1B supports a new dynamic flag to mark shared libraries
that cannot be dlopened.
See
RHF_NO_DLOPEN
in
Table 14-2
for details.
Tru64 UNIX V5.0 supports depth-first symbol resolution order for individual
shared objects.
See
DT_SYMBOLIC
in
Section 14.2.1
for details.
14.2 Structures, Fields, and Values for Dynamic Loading Information
All structures and macros are declared in the header file
coff_dyn.h
unless otherwise indicated.
14.2.1 Dynamic Header Entry
typedef struct {
coff_int d_tag;
coff_uint reserved;
union {
coff_uint d_val;
coff_addr d_ptr;
} d_un;
} Coff_Dyn;
SIZE - 16 bytes, ALIGNMENT - 8 bytes
Dynamic Header Entry Fields
d_tagIndicates how
the
d_un
field is to be interpreted.
reservedMust be zero.
d_valRepresents integer values.
d_ptrRepresents virtual addresses. Virtual addresses stored in this field may not match the memory virtual addresses during execution. The dynamic loader computes actual addresses based on the virtual address from the file and the memory base address. Object files do not contain relocation entries to correct addresses in the dynamic section.
The
d_tag
requirements for dynamic executable
files and shared library files are summarized in
Table 14-1.
"mandatory" indicates that the dynamic linking array must contain an entry
of that type; "optional" indicates that an entry for the tag may exist but
is not required.
Table 14-1: Dynamic Array Tags (
d_tag)
| Name | Value | d_un |
Executable | Shared Library |
DT_NULL |
0 | ignored | mandatory | mandatory |
DT_NEEDED |
1 | d_val | optional | optional |
DT_PLTGOT |
3 | d_ptr | optional | optional |
DT_HASH |
4 | d_ptr | mandatory | mandatory |
DT_STRTAB |
5 | d_ptr | mandatory | mandatory |
DT_SYMTAB |
6 | d_ptr | mandatory | mandatory |
DT_STRSZ |
10 | d_val | optional | optional |
DT_SYMENT |
11 | d_val | optional | optional |
DT_INIT |
12 | d_ptr | optional | optional |
DT_FINI |
13 | d_ptr | optional | optional |
DT_SONAME |
14 | d_val | ignored | mandatory |
DT_RPATH |
15 | d_val | optional | ignored |
DT_SYMBOLIC |
16 | ignored | optional | optional |
DT_REL |
17 | d_ptr | mandatory | mandatory |
DT_RELSZ |
18 | d_val | mandatory | mandatory |
DT_RELENT |
19 | d_val | optional | optional |
DT_RLD_VERSION |
0x70000001 | d_val | mandatory | mandatory |
DT_TIME_STAMP |
0x70000002 | d_val | optional | optional |
DT_ICHECKSUM |
0x70000003 | d_val | optional | optional |
DT_IVERSION |
0x70000004 | d_val | optional | optional |
DT_FLAGS |
0x70000005 | d_val | optional | optional |
DT_BASE_ADDRESS |
0x70000006 | d_ptr | optional | optional |
DT_MSYM |
0x70000007 | d_ptr | optional | optional |
DT_CONFLICT |
0x70000008 | d_ptr | optional | optional |
DT_LIBLIST |
0x70000009 | d_ptr | optional | optional |
DT_LOCAL_GOTNO |
0x7000000A | d_val | mandatory | mandatory |
DT_CONFLICTNO |
0x7000000B | d_val | optional | optional |
DT_LIBLISTNO |
0x70000010 | d_val | optional | optional |
DT_SYMTABNO |
0x70000011 | d_val | mandatory | mandatory |
DT_UNREFEXTNO |
0x70000012 | d_val | optional | optional |
DT_GOTSYM |
0x70000013 | d_val | mandatory | mandatory |
DT_HIPAGENO |
0x70000014 | d_val | optional | optional |
DT_SO_SUFFIX |
0x70000017 | d_val | optional | optional |
The uses of the various dynamic array tags are as follows:
DT_NULLMarks the end of the array.
DT_NEEDEDContains the string table offset of a null-terminated string that is
the name of a needed library.
The offset is an index into the table indicated
in the
DT_STRTAB
entry.
The dynamic
array can contain multiple entries of this type.
The order of these entries
is significant.
DT_HASHContains the quickstart address of the symbol hash table.
DT_STRTABContains the quickstart address of the string table.
DT_SYMTABContains the
quickstart address of the symbol table with
Coff_Sym
entries.
DT_STRSZContains the size of the string table (in bytes).
DT_SYMENTContains the size of a symbol table entry (in bytes).
DT_INITContains the quickstart address of the initialization function.
DT_FINIContains the quickstart address of the termination function.
DT_SONAMEContains the string table offset
of a null-terminated string that gives the name of the shared library file.
The offset is an index into the table indicated in the
DT_STRTAB
entry.
DT_RPATHContains the string table offset
of a null-terminated library search path string.
The offset is an index into
the table indicated in the
DT_STRTAB
entry.
DT_SYMBOLICThe presence of this entry indicates that symbol references should be resolved using a depth-ring search of the shared object's dependencies. See Section 14.3.4.3 for a details on shared object search order.
This dynamic entry is for information only.
The search order is controlled
by the
DT_FLAGS
setting that includes
the
RHF_RING_SEARCH
and
RHF_DEPTH_FIRST
flags when
DT_SYMBOLIC
is added to the dynamic section.
Version Note
DT_SYMBOLICis supported in Tru64 UNIX V5.0 and greater.
DT_RELContains the address
of the dynamic relocation table.
If this entry is present, the dynamic structure
must contain the
DT_RELSZ
entry.
DT_RELSZContains the size (in bytes) of the dynamic relocation table pointed
to by the
DT_REL
entry.
DT_RELENTContains the size (in bytes) of a
DT_REL
entry.
DT_RLD_VERSIONContains the version number of the run-time linker interface. The version is:
1 for executable objects that have a single GOT
2 for executable objects that have multiple GOTs
3 only for objects built on Tru64 UNIX V2.x
DT_TIME_STAMPContains a 32-bit time stamp.
DT_ICHECKSUMContains a checksum value computed from the names and other attributes of all symbols exported by the library.
DT_IVERSIONContains the string table offset of a series of colon-separated versions. An index value of zero means no version string was specified.
DT_FLAGSContains a set of 1-bit flags. See Table 14-2 for a list of supported flag values.
DT_BASE_ADDRESSContains the quickstart base address of the object.
DT_CONFLICTContains the
quickstart address of the
.conflict
section.
DT_LIBLISTContains the
quickstart address of the
.liblist
section.
DT_LOCAL_GOTNOContains the number of local GOT entries. The dynamic array contains one of these entries for each GOT.
DT_CONFLICTNOContains the number of entries in the
.conflict
section.
DT_LIBLISTNOContains the number of entries in the
.liblist
section.
DT_SYMTABNOIndicates
the number of entries in the
.dynsym
section.
DT_UNREFEXTNOHolds the index to the first dynamic symbol table entry that is an external symbol not referenced within the object.
DT_GOTSYMHolds the index to the first dynamic symbol table entry that corresponds to an entry in the global offset table. The dynamic array contains one of these entries for each GOT.
DT_HIPAGENONot used by the default system loader. If present, must contain the value 0.
DT_SO_SUFFIXContains a shared library suffix that the loader appends to library names when searching for dependencies. This tag is used, for example, with Atom tools. Instrumented applications may be dependent on instrumented shared libraries identified by a tool-specific suffix.
All other tag values are reserved.
Entries can appear in any order,
except for the
DT_NULL
entry at
the end of the array and the relative order of the
DT_NEEDED
entries.
Table 14-2:
DT_FLAGS Flags
| Flag | Value | Meaning |
RHF_QUICKSTART |
0x00000001 | Object may be quickstarted by loader |
RHF_NOTPOT |
0x00000002 | Hash size not a power of two |
RHF_NO_LIBRARY_REPLACEMENT |
0x00000004 | Use default system libraries only |
RHF_NO_MOVE |
0x00000008 | Do not relocate |
RHF_NO_DLOPEN |
0x02000000 | (V5.1B - )Identifies objects that cannot be dynamically loaded |
RHF_TLS |
0x04000000 | Identifies objects that use TLS |
RHF_BIND_NOW |
0x08000000 | Identifies objects that must be loaded with immediate binding |
RHF_RING_SEARCH |
0x10000000 | Symbol resolution same as
DT_SYMBOLIC.
This flag is only meaningful when combined with
RHF_DEPTH_FIRST |
RHF_DEPTH_FIRST |
0x20000000 | Depth-first symbol resolution |
RHF_USE_31BIT_ADDRESSES |
0x40000000 | TASO (Truncated Address Support Option) objects |
typedef struct {
coff_uint st_name;
coff_uint reserved;
coff_addr st_value;
coff_uint st_size;
coff_ubyte st_info;
coff_ubyte st_other;
coff_ushort st_shndx;
} Coff_Sym;
SIZE - 24 bytes, ALIGNMENT - 8 bytes
See
Section 14.3.3
for related information.
Dynamic Symbol Entry Fields
st_nameContains the offset of the symbol's name in the dynamic string section.
reservedMust be zero.
st_valueContains the quickstart address if the symbol is defined within the object. Contains 0 for undefined external symbols, the alignment value for commons, or any arbitrary value for absolute symbols.
For undefined external conflict symbols (see Section 14.3.6.2) this field will contain the quickstart address of the symbol in the first shared library in which the linker found a definition of the symbol.
st_sizeIdentifies the size of symbols
with common storage allocation; otherwise, contains the value zero.
For
STB_DUPLICATE
symbols (see
Table 14-4).
The size field holds the index of the primary symbol.
st_infoIdentifies
the symbol's binding and type.
The macros
COFF_ST_BIND
and
COFF_ST_TYPE
are
used to access the individual values.
See
Table 14-3
and
Table 14-4
for the possible values.
st_otherCurrently has a value of zero and no defined meaning.
st_shndxIdentifies the symbol's dynamic storage class. See Table 14-5 for the possible values.
Table 14-3: Dynamic Symbol Type (
st_info) Constants
| Name | Value | Description |
STT_NOTYPE |
0 | Indicates that the symbol has no type or its type is unknown. |
STT_OBJECT |
1 | Indicates that the symbol is a data object. |
STT_FUNC |
2 | Indicates that the symbol is a function. |
STT_SECTION |
3 | Indicates that the symbol is associated with a program section. |
STT_FILE |
4 | Indicates that the symbol is the name of a source file. |
Table 14-4: Dynamic Symbol Binding (
st_info) Constants
| Name | Value | Description |
STB_LOCAL |
0 | Indicates that the symbol is local to the object (or designated as hidden). |
STB_GLOBAL |
1 | Indicates that the symbol is visible to other objects. |
STB_WEAK |
2 | Indicates that the symbol is a weak global symbol. |
STB_DUPLICATE |
13 | Indicates the symbol is a duplicate. (Used for objects that have multiple GOTs.) |
Table 14-5: Dynamic Section Index (
st_shndx) Constants
| Name | Value | Description |
SHN_UNDEF |
0x0000 |
Indicates that the symbol is undefined. |
SHN_ACOMMON |
0xff00 |
Indicates that the symbol has common storage (allocated). |
SHN_TEXT |
0xff01 |
Indicates that the symbol is in a text segment. |
SHN_DATA |
0xff02 |
Indicates that the symbol is in a data segment. |
SHN_ABS |
0xfff1 |
Indicates that the symbol has an absolute value. |
SHN_COMMON |
0xfff2 |
Indicates that the symbol has common storage (unallocated). |
14.2.3 Dynamic Relocation Entry
typedef struct {
coff_addr r_offset;
coff_uint r_info;
coff_uint reserved;
} Coff_Rel;
SIZE - 16 bytes, ALIGNMENT - 8 bytes
See
Section 14.3.5
for related information.
Dynamic Relocation Entry Fields
r_offsetIndicates the quickstart address within the object that contains the value requiring relocation.
r_infoIndicates the
relocation type and the index of the dynamic symbol that is referenced.
The
macros
COFF_R_SYM
and
COFF_R_TYPE
access the individual attributes.
The relocation
type must be
R_REFQUAD,
R_REFLONG, or
R_NULL.
reservedMust be zero.
typedef struct {
coff_uint ms_hash_value;
coff_uint ms_info;
} Coff_Msym;
SIZE - 8 bytes, ALIGNMENT - 4 bytes
See
Section 14.3.3.4
for related information.
Msym Table Entry Fields
ms_hash_valueContains the hash value computed from the name of the corresponding dynamic symbol.
ms_infoContains both the dynamic relocation
index and the symbol flags field.
The macros
COFF_MS_REL_INDEX
and
COFF_MS_FLAGS
are
used to access the individual values.
The dynamic relocation index identifies
the first entry in the
.rel.dyn
section that references
the dynamic symbol corresponding to this msym entry.
If the index is 0, no
dynamic relocations are associated with the symbol.
The symbol flags field
is reserved for future use and should be zero.
typedef struct {
coff_uint l_name;
coff_uint l_time_stamp;
coff_uint l_checksum;
coff_uint l_version;
coff_uint l_flags;
} Coff_Lib;
SIZE - 20 bytes, ALIGNMENT - 4 bytes
See
Section 14.3.2
for related information.
Library List Entry Fields
l_nameRecords the name of a shared library dependency. The value is a string table index. This name can be a full pathname, relative pathname, or file name.
l_time_stampRecords the time stamp of a shared library
dependency.
The value can be combined with the
l_checksum
value and the
l_version
string to form a unique
identifier for this shared library file.
l_checksumRecords the checksum of a shared library dependency.
l_versionRecords the interface version of a shared library dependency. The value is a string table index.
l_flagsSpecifies a
set of 1-bit flags.
The
l_flags
field can have
one or more of the flags described in
Table 14-6.
Table 14-6: Library List Flags
| Name | Value | Description |
LL_EXACT_MATCH |
0x01 |
Requires that the run-time dynamic shared library file match exactly the shared library file used at static link time. |
LL_IGNORE_INT_VER |
0x02 |
Ignores any version incompatibility between the dynamic shared library file and the shared library file used at link time. |
LL_USE_SO_SUFFIX |
0x04 |
Marks shared library dependencies that should be loaded
with a suffix appended to the name.
The
DT_SO_SUFFIX
entry in the
.dynamic
section records the
name of this suffix.
This is used by object instrumentation tools to distinguish
instrumented shared libraries. |
LL_NO_LOAD |
0x08 |
Marks entries for shared libraries that are not loaded
as direct dependencies of an object.
Object instrumentation tools may use
LL_NO_LOAD
entries to set the
LL_USE_SO_SUFFIX
for dynamically loaded shared libraries or for
indirect shared library dependencies. |
If neither
LL_EXACT_MATCH
nor
LL_IGNORE_INT_VER
bits are set,
the dynamic loader requires that the version of the dynamic shared library
match at least one of the colon-separated version strings indexed by the
l_version
string table index.
14.2.6 Conflict Entry
typedef struct {
coff_uint c_index;
} Coff_Conflict;
SIZE - 4 bytes, ALIGNMENT - 4 bytes
The conflict entry is an index into the dynamic symbols (.dynsym) section.
See
Section 14.3.6.2
for related information.
14.2.7 GOT Entry
typedef struct {
coff_addr g_index;
} Coff_Got;
SIZE - 8 bytes, ALIGNMENT - 8 bytes
The GOT entry is a 64-bit address.
Most GOT entries map to dynamic symbols.
See
Section 14.3.3
for details.
14.2.8 Hash Table Entry
The hash table is implemented as an array of 32-bit values. The structure is declared internal to system utilities.
See
Section 14.3.3.5
for more information.
14.2.9 Dynamic String Table
The dynamic string table consists of null-terminated character strings. The strings are of varying length and separated only by a single character. Offsets into the dynamic string table give the number of bytes from the beginning of the string space to the beginning of the name in question.
Offset 0 in the dynamic string table is reserved for the null string.
14.3 Dynamic Loading Information Usage
14.3.1 Shared Object Identification
A shared object
is either a dynamic executable or a shared library.
The file header flags
indicate whether the object is a shared object and, if so, what type of shared
object it is.
The layout of the object is also stated in the file header.
Normally shared objects use a
ZMAGIC
image layout (see
Section 2.3.2.3).
Additional information on the shared object is located in the dynamic
header (.dynamic
section).
When the dynamic loader is invoked
by the kernel's
exec()
routine, this header information
is read.
The kernel and loader take the following steps upon receiving a user command to execute a dynamic executable:
User enters command.
Shell calls
exec()
in kernel.
exec()
opens the file and reads the file
header.
If the file is a dynamic executable,
exec()
calls
/sbin/loader.
The loader then:
Reads file header and dynamic header information.
Maps the executable into memory.
Locates each shared library dependency, maps it into memory, and relocates it if necessary.
Resolves symbols for all shared objects.
Sets the heap address.
Transfers control to program entry point.
The program entry point (__start
in
crt0.o) then:
Calls special symbol
__istart
which invokes
the loader routine to run INIT routines
Calls
main()
with
__Argc,
__Argv,
__environ
and
_auxv.
14.3.2 Shared Library Dependencies
Dynamic executables usually rely on shared libraries. At load time, these shared libraries must be located, validated, and mapped with the process image.
If an executable object refers to a symbol whose definition resides in a shared library, the executable is dependent on that library. This relationship is described as a direct dependency. A shared library dependency also exists if a library is used by any previously identified dependency. This is an indirect dependency for the executable.
In the example shown in
Figure 14-2,
libA,
libB, and
libcool
are all shared library dependencies
for
a.out.
The library
libA
is a direct
dependency, and the others are indirect dependencies.
Figure 14-2: Shared Library Dependencies
Although the possibility of duplicate dependencies exists, as in the
preceding example, each library is mapped only once with the image.
The linker
also prevents recursive inclusion, which could occur in a case of cyclic dependencies.
14.3.2.1 Identification
A shared object's
dependencies are stored in its
.liblist
entries and in
DT_NEEDED
entries in the
.dynamic
section.
The linker records this information as dependencies are encountered.
The library list (.liblist
section) has name, timestamp,
checksum, and version information for every entry, along with a flags field.
Taken together, the timestamp and checksum value and the version string form
a unique identifier for a shared library.
An entry is created for each shared
library dependency.
A
DT_NEEDED
tag in the dynamic
header also indicates a shared library dependency.
The value of the entry
is the string table offset for the needed library's name.
Note that this representation
of the dependency information is redundant with that contained in the library
list.
The loader relies on the library list only.
The
DT_NEEDED
entries are maintained for historical reasons.
As an example, an object linked against
libc
has
the following dependency information:
***DYNAMIC SECTION***
LIBLISTNO: 1.
LIBLIST: 0x0000000120000690
NEEDED: libc.so
***LIBRARY LIST SECTION***
Name Time-Stamp CheckSum Flags Version
a.out:
libc.so May 19 22:18:46 1996 0xf937323b 0 osf.1
A shared library's
checksum is computed by the linker when the library is created or updated,
and the value is written into the dynamic header.
When an application is linked
against the library, the linker copies the library's current checksum into
its entry in the application's
.liblist.
The checksum computation is a summation of the names of dynamic symbols that meet the following criteria:
Defined
Not local
Not hidden
Not duplicate
Common storage class symbol names are included, along with their size. Weak symbols are included, but the calculation for weak symbols differs from that used for non-weak symbols.
For a single symbol, the checksum is computed using this algorithm :
if (SYMBOL.st_shndx == SHN_COMMON || SYMBOL.st_shndx == SHN_ACOMMON)
CHECKSUM = SYMBOL.st_size
else
CHECKSUM = 0
for (# of characters in symbol name)
CHECKSUM = (CHECKSUM << 5) + character_value
if (weak symbol)
CHECKSUM = (CHECKSUM << 5) + CHECKSUM + 1
A change in the number of weak symbols or a change in the size of a common storage class symbol is therefore reflected in the checksum. However, the checksum calculation is insensitive to symbol reordering.
The checksums for all symbols included are summed to produce the shared
object's checksum.
14.3.2.2 Searching
After loading an executable, the loader loads the executable's shared
library dependencies.
The loader searches for shared libraries that match
the names contained in the executable's
.liblist
entries.
Subject to the search guidelines described in this section, the loader will
load the first matching shared library that it finds for each dependency.
Certain directories are searched by default, in the following order:
/usr/shlib
/usr/ccs/lib
/usr/lib/cmplrs/cc
/usr/lib
/usr/local/lib
/var/shlib
The loader's search path can be altered by several methods:
-soname linker option
-rpath linker option
environment variables
The -soname option is used to set internal shared library names. The default soname is the output file name of the library when it is built. The linker uses an soname value to record shared library dependencies in the library list. Dependencies containing pathnames are located without prepending search directories to their paths. A pathname is identified by the presence of one or more slashes in the string.
The RPATH is included
in a shared object's
.dynamic
section under an entry tagged
DT_RPATH.
It is a colon-separated list of shared
library search directories.
The RPATH is set using the
-rpath
linker option.
The loader will search RPATH directories prior to searching
LD_LIBRARY_PATH
and default directories.
The environment variables that impact
the search order are
LD_LIBRARY_PATH
and
_RLD_ROOT.
LD_LIBRARY_PATH
has the same format as RPATH.
No root directories
are prepended to the
LD_LIBRARY_PATH
directories.
LD_LIBRARY_PATH
can
also be set by a program before it calls
dlopen().
The
_RLD_ROOT
environnment
variable is a colon-separated list of "root" directories that are prepended
to other search directories.
It modifies RPATH and the default search directories.
The precedence (highest to lowest) of search directories used by the loader is as follows:
soname (if it includes a path)
_RLD_ROOT
+ RPATH
LD_LIBRARY_PATH
_RLD_ROOT
+ default
search directories
When using non-system libraries, it is often necessary to specify the search path rather than relying on the defaults. Here is one example:
$ ld -shared -o my.so mylib.o -lc $ cc -o hello hello.c my.so $ hello 7526:hello: /sbin/loader: Fatal Error: cannot map my.so $ LD_LIBRARY_PATH=. $ export LD_LIBRARY_PATH $ hello Hello, World!
One of the loader's jobs is to ensure that correct shared libraries are available to the program. Shared library versioning is used to distinguish incompatible versions of shared libraries. The loader tests for matching versions when shared library dependencies are loaded. If the application is found to be incompatible with a needed shared library, the program may have to be recoded or relinked. Causes of binary incompatibility include altered global data definitions and changes to documented interfaces.
Each shared library is built with a version identifier.
This identifier
is recorded in the
.dynamic
section with the tag
DT_IVERSION.
Each entry in the dependency information
(.liblist
section) also records the version identifier
of a shared library dependency.
The
-set_version
linker option
is used to provide the version identifier.
Without this option, the linker
will build a shared library with a null version.
Version identifiers can be
any ASCII string.
Version checking can also be controlled by the user. The linker option -exact_version leads to more rigorous version testing by the loader. When this option is in effect, timestamps and checksums are checked in addition to version numbers. The linker-recorded dependency information for the timestamp and checksum must precisely match the load-time values for all shared libraries. Normally, a mismatch leads to additional symbol resolution work instead of a rejected object.
Version checking can be disabled through use of the loader environment
variable
_RLD_ARGS.
Setting this
variable to
-ignore_all_versions
disables version testing
for all shared library dependencies.
Setting it to
-ignore_version
with a library name parameter turns off version checking for that
specific dependency.
By default, versions are checked, but not checksums or timestamps. If version testing fails, the loader searches for the matching version of the shared library.
The version identifiers are used to locate version-specific libraries. The loader looks for these libraries in:
dirname/version_id
/usr/shlib/version_id
where dirname is the first directory where a library with a matching name but non-matching version is found.
For example, if an application needs version 1 of a shared library but
the loader first encounters version 2, it continues looking for the correct
version.
14.3.2.3.1 Backward Compatibility
When shared libraries are modified and new versions built, the older versions are frequently retained to support previously linked applications. Maintaining multiple versions of the library helps ensure backward compatibility for existing applications even after binary-incompatible changes have been made.
Backward-compatible shared libraries can be:
Complete independent shared libraries
Partial shared libraries that import missing symbols from other versions of the same shared libraries
The advantage of partial shared libraries is that they require less disk space; a disadvantage is that they require more swap space.
The linker's -L option can be used to link with backward-compatible shared libraries. Warnings are generated when a shared library is linked with dependencies on different versions of the same shared library. However, the linker tests direct dependencies only. The option -transitive_link should be used to uncover all multiple-version dependencies.
Multiple versions of the same shared library can only be loaded to support partial shared library dependencies. Otherwise, dependencies on multiple versions of a library are invalid.
Figure 14-3 shows examples of valid uses of multiple versions.
Figure 14-3: Valid Shared Library with Multiple Versions
Figure 14-4 shows examples of invalid uses of multiple versions.
Figure 14-4: Invalid Shared Library with Multiple Versions
The executable
object is placed in memory first, at the segment base addresses designated
by the linker and recorded in the
a.out
header.
These
addresses are never changed during the lifetime of the executable's image.
After the executable file's segments have been mapped into memory, shared
library dependencies are loaded.
Shared library dependencies are mapped recursively.
The linker chooses quickstart addresses for the text and data regions of shared libraries. The loader attempts to map shared libraries to their quickstart addresses. If this attempt fails because another library has already been mapped to the same address range, the library is relocated to a different address. Note that this problem could be caused by a library mapped by another process. The system tries to map no more than one shared library at a particular virtual address range, system-wide.
Additional dependencies, not present in the library list, can be dynamically
loaded using a
dlopen()
call.
Again, the loader will attempt
to load the library at its quickstart addresses and will relocate it if necessary.
When a shared library is relocated, its text and data segments must
move the same distance in memory.
By fixing the distance between these segments
at link time, the number of dynamic relocations is minimized and restricted
to the data segment.
14.3.2.4.1 Dynamic Loading and Unloading
Dependencies can be loaded and unloaded during
execution by using the
dlopen()
and
dlclose()
system functions.
The
dlopen()
routine accepts a library name and loads
the library and its dependencies.
The loader resolves all symbols in all shared
objects while processing a
dlopen()
call.
If the library
was previously loaded,
dlopen()
re-resolves global symbols
and returns a handle without loading any new objects.
The loader maintains a count of references made to all shared objects
that have been loaded.
For example, if
libm.so
is dependent
upon
libc.so,
libc's reference count
is incremented when the libraries are loaded.
This reference counting is part
of an effort to ensure that a library is never unloaded prematurely.
As an
additional precaution to avoid unloading a library that is still needed, the
number of existing
dlopen()
handles is tracked by the loader.
This
dlopen()
count is incremented each time a
dlopen()
call is made for a particular object.
The
dlclose()
routine unloads a shared library and
its dependencies.
It accepts a handle that was returned by
dlopen().
The
dlclose()
routine will not unload shared libraries
that are still in use.
Both the
dlopen()
count and the
reference count are checked and should be zero before a library is unloaded.
The
dlclose()
routine cannot unload an executable.
It is designed for shared libraries only.
It also cannot unload a shared library
that was not dynamically loaded by
dlopen().
Objects with TLS data
can be dynamically loaded or unloaded during process execution.
A new TLS
region is allocated for all existing threads when an object with TLS data
is loaded.
Similarly, the TLS region will be deallocated for all threads when
the object is unloaded.
14.3.3 Dynamic Symbol Information
The dynamic
symbol table is created at link time for shared objects.
Its primary purpose
is to enable dynamic symbol resolution.
Run-time address information for dynamic
symbols is contained in the GOT section (.got).
The dynamic symbol section (.dynsym) provides information
on globally scoped symbols that are defined or used by the object.
This section
consists of a table of dynamic symbol entries.
The entries are ordered as
follows:
A single null entry
Symbols local to the object
Unreferenced global symbols
Relocations-referenced global symbols (corresponding to special final GOT)
Local symbols are global in scope but are not exported to other objects.
The local portion of the dynamic symbol table contains system symbols representing
the sections of the object:
.text,
.data,
and other linker-defined symbols.
Typically, they do not have GOT entries.
Unreferenced globals are symbols that can be exported but are not referenced by the defining object. They are present in the dynamic symbol table so that other shared objects can import and use them. Unreferenced globals do not have GOT entries.
Referenced globals are exported and are used internally. Dynamic symbols in this category have global GOT entries.
Global symbols that are referenced only by the object's dynamic relocation entries are grouped at the end of the dynamic symbol table, corresponding to a special final GOT. These symbols require GOT entries to record their run-time addresses used in processing dynamic relocations. This special GOT is only used by the loader and is never directly referenced by the program itself.
All linker-defined TLS symbols (see Section 2.3.7) have dynamic symbol entries.
Note that the dynamic symbol table itself is never relocated; it contains
only link-time addresses (in the
st_value
field).
14.3.3.1 Finding Symbol Addresses
Dynamic symbol addresses are found
by the
dlsym()
routine.
The routine searches for the symbol
name beginning in the object associated with the handle.
By default, the search
is breadth-first.
The search is depth-first for objects that were built with
the linker's
-B symbolic
option and for objects that were
loaded with neither the
RTLD_LOCAL
nor
RTLD_GLOBAL
dlopen()
flag.
If the handle is null, the routine performs a depth-first
search beginning at the main executable.
It is important to use the
dlsym()
interface for
symbol look-up to avoid using an outdated address.
This problem can be caused
by an improper compiler assumption that a symbol's address will not change
after load time.
A symbol's address may be cached as an optimization and not
reloaded thereafter.
However, that address may be changed during execution
as the result of dynamic loading and unloading.
14.3.3.2 Scope and Binding
The concept of scope in the dynamic symbol table differs somewhat from the concept of scope in the debug symbol table because the dynamic symbol table contains only global user-program symbols. The terms "local" and "external" thus have different meanings in this context.
The two scoping levels for symbols in the dynamic symbol table are object scope and process scope. A symbol with object scope is local to the shared object and can only be referenced in the library or executable where it is defined. A symbol with process scope is visible to all program components, and may be referenced anywhere. A symbol with process scope can also be preempted by a higher-precedence definition in another shared object.
Note that the distinction between object scope and process scope does not correspond directly to the local/global symbol division in the dynamic symbol table. All symbols in the local part of the table have object scope, but global dynamic symbols can be internal to the object as well. Another factor, called binding, comes into play.
The possible bind values in the dynamic symbol table are local, global,
weak, and duplicate.
These values are encoded in the
st_info
field of the dynamic symbol entry.
(See
Section 14.2.2
for details.)
Users are able to designate global symbols as "hidden". In the dynamic symbol table, hidden symbols have a local binding. This representation ensures that they will not be exported from the object and will not preempt any other symbol definition. Also, internal references to hidden symbols will not be preempted. The linker's "-hidden_symbol symbol" option can be used to specify a hidden symbol.
Weak symbols are
also a special-case category of global symbols that have the same scope as
globals but a lower precedence for symbol resolution conflicts.
See
Section 14.3.4.2
for details.
14.3.3.3 Multiple GOT Representation
The GOT contains address information for all referenced external symbols in the dynamic symbol table. Observe that the GOT is the source of final, run-time addresses, whereas the symbol table contains only link-time addresses. To access a dynamic symbol, the GOT must be referenced. To associate GOT entries with dynamic symbol table entries, the symbol table and GOT are aligned as shown in Figure 14-5.
Figure 14-5: Dynamic Symbol Table and Multiple-GOT
Note that the GOT also contains entries that do not correspond to dynamic symbols. These are placed at the top of each GOT table.
The maximum number of entries in a GOT is 8189. A single GOT may be sufficient to represent all necessary addresses for an object, but one or more additional GOTs are sometimes required, as illustrated in Figure 14-5. One GOT table can contain entries from multiple input objects, but a single object's entries cannot be split between two tables. The linker also builds a separate, final GOT for relocatable global symbols, referenced only in the dynamic relocation section. These constraints generally result in some unused GOT entries at the bottom of each table.
The loader recognizes a multiple-GOT object by examining the dynamic
header.
A
DT_GOTSYM
entry exists
in the dynamic header for each GOT.
This entry holds the index of the first
dynamic symbol table entry corresponding to a GOT entry.
A
DT_LOCAL_GOTNO
entry exists for each GOT as well.
This entry
contains the index of the first global entry in that GOT.
The number of
DT_GOTSYM
entries and
DT_LOCAL_GOTNO
entries in the dynamic header should match.
They
are also expected to occur in ascending numerical order.
The first (zero-indexed) entry for every GOT in a multiple-GOT object
points to the loader's
lazy_text_resolve()
entry point.
In the final GOT (consisting of relocatable symbols), it is present even though
it is unused.
Multiple-GOT objects may contain duplicate symbols.
A symbol appears
only once per GOT, but it can be duplicated in other GOTs.
All duplicate symbols,
marked in the symbol table as
STB_DUPLICATE,
have an associated primary symbol.
The primary symbol is simply the first
instance of a duplicate symbol.
The
st_size
field
for a duplicate symbol is the dynamic symbol table index of the primary symbol.
When a symbol is resolved in a multiple-GOT situation, all duplicates must
be found and resolved as well.
To further illustrate the relationship of GOT entries to dynamic symbol
table entries, a program example is given in
Section 18.3
that
converts a GOT address to a dynamic symbol table entry.
14.3.3.4 Msym Table
The msym table,
which is stored in the
.msym
section of a shared object
file, maps dynamic symbol hash values to the first of any dynamic relocations
for that symbol.
This optional section is included for performance reasons
by building shared objects with the linker's
-msym
option.
An entry in the msym table contains a hash value and an information field. The information field can be masked to obtain a dynamic relocation index and a flags field. The size of the msym table is the same as the size of the dynamic symbol table; the two tables line up directly and have matching indices.
The msym table is referenced repeatedly when an object is opened. The loader resolves symbols by searching all shared objects for matching definitions. The search requires a hash value computed from the symbol name. The msym table provides precomputed hash values for symbols to avoid the costly hash computation at load time.
If the
.msym
section is not present in a shared object,
the loader will create the table each time that the object is loaded.
For
this reason, it is often preferable to specify the
.msym
section's inclusion when building shared objects.
14.3.3.5 Hash Table
A hash table, stored
in the
.hash
section of a shared object file, provides
fast access to symbol entries in the dynamic symbol section.
The table is
implemented as an array of 32-bit integers.
The hash table has the format shown in Figure 14-7.
The entries in the hash table contain the following information:
The
nbucket
entry indicates the
number of entries in the
bucket
array.
The
nchain
entry indicates the
number of entries in the
chain
array.
The
bucket
and
chain
arrays both hold dynamic symbol table indices, and the entries
in
chain
parallel the dynamic symbol table.
The
value of
nchain
is equal to the number of symbol
table entries.
Symbol table indices can be used to select
chain
entries.
The hashing function accepts a symbol name and returns the hash value,
which can be used to compute a
bucket
index.
If
the hashing function returns the value
X
for a
name,
X%nbucket
is the
bucket index.
The hash table entry
bucket[X%nbucket]
gives an index,
Y,
into the dynamic symbol table.
The loader must determine whether the indexed symbol is the correct one. It checks the corresponding dynamic symbol's hash value in the msym table and its name.
If the symbol table entry indicated is not the correct one, the hash
table entry
chain[Y]
indicates the next symbol
table entry for a dynamic symbol with the same hash value.
The indexed symbol
is again checked by the loader.
If it is incorrect, the same index is used
in the
chain
array to try the next symbol that
has the same hash value.
The
chain
links can be
followed in this manner until the correct symbol table entry is located or
until the
chain
entry contains the value
STN_UNDEF.
As an example, assume that a symbol with the hash value 12 is sought.
If there are ten buckets, the calculation
12 % 10
gives
the bucket index 2, which signifies the third bucket.
A bucket index translates
into a hash table index as
bucket[i]=hash[i+2].
If that
bucket contains a 3, the dynamic symbol table entry with an index of 3 is
checked.
If the symbol is incorrect, the hash table entry
chain[3]
is accessed to get the next possible symbol index.
A chain
index translates into a hash table index as
chain[i]=hash[nbucket+2+i].
If
chain[3]
is 7, the dynamic symbol
table entry with an index of 7 is checked.
If it is the correct symbol, the
search is successful and halts.
The structures used in this example are shown in Figure 14-8.
14.3.4 Dynamic Symbol Resolution
The dynamic loader must perform symbol resolution for unresolved symbols that remain after link time. A post-link unresolved symbol is one that was not defined in a shared object or in any of the shared object's shared library dependencies searched by the linker. If a dependency is changed before execution or additional libraries are dynamically loaded, the loader will attempt to resolve the symbol.
The linker accepts unresolved symbols when linking shared objects and
records them in the dynamic symbol (.dynsym) section.
The
loader recognizes an unresolved symbol by a symbol type of undefined (st_shndx
==
SHN_UNDEF)
and a symbol value of zero (st_value
== 0) in the
dynamic symbol table.
For such symbols, the GOT value distinguishes imported
symbols from symbols that are unresolved across all shared objects.
Table 6-7 gives a rough idea of different categories of symbols and how they are represented in the dynamic symbol table. Run-time addresses are stored in the GOT. They can be pre-computed by the linker and adjusted at load time.
Table 14-7: Dynamic Symbol Categories
| Description | Type | Section | Value | GOT |
| defined item |
STT_OBJECT,
STT_FUNC |
SHN_TEXT,
SHN_DATA,
SHN_ACOMMON |
address | address |
| imported function |
STT_FUNC |
SHN_UNDEF |
0 | address (in defining object) |
| imported data |
STT_OBJECT |
SHN_UNDEF |
0 | address (in defining object) |
| common |
STT_OBJECT |
SHN_COMMON |
alignment | address of allocated common (in defining object) |
| unresolved function |
STT_FUNC |
SHN_UNDEF |
0 | lazy text stub address |
| unresolved data |
STT_OBJECT |
SHN_UNDEF |
0 | 0 |
The loader performs symbol resolution during initial load of a program. The amount of symbol resolution work required by a program varies (see Section 14.3.4.6).
The loader can also perform dynamic symbol resolution
for particular symbols during program execution.
If new dependencies are added
or existing dependencies are rearranged, externally visible symbols (those
with process scope) may be bound to a new address.
Rebinding after a
dlopen()
or
dlclose()
call is only performed for
symbol references in shared libraries that were not loaded with a
dlopen()
flag of
RTLD_LOCAL
or
RTLD_GLOBAL.
Unresolved
text symbols can be resolved at run time instead of load time (see
Section 14.3.4.5).
14.3.4.1 Symbol Preemption and Namespace Pollution
A namespace is a scope within which symbol names should all be unique. In a namespace, a given name is bound to a single item, wherever it may be used. This generic use of the term "namespace" is distinct from the C++ namespace construct, which is discussed in Section 11.3.1.5.
Dynamic executables running on Tru64 UNIX share a namespace with their shared library dependencies. This policy is implemented with symbol preemption. Symbol preemption, also referred to as "hooking", is a mechanism by which all references to a multiply-defined symbol are resolved to the same instance of the symbol.
Advantages of symbol preemption include:
All shared objects use one global namespace.
Dynamic and static executables behave more consistently.
Applications can replace library routines to debug, improve, or customize them.
Disadvantages include extra load time for symbol resolution and potential problems resulting from namespace pollution.
Namespace pollution can occur during the use of shared libraries. A library routine may malfunction if it calls or accesses a global symbol that is redefined by another shared library or application. Figure 14-9 presents an example of this situation.
Figure 14-9: Namespace Pollution
Namespace pollution is partly covered by ANSI standards.
Namespace
conflicts that occur between
libc
and ANSI-compliant
programs must not affect the behavior of ANSI-defined functions implemented
in
libc.
The identifiers reserved for use by the library are:
Names beginning with underscores
ANSI-defined symbols (fopen(),
malloc(), and so forth)
All other names are available to user programs. User versions of non-reserved identifiers preempt library versions.
Historically, system libraries have used many unreserved symbol names. To achieve compliance with the ANSI standard, global symbols have undergone a name change. Documented interfaces have been retained as weak symbols (see Section 14.3.4.2). Their strong counterparts have names that are formed by prepending two underscores to the corresponding weak symbol's name.
Hidden symbols do not cause namespace pollution problems and cannot be preempted because they are not exported from the shared object where they are defined.
The linker options -hidden_symbol and -exported_symbol turn the hidden attribute on or off for a given symbol name. The options -hidden and -non_hidden turn the hidden attribute on or off for all subsequent symbols.
TLS data symbols have
the same name scope as hidden symbols.
The names are not shared among multiple
threads.
14.3.4.2 Weak Symbols
Weak symbols are global symbols that have a lower precedence in symbol resolution than other globals. Strong symbols are any symbols that are not marked as weak.
Weak symbols can be used as aliases for other weak or strong symbols. This technique can be useful when it is desirable to provide both a low-precedence name and a high-precedence name for the same data item or procedure. When the weak symbol is referenced, its strong counterpart is the one actually used.
This aliasing approach
employing weak symbols is used in
libc.so
to avoid namespace
pollution problems.
In the example in
Figure 14-10, the strong
symbol definition in the application takes precedence over the weak library
definition, and the program functions properly.
Figure 14-10: Weak Symbol Resolution (I)
Figure 14-11: Weak Symbol Resolution (II)
If no non-weak
open
symbols were defined, references
to
open
would bind to the weak symbol definition in
libc.so, as shown in
Figure 14-11.
Weak symbols can also be used to prevent multiple symbol definition errors or warnings when linking. Neither the linker nor loader require a weak symbol to be aliased to a strong symbol, but the loader will attempt to find a matching strong symbol for any weak symbol it is attempting to resolve.
To find a weak symbol's strong counterpart, the loader follows these steps:
Use hash lookup to find __name
If __name is not found or not a match, test each dynamic symbol for matching attributes
If a strong matching symbol is found check for a preempting symbol definition in another shared object
Matching symbols will have the same
st_value,
COFF_ST_TYPE(st_info)
and
st_shndx.
A weak symbol is identified in the dynamic symbol table by a
STB_WEAK
bind value.
In the external symbol
table, a weak symbol has its
weak_ext
flag set
in the
EXTR
entry.
Users can specify weak symbols using the
.weakext
assembler directive or the C
#pragma weak
preprocessor
directive.
14.3.4.3 Search Order
The dynamic
symbol resolution policy, or symbol search order, defines the order in which
the loader searches for symbol definitions in a dynamic executable, its shared
library dependencies, and shared libraries added to the process image by
dlopen().
Default search order is a breadth-first, left-to-right traversal of the shared library dependency graph.
Figure 14-12: Symbol Resolution Search Order
The search order in
Figure 14-12
is:
a.out libA
libB libc.so libD libE
Objects loaded dynamically
by
dlopen()
are appended to the search order established
at load time.
However,
dlopen()
options will determine
whether a dynamically loaded object's symbols are visible to objects that
do not include it in their dependency lists.
See
dlopen(3)
Alternate search orders can be specified using linker or loader options. The -B symbolic linker option marks an object to be loaded with "depth ring" search order. This search order consists of a two-step process:
Depth-first search the referencing object and its dependencies
Depth-first search from the main executable
Using the depth ring search policy and the dependency graph from Figure 14-12, the search order is:
| From | Search Order |
a.out |
a.out libA libD libc.so libB libE |
libA |
libA libD libc.so a.out libB libE |
libB |
libB libE libc.so a.out libA libD |
libD |
libD libc.so a.out libA libB libE |
libE |
libE libc.so a.out libA libD libB |
libc.so |
libc.so a.out libA libD libB libE |
The highest-to-lowest precedence order for dynamic symbol resolution is:
Strong text or data
Strong largest allocated common
Weak data
Weak largest allocated common
Largest common
Weak text
In case (5), the loader allocates the common symbol. This situation only arises when an object containing an allocated common of the same name has been changed between link time and load time or is dynamically unloaded during run time. The linker will always allocate a common storage class symbol, but if there are multiple occurrences of that symbol, the others are retained as unallocated commons.
When symbols have equal precedence, the loader relies on the search
order to choose the correct definition for the symbol.
14.3.4.5 Lazy Text Resolution
Lazy text resolution allows programs to execute without resolving text symbols that are never referenced.
Programs with unresolved text symbols are linked with stub routines. When a program or library calls a stub routine, the stub calls the loader's lazy text resolution entry point with a dynamic symbol index as an argument. The loader then resolves the text symbol. Subsequent calls will use the true address, which has replaced the stub in the appropriate GOT entry.
The dynamic symbol table does not contain any explicit information that indicates whether a text symbol has a stub associated with it. The loader looks for the following clues instead:
Symbol's
st_shndx
is
SHN_UNDEF
Symbol's
st_value
is zero
Symbol's GOT entry is not 0 and is in text segment's address range
The environment variable
LD_BIND_NOW
controls the loader's text resolution
mode.
If the variable has a non-null value, the bind mode is immediate.
If
the value is null, the bind mode is deferred.
Immediate binding requires all
symbols to be resolved at load time.
Deferred binding allows text symbols
to be resolved at run time using lazy text evaluation.
The default is deferred
binding.
See
Section 3.3.3
for related information.
14.3.4.6 Levels of Resolution
Conditions may exist that cause the loader to do more symbol resolution work for some programs than for others. The amount of symbol resolution work that is necessary can have a significant impact on a program's start-up time.
Descriptions of the possible levels of dynamic symbol resolution follow.
Quickstart Resolution
Minimal symbol resolution.
For details on quickstart, see
Section 14.3.6.
Timestamp
Resolution
Moderate symbol resolution. This is used when any of the following are true:
The executable or one of its dependencies has indirect dependencies that it was not linked with.
The executable or one of its dependencies has unresolved text symbols that are used in dynamic relocations.
A shared library dependency was rebuilt so that the timestamp no longer matches the dependency information in the executable.
Extensive symbol resolution. This is used when a shared library dependency has been rebuilt and its checksum no longer matches the dependency information in the executable. The checksum changes if any of the following conditions are met:
Global symbols are added
Global symbols are deleted
Global symbols change from strong to weak or vice versa
Common storage class symbols' sizes change.
Re-resolve symbols marked
SHN_UNDEF
for immediate binding.
This is used by
dlopen()
to apply
immediate binding symbol resolution to shared objects that were previously
resolved with deferred binding.
14.3.5 Dynamic Relocation
The dynamic relocation section describes all locations that must be adjusted within the object if an object is loaded at an address other than its linked base address.
Although an object may have multiple relocation sections, the linker
concatenates all relocation information present in its input objects.
The
dynamic loader is thus faced with a single relocation table.
This dynamic
relocation table is stored in the
.rel.dyn
section and
is ordered by the corresponding dynamic symbol index.
Offset 0 in the dynamic relocation table is reserved for a null entry with all fields zeroed.
All dynamic relocations must be of the type
R_REFQUAD
or
R_REFLONG.
This simplifies the dynamic relocation process.
These two relocation types
are sufficient to represent all information that is necessary to accomplish
dynamic relocations.
Dynamic relocation entries must only apply to addresses
in an object's data segment.
The object's text segment must not contain any
relocatable addresses.
Relocation entries are updated during dynamic symbol resolution. When a dynamic symbol's value changes, any dynamic relocations associated with that symbol must be updated. To update the entries, the relocation value is computed by subtracting the old value of the from the new value. This value is then added to the contents of the relocation targets. The old value of a dynamic symbol is always stored in a GOT entry. The new value of a dynamic symbol is stored in that GOT entry after dynamic relocations are processed.
Relocation types other than
R_REFQUAD
and
R_REFLONG
are not allowed for
dynamic relocations because no other relocation types apply to absolute addresses
stored in data.
Most relocation types apply to values that need to be computed
at link time and do not change at run time.
A dynamic executable or shared library may also contain preserved normal
relocation sections.
If normal relocation entries are present, the loader
ignores them.
14.3.6 Quickstart
Quickstart is a loading technique that uses predetermined addresses to run a program that depends on shared libraries. It is particularly useful for applications that rely on shared libraries that change infrequently.
The linker chooses quickstart addresses
for all shared library dependencies when a dynamic executable is linked.
These
addresses are stored in the registry file normally named
so_locations.
For details on the shared library registry file, refer to the
Programmer's Guide.
Any modification to
a shared library impairs quickstarting of applications that depend on that
library.
If a shared library dependency has changed, it may be possible to
use the
fixso
utility to update the application and thus
enable quickstart to succeed.
To verify that an application is quickstarted, use the -quickstart_only loader option. For example:
% setenv _RLD_ARGS -quickstart_only % a.out 1834:a.out: /sbin/loader: Fatal Error: quickstart requirements not met
Additional information on quickstart is available in the
Programmer's Guide.
14.3.6.1 Quickstart Levels
Not all shared objects can be successfully quickstarted. If an executable cannot be quickstarted, it still runs, but start up is slower. Quickstarting is possible for programs requiring minimal symbol resolution at load time. A dynamic executable is quickstarted if:
The object's mapped virtual address matches the quickstart address chosen by the linker.
The object's dependencies have not been modified incompatibly since the object was linked.
The object's indirect dependencies are all included as direct dependencies.
The object's dependencies also meet quickstart criteria.
Each quickstart requirement that is not met by a dynamic executable and its dependencies leads to additional symbol resolution work.
If all quickstart requirements are met, only undefined and multiply defined symbols need to be resolved.
If the mapped address differs from the quickstart address, addresses of defined symbols must be adjusted.
If the timestamp has been changed, external (imported) symbols must be resolved.
If the checksum has been changed, all symbols must be resolved.
At this point, the timesaving advantage of quickstarting has disappeared.
For quickstart purposes, a link-time shared library matches its associated
load-time shared library if the timestamp and checksum are unchanged.
If they
have been changed, using the
fixso
tool may remedy the
situation and enable quickstart to succeed.
14.3.6.2 Conflict Table
The conflict
table, stored in the
.conflict
section, contains a list
of symbols that are multiply defined and must be resolved by the loader.
The
conflict table is used only when full quickstarting is possible.
If any changes
preventing quickstart have occurred, the loader resorts to other methods of
symbol resolution.
The linker records conflicts in a shared object's
.conflict
section if a second definition is found for a previously-defined
symbol.
Common storage class symbols are not considered conflicts unless they
are allocated in more than one shared object.
Weak symbols aliased to a newly resolved conflict entry are also treated as conflicts. This means the loader does not have to search for weak symbols matching conflict symbols. The weak symbols are added to the conflict list for the first shared library that defined the symbol in question as well as the library where the conflicting definition was found.
Figure 14-13 shows a simple example of the use of conflict entries.
Figure 14-13: Conflict Entry Example
In this example, the
a.out
executable has been
linked with liba.so, and a single conflict has been recorded for the symbol
a_error().
The conflict is recorded in the executable file at link
time because both the executable and shared library define the symbol.
At
run time, any calls to
a_error()
from
a_sort()
will be preempted by the definition of
a_error()
in the
a.out
executable.
Without the conflict entry,
the call to
a_error()
would not be preempted properly when
a.out
is quickstarted.
14.3.6.3 Repairing Quickstart
The
fixso
utility updates shared libraries to permit quickstarting of applications that
utilize them, even if the libraries have changed since the executable was
originally linked against them.
Given a shared object as input, it updates
the object and its dependencies to make them meet quickstart criteria.
The
library changes handled by
fixso
are timestamp and checksum
discrepancies.
The
fixso
utility creates a breadth-first list of
the object's dependencies.
It then handles conflicts present in the conflict
table.
Next,
fixso
resolves globals, updating global symbol
values, dynamic relocation entries, and GOT entries where necessary.
Lastly,
if these actions are successful,
fixso
resets the timestamp
and checksum of its target object.
When a dependency is discovered during processing,
fixso
automatically opens the associated object and adds it to the object list if
possible.
The dependency will be found and opened if it is located in the
default library search path, the path indicated by the
LD_LIBRARY_PATH
environment variable, or the path specified in
the command line.
Otherwise, it may be necessary to run the
fixso
program on the library separately, before fixing the target object.
Some changes made to shared libraries cannot be reconciled by
fixso.
The
fixso
utility does not support:
Increases in size required in the conflict list (new conflicts)
Movement of the library in memory
Discrepancies in interface versions
Changes to a library's path
Discrepancies in soname values