Shared libraries are the default system libraries. The default behavior of the C compiler is to use shared libraries when performing compile and link operations.
This chapter discusses the following topics:
Shared libraries consist of executable code that can be located at any available address in memory. Only one copy of a shared library's instructions is loaded, and the system shares that one copy among multiple programs instead of loading a copy for each program using the library, as is the case with archive (static) libraries.
Programs that use shared libraries enjoy the following significant advantages over programs that use archive libraries:
This means that use of shared libraries occupies less space in memory and on disk. When multiple programs are linked to a single shared library, the amount of physical memory used by each process can be significantly reduced.
From a user perspective, the use of shared libraries is transparent. In addition, you can build your own shared libraries and make them available to other users. Most object files and archive libraries can be made into shared libraries. See Section 4.5 for more information on which files can be made into shared libraries.
Shared libraries differ from archive libraries in the following ways:
Figure 4-1 illustrates the difference between the use of archive and shared libraries.
Symbol resolution is the process of mapping an unresolved symbol imported by a program or shared library to the pathname of the shared library that exports that symbol. Symbols are resolved in much the same way for shared and archive libraries, except that the final resolution of symbols in shared objects does not occur until a program is invoked.
The following sections describe:
When the linker (ld) searches for files that have been specified by using the -l option on the command line, it searches each directory in the order shown in the following list, looking first in each directory for a shared library (.so) file.
If the linker does not find a shared library, it searches through the same directories again, looking for an archive (.a) library. You can prevent the search for archive libraries by using the -no_archive option to the ld command.
Unless otherwise directed, the run-time loader (/sbin/loader) follows the same search path as the linker (ld). You can use one of the following methods to direct the run-time loader to look in directories other than those specified by the default search path:
You can set the LD_LIBRARY_PATH variable by using either of the following methods:
For the C shell, use the
setenv
command followed by a colon-separated path. For example:
%
setenv LD_LIBRARY_PATH .:$HOME/testdir
For the Bourne and Korn shells, set the variable and then export it.
For example:
$
LD_LIBRARY_PATH=.:$HOME/testdir
$
export LD_LIBRARY_PATH
These examples set the path so that the loader looks first in the current directory and then in your $HOME/testdir directory.
setenv LD_LIBRARY_PATH .:$HOME/testdir:/usr/shlib
If the loader cannot find the library it needs in the paths defined by any of the preceding steps, it looks through the directories specified in the default path described in the previous section. In addition, you can use the _RLD_ROOT environment variable to alter the search path of the run-time loader. For more information, see the loader(5) reference page.
The semantics of symbol name resolution are based on the order in which the object file or shared object containing a given symbol appears on the link command line. The linker (ld) normally takes the leftmost definition for any symbol that must be resolved.
The sequence in which names are resolved proceeds as if the link command line were stored in the executable program. When the program runs, all symbols that are accessed during execution must be resolved. The loader aborts execution of the program if an unresolved text symbol is accessed.
For information on how to determine the behavior of the system regarding unresolved symbols, see Section 4.2.4. The following sequence is followed to resolve references to any symbol from the main program or from a library:
A / \ B D / C
The search order is A-B-D-C. In a breadth-first search, the grandchildren of a node are searched after all the children have been searched.
Note that because symbol resolution always prefers the main object, shared libraries can be set up to call back into a defined symbol in the main object. Likewise, the main object can define a symbol that will override (preempt or hook) a definition in a shared library.
The default behavior of the linker when building executable programs differs from its default behavior when building shared libraries:
You can control the behavior of the linker by using the following flags to the ld command:
When compiling and linking a program, using shared libraries is the
same as using static libraries.
For example, the following command compiles program
hello.c
and links it against the default system C shared library
libc.so:
%
cc -o hello hello.c
You can pass certain
ld
command flags to the
cc
command to allow flexibility in determining the search path for a
shared library. For example, you can use the
-Ldir flag with the
cc
command to change the search path by adding
dir
before the default directories, as in the following example:
%
cc -o hello hello.c -L/usr/person -lmylib
To exclude the default directories from the search and limit the search
to specific directories
and specific libraries, specify the
-L
flag first with no arguments. Then, specify it again with the
directory to search, followed by the
-l
flag with the name of the library to search for. For example, to limit
the search path to
/usr/person
for use with the private library
libmylib.so,
enter the following command:
%
cc -o hello hello.c -L -L/usr/person -lmylib
Note that because the cc command always implicitly links in the C library, the preceding example requires that a copy of libc.so or libc.a be in the /usr/person directory.
In application linking, the default behavior is to use shared libraries. To link an application that does not use shared libraries, you must use the -non_shared flag to the cc or ld commands when you link that application.
For example,
%
cc -non_shared -o hello hello.c
Although shared libraries are the default for most programming applications, some applications cannot use shared libraries:
You create shared libraries by using the ld command with the -shared flag. You can create shared libraries from object files or from existing archive libraries.
To create the shared library
libbig.so
from the object files
bigmod1.o
and
bigmod2.o,
enter the following command:
%
ld -shared -no_archive -o libbig.so bigmod1.o bigmod2.o -lc
The -no_archive flag tells the linker to resolve symbols using only shared libraries. The -lc flag tells the linker to look in the system C shared library for unresolved symbols.
To make a shared library available on a system level by copying it into the /usr/shlib directory, you must have root privileges. System shared libraries should be located in the /usr/shlib directory or in one of the default directories so that the run-time loader (/sbin/loader) can locate them without requiring every user to set the LD_LIBRARY_PATH variable to directories other than those in the default path.
You can also create a shared library from an existing archive library
by using the
ld
command. The following example shows how to convert the static library
old.a
into the shared library
libold.so:
%
ld -shared -no_archive -o libold.so -all old.a -none -lc
In this example, the -all flag tells the linker to link all the objects from the archive library old.a. The -none flag tells the linker to turn off the -all flag. Note that the -no_archive flag applies to the resolution of the -lc flag but not to old.a (because old.a is explicitly mentioned).
In addition to system shared libraries, any user can create and use private shared libraries. For example, you have three applications that share some common code. These applications are named user, db, and admin. You decide to build a common shared library, libcommon.so, containing all the symbols defined in the shared files io_util.c, defines.c, and network.c. To do this, take the following steps:
%
cc -c io_util.c
%
cc -c defines.c
%
cc -c network.c
%
ld -shared -no_archive \
?
-o libcommon.so io_util.o defines.o network.o -lc
%
cc -c user.c
%
cc -o user user.o -L. -lcommon
Note that the second command in this step tells the linker to look in the current directory and use the library libcommon.so.
Compile
db.c
and
admin.c
in the same manner:
%
cc -c db.c
%
cc -o db db.o -L. -lcommon
%
cc -c admin.c
%
cc -o admin admin.o -L. -lcommon
One advantage of using shared libraries is the ability to change a library after all executable images have been linked and to fix bugs in the library. This ability is very useful during the development phase of an application.
During the production cycle, however, the shared libraries and applications you develop are often fixed and will not change until the next release. If this is the case, you can take advantage of quickstart, a method of using predetermined addresses for all symbols in your program and libraries.
No special link options are required to prepare an application for quickstarting; however, a certain set of conditions must be satisfied. If an object cannot be quickstarted, it still runs, but startup time is slower.
When the linker creates a shared object (a shared library or a main executable program that uses shared libraries), it assigns addresses to the text and data portions of the object. These addresses are what might be called "quickstarted addresses." The linker performs all dynamic relocations in advance, as if the object will be loaded at its quickstarted address.
Any object depended upon is assumed to be at its quickstarted address. References to that object from the original object have the address of the depended-upon object set accordingly.
In order to use quickstart, an object must meet the following conditions:
The operating system detects these conditions by using checksums and timestamps.
When you build libraries, they are given a quickstart address. Unless each library used by an application chooses a unique quickstart address, the quickstart constraints cannot be satisfied. Rather than worry about addresses on an application basis, you should give each shared library you build a unique quickstart address to ensure that all of your objects can be loaded at their quickstart addresses.
The linker maintains the so_locations database to register each quickstart address when you build a library. The linker avoids addresses already in the file when choosing a quickstart address for a new library.
By default, ld runs as though the -update_registry ./so_locations flag has been selected, so the so_locations file in the directory of the build is updated (or created) as necessary.
To ensure that your libraries do not collide with shared libraries on
your system, enter these commands:
%
cd <directory_of_build>
%
cp /usr/shlib/so_locations .
%
chmod +w so_locations
You can now build your libraries. If your library builds occur in
multiple directories, use the
-update_registry
flag to the
ld
command to explicitly specify the location of a common
so_locations
file.
For example:
%
ld -shared -update_registry /common/directory/so_locations ...
If you install your shared libraries globally for all users of your
system, update the system-wide
so_locations
file. Enter the following commands as root, with
shared_library.so being the name of your actual shared library:
#
cp
shared_library.so /usr/shlib
#
mv /usr/shlib/so_locations /usr/shlib/so_locations.old
#
cp so_locations /usr/shlib
Of course, if several people are building shared libraries, the common so_locations file must be administered as any shared database would be. Each shared library used by any given process must be given a unique quickstart address in the file. The range of default starting addresses that the linker assigns to main executable files does not conflict with the quickstarted addresses it creates for shared objects. Because only one main executable file is loaded into a process, an address conflict never occurs between a main file and its shared objects.
If you are building only against existing shared libraries (and not building your own libraries), you do not need to do anything special. As long as the libraries meet the previously described conditions, your program will be quickstarted unless the libraries themselves are not quickstarted. Most shared libraries shipped with the operating system are quickstarted.
If you are building shared libraries, you must first copy the so_locations file as previously described. Next, you must build all shared libraries in bottom-up dependency order, using the so_locations file. You should mention all libraries that are depended upon on the link line. After all libraries are built, you can then build your applications.
To test whether an application's executable program is quickstarting,
set the
_RLD_ARGS
environment variable to
-quickstart_only
and run the program. For example:
%
setenv _RLD_ARGS -quickstart_only
%
foo
(non-quickstart output)
21887:foo: /sbin/loader: Fatal Error: NON-QUICKSTART detected \ -- QUICKSTART must be enforced
If the program runs successfully, it is quickstarting. If a load error message is produced, the program is not quickstarting.
To determine why an executable program is not quickstarting, you can use the fixso utility as described in Section 4.7.3 or you can manually test for the conditions described in the following list of requirements. Using fixso is easier, but it is helpful to understand the process involved:
Test the quickstart flag in the dynamic header. The value of the
quickstart flag is (0x00000001). For example:
%
odump -D foo | grep FLAGS
(non-quickstart output)
FLAGS: 0x00000000
(quickstart output)
FLAGS: 0x00000001
If the quickstart flag is not set, one or more of the following conditions exists:
Get a list of an executable program's dependencies:
%
odump -Dl foo
(quickstart output)
***LIBRARY LIST SECTION*** Name Time-Stamp CheckSum Flags Version foo: libX11.so Sep 17 00:51:19 1993 0x78c81c78 NONE libc.so Sep 16 22:29:50 1993 0xba22309c NONE osf.1 libdnet_stub.so Sep 16 22:56:51 1993 0x1d568a0c NONE osf.1
Test the quickstart flag in the dynamic header of each of the
dependencies:
% cd
/usr/shlib
%
odump -D libX11.so libc.so libdnet_stub.so | grep FLAGS
(quickstart output)
FLAGS: 0x00000001 FLAGS: 0x00000001 FLAGS: 0x00000001
If any of these dependencies cannot be quickstarted, the same measures suggested in step 1 can be applied here, provided that the shared library can be rebuilt by the user.
The dependencies list in step 2 shows the expected values of the
timestamp and checksum fields for each of
foo's
dependencies. Match these values against the current values for each
of the libraries:
% cd
/usr/shlib
%
odump -D libX11.so libc.so libdnet_stub.so | \
grep TIME_STAMP
(quickstart output)
TIME_STAMP: (0x2c994247) Fri Sep 17 00:51:19 1993 TIME_STAMP: (0x2c99211e) Thu Sep 16 22:29:50 1993 TIME_STAMP: (0x2c992773) Thu Sep 16 22:56:51 1993% odump -D libX11.so libc.so libdnet_stub.so | grep CHECKSUM
(quickstart output)
ICHECKSUM: 0x78c81c78 ICHECKSUM: 0xba22309c ICHECKSUM: 0x1d568a0c
If any of the tests in these examples shows a timestamp or checksum mismatch, relinking the program should fix the problem.
You can use the version field to verify that you have identified the
correct libraries to be loaded at run time. To test the dependency
versions, use the
odump
command as in the following example:
%
odump -D libX11.so | grep IVERSION
%
odump -D libc.so | grep IVERSION
IVERSION: osf.1
%
odump -D libdnet_stub.so | grep IVERSION
IVERSION: osf.1
The lack of an IVERSION entry is equivalent to a blank entry in the dependency information. It is also equivalent to the special version _null.
If any version mismatches are identified, you can normally find the correct matching version of the shared library by appending the version identifier from the dependency list or _null to the path /usr/shlib.
Repeat step 3 for each of the shared libraries in the executable
program's list of dependencies:
%
odump -Dl libX11.so
(quickstart output)
***LIBRARY LIST SECTION*** Name Time-Stamp CheckSum Flags Version libX11.so: libdnet_stub.so Sep 16 22:56:51 1993 0x1d568a0c NONE osf.1 libc.so Sep 16 22:29:50 1993 0xba22309c NONE osf.1% odump -D libdnet_stub.so libc.so | grep TIME_STAMP
TIME_STAMP: (0x2c992773) Thu Sep 16 22:56:51 1993 TIME_STAMP: (0x2c99211e) Thu Sep 16 22:29:50 1993% odump -D libdnet_stub.so libc.so | grep CHECKSUM
ICHECKSUM: 0x1d568a0c ICHECKSUM: 0xba22309c
If the timestamp or checksum information does not match, the shared library must be rebuilt to correct the problem. Rebuilding a shared library will change its timestamp and, sometimes, its checksum. Rebuild dependencies in bottom-up order so that an executable program or shared library is rebuilt after its dependencies have been rebuilt.
The fixso utility can identify and repair quickstart problems caused by timestamp and checksum discrepancies. It can repair programs as well as the shared libraries they depend on, but it might not be able to repair certain programs, depending on the degree of symbolic changes required.
The fixso utility cannot repair a program or shared library if any of the following restrictions apply:
The
fixso
utility can identify quickstart problems as shown in the following
example:
%
fixso -n hello.so
fixso: Warning: found '/usr/shlib/libc.so' (0x2d93b353) which does not match timestamp 0x2d6ae076 in liblist of hello.so, will fix fixso: Warning: found '/usr/shlib/libc.so' (0xc777ff16) which does not match checksum 0x70e62eeb in liblist of hello.so, will fix
The
-n
flag suppresses the generation of an output file. Discrepancies are
reported, but
fixso
does not attempt to repair the problems it finds. The following
example shows how
fixso
can be used to repair quickstart problems:
%
fixso -o ./fixed/main main
fixso: Warning: found '/usr/shlib/libc.so' (0x2d93b353) which does not match timestamp 0x2d7149c9 in liblist of main, will fix% chmod +x fixed/main
The -o flag specifies an output file. If no output file is specified, fixso uses a.out. Note that fixso does not create the output file with execute permission. The chmod command allows the output file to be executed. This change is necessary only for executable programs and can be bypassed when using fixso to repair shared libraries.
If a program or shared library does not require any modifications to
repair quickstart,
fixso
indicates this as shown in the following example:
%
fixso -n /bin/ls
no fixup needed for /bin/ls
Debugging a program that uses shared libraries is essentially the same as debugging a program that uses archive libraries.
The dbx debugger's listobj command displays the names of the executable programs and all of the shared libraries that are known to the debugger. Refer to Chapter 5 for more information about using dbx.
In some situations, you might want to load a shared library from within a program. This section includes two short C program examples and a makefile to demonstrate how to load a shared library at run time.
The following example
(pr.c)
shows a C source file that prints out a simple message:
printmsg() { printf("Hello world from printmsg!\n"); }
The next example
(used1.c)
defines symbols and demonstrates how to use the
dlopen
function:
#include <stdio.h> #include <dlfcn.h>
/* All errors from dl* routines are returned as NULL */ #define BAD(x) ((x) == NULL)
main(int argc, char *argv[]) { void *handle; void (*fp)();
/* * Using "./" prefix forces dlopen to look only in the current * current directory for pr.so. Otherwise, if pr.so were not * found in the current directory, dlopen would use rpath, * LD_LIBRARY_PATH and default directories for locating pr.so. */ handle = dlopen("./pr.so", RTLD_LAZY); if (!BAD(handle)) { fp = dlsym(handle, "printmsg"); if (!BAD(fp)) { /* * Here is where the function * we just looked up is called. */ (*fp)(); } else { perror("dlsym"); fprintf(stderr, "%s\n", dlerror()); } } else { perror("dlopen"); fprintf(stderr, "%s\n", dlerror()); } dlclose(handle); }
The following example shows the makefile that makes
pr.o,
pr.so,
so_locations,
and
usedl.o.
# this is the makefile to test the examples
all: runit
runit: usedl pr.so ./usedl
usedl: usedl.c $(CC) -o usedl usedl.c
pr.so: pr.o $(LD) -o pr.so -shared pr.o -lc
Because of the sharing mechanism used for shared libraries, normal file system protections do not protect libraries against unauthorized reading. For example, when a shared library is used in a program, the text part of that library can be read by other processes even when the following conditions exist:
Only the text part of the library, not the data segment, is shared in this manner.
To prevent unwanted sharing, link any shared libraries that need to
be protected by using the linker's
-T
and
-D
flags to put the data section in the same 8-megabyte segment as the
text section. For example, enter a command similar to the following:
%
ld -shared -o libfoo.so -T 30000000000 \
-D 30000400000 object_files
In addition, segment sharing can occur with any file that uses the mmap system call without the PROT_WRITE flag as long as the mapped address falls in the same memory segment as other files using mmap.
Any program using
mmap
to examine files that might be highly protected can ensure that no
segment sharing takes place by introducing a writable page into the
segment before or during the
mmap.
The easiest way to provide protection is to use the
mmap
system call on the file with
PROT_WRITE
enabled in the protection, and use the
mprotect
system call to make the mapped memory read-only. Alternatively, to
disable all segmentation and avoid any unauthorized sharing, enter the
following in the configuration file:
segmentation 0
One of the advantages of using shared libraries is that a program linked with a shared library does not need to be rebuilt when changes are made to that library. When a changed shared library is installed, applications should work as well with the newer library as they did with the older one.
Note
Because of the need for address fixing, it can take longer to load an existing application that uses an older version of a shared library when a new version of that shared library is installed. You can avoid this kind of problem by relinking the application with the new library.
Infrequently, a shared library might be changed in a way that makes it incompatible with applications that were linked with it before the change. This type of change is referred to as a binary incompatibility. A binary incompatibility introduced in a new version of a shared library does not necessarily cause applications that rely on the old version to break (that is, violate the backward compatibility of the library). The system provides shared library versioning to allow you to take steps to maintain a shared library's backward compatibility when introducing a binary incompatibility in the library.
Among the types of binarily incompatible changes that might occur in
shared libraries are the following:
For example, if the malloc() function in libc.so were replaced with a function called _ _malloc, programs that depend on the older function would fail due to the missing malloc symbol.
For example, if a second argument to the malloc() function in libc.so were added, the new malloc() would probably fail when programs that depend on the older function pass in only one argument, leaving undefined values in the second argument.
For example, if the type of the errno symbol in libc.so were changed from an int to a long, programs linked with the older library might read and write 32-bit values to and from the newly expanded 64-bit data item. This might yield invalid error codes and indeterminate program behavior.
This is by no means an exhaustive list of the types of changes that result in binary incompatibilities. Shared library developers should exercise common sense to determine whether any change is likely to cause failures in applications linked with the library prior to the change.
You can maintain the backward compatibility of a shared library affected by binarily incompatible changes by providing multiple versions of the library. Each shared library is marked by a version identifier. You install the new version of the library in the library's default location, and the older, binary compatible version of the library in a subdirectory whose name matches that library's version identifier.
For example, if a binarily incompatible change was made to libc.so, the new library (/usr/shlib/libc.so) must be accompanied by an instance of the library before the change (/usr/shlib/osf.1/libc.so).
In this example, the older, binary compatible version of libc.so is "osf.1". After the change is applied, the new libc.so is built with a new version identifier. Because a shared library's version identifier is listed in the shared library dependency record of a program that uses the library, the loader can identify which version of a shared library is required by an application (see Section 4.11.6).
In the example, a program built with the older libc.so, before the binary incompatible change, requires version "osf.1" of the library. Because the version of /usr/shlib/libc.so does not match the one listed in the program's shared library dependency record, the loader will look for a matching version in /usr/shlib/osf.1.
Applications built after the binarily incompatible change will use /usr/shlib/libc.so and will depend on the new version of the library. The loader will load these applications by using /usr/shlib/libc.so until some further binary incompatibility is introduced.
Table 4-1 describes the linker flags used to effect version control of shared libraries.
Flag | Description |
-set_version version-string | |
Establishes the version identifiers associated with a shared library.
The string
version-string
is either a single version identifier or a colon-separated list of
version identifiers. No restrictions are placed on the names of
version identifiers; however, it is highly recommended that UNIX
directory naming conventions be followed.
If a shared library is built with this flag, any program built against it will record a dependency on the specified version or, if a list of version identifiers is specified, the rightmost version specified in the list. If a shared library is built with a list of version identifiers, the run-time loader will allow any program to run that has a shared library dependency on any of the listed versions. This flag is only useful when building a shared library (with -shared). |
|
-exact_version |
Sets a flag in the dynamic object produced by the
ld
command that causes the run-time loader to ensure that the shared
libraries the object uses at run time match the shared libraries used
at link time.
This flag is used when building a dynamic executable file (with -call_shared) or a shared library (with -shared). Its use requires more rigorous testing of shared library dependencies. In addition to testing shared libraries for matching versions, timestamps and checksums must also match the timestamps and checksums recorded in shared library dependency records at link time. |
You can use the
odump
command to examine a shared library's versions string, as set by using
the
-set_version
version-string
flag of the
ld
command that created the library.
For example:
%
odump -D
library-name
The value displayed for the IVERSION field is the version string specified when the library was built. If a shared library is built without the -set_version flag, no IVERSION field will be displayed. These shared libraries are handled as if they had been built with the version identifier _null.
When
ld
links a shared object, it records the version of each shared library
dependency. Only the rightmost version identifier in a colon-separated
list is recorded. To examine these dependencies for any shared
executable file or library, use the following command:
%
odump -Dl
shared-object-name
Digital UNIX does not distinguish between major and minor versions of shared libraries.
Major versions are used to distinguish incompatible versions of shared libraries. Minor versions typically distinguish different but compatible versions of a library. Minor versions are often used to provide revision-specific identification or to restrict the use of backward-compatible shared libraries.
Digital UNIX shared libraries use a colon-separated list of version identifiers to provide the versioning features normally attained through minor versions.
The sequence of library revisions that follows illustrates how revision-specific identification can be added to the version list of a shared library without affecting shared library compatibility.
Shared Library | Version |
libminor.so | 3.0 |
libminor.so | 3.1:3.0 |
libminor.so | 3.2:3.1:3.0 |
Each new release of libminor.so adds a new identifier at the beginning of the version list. The new identifier distinguishes the latest revision from its predecessors. Any executable files linked against any revision of libminor.so will record "3.0" as the required version, so no distinction is made between the compatible libraries. The additional version identifiers are only informational.
The sequence of library revisions that follows illustrates how the use of backward-compatible shared libraries can be restricted:
Shared Library | Version |
libminor2.so | 3.0 |
libminor2.so | 3.0:3.1 |
libminor2.so | 3.0:3.1:3.2 |
In this example, programs linked with old versions of libminor2.so can be executed with newer versions of the library, but programs linked with newer versions of libminor2.so cannot be executed with any of the previous versions.
You can implement a binary compatible version of a shared library as a complete, independent object or as a partial object that depends directly or indirectly on a complete, independent object. A fully duplicated shared library takes up more disk space than a partial one, but involves simpler dependency processing and uses less swap space. The reduced disk space requirements are the only advantage of a partial version of a shared library.
A partial shared library includes the minimum subset of modules required to provide backward compatibility for applications linked prior to a binary incompatible change in a newer version of the library. It is linked against one or more earlier versions of the same library that provide the full set of library modules. By this method, you can chain together multiple versions of shared libraries so that any instance of the shared library will indirectly provide the full complement of symbols normally exported by the library.
For example, version "osf.1" of
libxyz.so
includes modules
x.o,
y.o,
and
z.o.
It was built and installed using the following commands:
%
ld -shared -o libxyz.so -set_version osf.1 \
x.o y.o z.o -lc
%
mv libxyz.so /usr/shlib/libxyz.so
If, at some future date,
libxyz.so
requires a binarily incompatible change that affects only module
z.o,
a new version, called "osf.2", and a partial version, still called
"osf.1", can be built as follows:
%
ld -shared -o libxyz.so -set_version osf.2 x.o \
y.o new_z.o -lc
%
mv libxyz.so /usr/shlib/libxyz.so
%
ld -shared -o libxyz.so -set_version osf.1 z.o \
-lxyz -lc
%
mv libxyz.so /usr/shlib/osf.1/libxyz.so
In general, applications are linked with the newest versions of shared libraries. Occasionally, you might need to link an application or shared library with an older, binary compatible version of a shared library. In such a case, use the ld command's -L flag to identify older versions of the shared libraries used by the application.
The linker issues a warning when you link an application with more than one version of the same shared library. In some cases, the multiple version dependencies of an application or shared library will not be noticed until it is loaded for execution.
By default, the ld command tests for multiple version dependencies only for those libraries it is instructed to link against. To identify all possible multiple version dependencies, use the ld command's -transitive_link flag to include indirect shared library dependencies in the link step.
When an application is linked with partial shared libraries, the linker must carefully distinguish dependencies on multiple versions resulting from partial shared library implementations. The linker reports multiple version warnings when it cannot differentiate between acceptable and unacceptable multiple version dependencies.
In some instances, multiple version dependencies might be reported at link time for applications that do not use multiple versions of shared libraries at run time. Consider the libraries and dependencies illustrated in Figure 4-2 and described in the following table.
Library | Version | Dependency | Dependent Version |
libA.so | v1 | libcommon.so | v1 |
libB.so | v2 | libcommon.so | v2 |
libcommon.so | v1:v2 | -- | -- |
Presumably libA.so has been linked against a previous version of libcommon.so. At that time the rightmost version identifier of libcommon.so was "v1". libB.so has been linked against the libcommon.so shown here. Because libcommon.so includes both "v1" and "v2" in its version string, the dependencies of both libA.so and libB.so are satisfied by the one instance of libcommon.so.
When a.out is linked, only libA.so and libB.so are mentioned on the link line. However, the linker examines the dependencies of libA.so and libB.so, recognizes the possible multiple version dependency on libcommon.so, and issues a warning. By linking a.out against libcommon.so as well, you can avoid this false warning.
The loader performs version-matching between the list of versions supported by a shared library and the versions recorded in shared library dependency records. If a shared object is linked with the ld flag -exact_match, the loader also compares the timestamp and checksum of a shared library against the timestamp and checksum values saved in the dependency record.
After mapping in a shared library that fails the version matching test, the loader attempts to locate the correct version of the shared library by continuing to search other directories in RPATH, LD_LIBRARY_PATH, or the default search path.
If all of these directories are searched without finding a matching version, the loader attempts to locate a matching version by appending the version string recorded in the dependency to the directory path at which the first nonmatching version of the library was located.
For example, a shared library
libfoo.so
is loaded in directory
/usr/local/lib
with version "osf.2", but a dependency on this library requires
version "osf.1". The loader attempts to locate the correct version
of the library using a constructed path like the following:
/usr/local/lib/osf.1/libfoo.so
If this constructed path fails to locate the correct library or if no
version of the library is located at any of the default or
user-specified search directories, the loader makes one last attempt to
locate the library by appending the required version string to the
standard system shared library directory
(/usr/shlib).
This last attempt will therefore use a constructed path like the
following:
/usr/shlib/osf.1/libfoo.so
If the loader fails to find a matching version of a shared library, it aborts the load and reports a detailed error message indicating the dependency and shared library version that could not be located.
You can disable version checking for programs that are not installed
with the
setuid
function by setting the loader environment variable as shown in the
following C-shell example:
%
setenv _RLD_ARGS -ignore_all_versions
You can also disable version checking for specific shared libraries as
shown in the following example:
%
setenv _RLD_ARGS -ignore_version libDXm.so
Like the linker, the loader must distinguish between valid and invalid uses of multiple versions of shared libraries:
The following figures illustrate shared object dependencies that will result in multiple dependency errors. Version identifiers are shown in parentheses.
In Figure 4-3, an application uses two layered products that are built with incompatible versions of the base system.
In Figure 4-4, an application is linked with a layered product that was built with an incompatible version of the base system.
In Figure 4-5, an application is linked with an incomplete set of backward compatible libraries that are implemented as partial shared libraries.
The following figures show valid uses of multiple versions of shared libraries.
In Figure 4-6, an application uses a backward-compatibility library implemented as a partial shared library.
In Figure 4-7, an application uses two backward compatibile libraries, one of which depends on the other.
The loader can resolve symbols using either deferred or immediate binding. Immediate binding requires that all symbols be resolved when an executable program or shared library is loaded. Deferred ("lazy") binding allows text symbols to be resolved at run time. A lazy text symbol is resolved the first time that a reference is made to it in a program.
By default, programs are loaded with deferred binding. Setting the LD_BIND_NOW environment variable to a non-null value selects immediate binding for subsequent program invocations.
Immediate binding can be useful to identify unresolvable symbols. With deferred binding in effect, unresolvable symbols might not be detected until a particular code path is executed.
Immediate binding can also reduce symbol-resolution overhead. Run-time symbol resolution is more expensive per symbol than load-time symbol resolution.
The use of shared libraries is subject to the following restrictions:
Shared libraries should be explicitly linked with other shared libraries that define the symbols they refer to.
In certain cases, such as a shared library that refers to symbols in an executable file, it is difficult to avoid references to undefined symbols. See Section 4.2.4 for a discussion on how to handle unresolved external symbols in a shared library.
C modules compiled with the Digital UNIX C compiler at optimization level O2 or less will work with shared libraries. Executable programs linked with shared libraries can be compiled at optimization level O3 or less.