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:
ld
command with the appropriate options. You create archive libraries by
using the
ar
command. For more information on the
ld
command, see the
ld
(1)
reference page.
/sbin/loader
)
assigns a location in the process's private virtual address space. In
contrast, when archive libraries are linked into an executable program,
they have a fixed location in the process's private virtual address
space.
/usr/shlib
directory. Archive libraries reside in the
/usr/lib
directory.
lib
and ends with the suffix
.so
.
For example, the library containing common C language functions is
libc.so
.
Archive library names also begin with the prefix
lib
,
but end with the suffix
.a
.
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:
ld
)
/sbin/loader
)
ld
command to determine behavior regarding unresolved external symbols
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.
/usr/shlib
/usr/ccs/lib
/usr/lib/cmplrs/cc
/usr/lib
/usr/local/lib
/var/shlib
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:
-rpath
string
option to the
ld
command and setting
string
to the list of directories to be searched.
LD_LIBRARY_PATH
to point to the directory in which you keep your private shared
libraries before executing your programs. The run-time loader
examines this variable when the program is executed; if it is set, the
loader searches the paths it defines before searching the list of
directories discussed in
Section 4.2.1.
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.
.login
or
.cshrc
file if you work in the C shell:
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:
-expect_unresolved pattern
pattern
are neither displayed nor treated as warnings or errors. This flag can
occur multiple times on a link command line. The patterns use shell
wildcards
(?
,
*
,
[
,
]
)
and must be quoted
properly to prevent expansion by the shell. See
sh
(1),
csh
(1),
and
ksh
(1)
for more information.
-warning_unresolved
-expect_unresolved
pattern produce warning messages. This mode is the default for linking
shared libraries.
-error_unresolved
-expect_unresolved
pattern. This mode is the default for linking executable images.
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:
/usr/shlib
directory must be mounted to provide access to 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
libcommon.so
by using the
ld
command:
%
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
libcommon.so
into a directory pointed to by
LD_LIBRARY_PATH
,
if it is not already in that directory.
user
,
db
,
and
admin
).
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:
fixso
utility on the changed objects. See the
fixso
(1)
reference page for additional information.)
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:
ld
flags
-warning_unresolved
and
-expect_unresolved
are not used when the executable program is linked. Fix any
"unresolved symbol" errors that occur when the executable program
is linked.
-transitive_link
to the
ld
flags used when the executable program is built.
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:
fixso
to repair shared libraries in bottom-up order.
so_locations
file for registering unique addresses for shared libraries.
fixso
where to find a compatible version of the offending shared library.
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
|
|
-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
|
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.
O3
might not work with shared libraries.
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.
setuid
or
setgid
subroutines do not use the settings of the various environment
variables that govern library searches (such as
LD_LIBRARY_PATH
,
_RLD_ARGS
,
_RLD_LIST
,
and
_RLD_ROOT
);
they use only system-installed libraries (that is, those in
/usr/shlib
).
This restriction prevents potential threats to the security of these
programs, and it is enforced by the run-time loader
(/sbin/loader
).