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 addresses the following topics:
Overview of shared libraries (Section 4.1)
Resolving symbols (Section 4.2)
Linking with shared libraries (Section 4.3)
Turning off shared libraries (Section 4.4)
Creating shared libraries (Section 4.5)
Working with private shared libraries (Section 4.6)
Using quickstart (Section 4.7)
Debugging programs linked with shared libraries (Section 4.8)
Loading a shared library at run time (Section 4.9)
Protecting shared library files (Section 4.10)
Shared library versioning (Section 4.11)
Symbol binding (Section 4.12)
Shared library restrictions (Section 4.13)
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:
Programs linked with shared libraries do not need to be recompiled and relinked when changes are made to those libraries.
Unlike programs linked with archive libraries, programs linked with shared libraries do not include library routines in the executable program file. Programs linked with shared libraries include information to load the shared library and gain access to its routines and data at load time.
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:
You build shared libraries by using the
ld
command with the appropriate options.
You create archive libraries by using
the
ar
command.
For more information on the
ld
command, see
ld
(1)
When shared libraries are linked into an executable program,
they can be positioned at any available address.
At run time, the loader (/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.
Shared libraries reside in the
/usr/shlib
directory.
Archive libraries reside in the
/usr/lib
directory.
Shared library
naming convention specifies that a shared library name begins with the prefix
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
they end with the suffix
.a
.
Figure 4-1
shows the difference between the use
of archive and shared libraries.
Figure 4-1: 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:
Search path of the linker (ld
) (Section 4.2.1)
Search path of the run-time loader (/sbin/loader
) (Section 4.2.2)
Name resolution (Section 4.2.3)
Options to the
ld
command to determine
how unresolved external symbols are to be handled (Section 4.2.4)
4.2.1 Search Path of the Linker
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 on the
ld
command.
4.2.2 Search Path of the Run-time Loader
Unless otherwise directed, the run-time loader (/sbin/loader
) follows the same search path as the linker.
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:
Specify a directory path by using the
-rpath
option to the
ld
command
and setting
string
to the list of directories to
be searched.
Set the environment variable
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
defined by
LD_LIBRARY_PATH
before searching the list of
directories discussed in
Section 4.2.1.
You can set the
LD_LIBRARY_PATH
variable by either
of the following methods:
Set it as an environment variable at the shell prompt.
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.
Add the definition of the variable to your login or shell
startup files.
For example, you could add the following line to your
.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
Section 4.2.1.
In addition,
you can use the
_RLD_ROOT
environment variable to alter
the search path of the run-time loader.
For more information, see
loader
(5)4.2.3 Name Resolution
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 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 was 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 unresolved symbols are handled by the system, see Section 4.2.4. The following sequence resolves references to any symbol from the main program or from a library:
If a symbol is defined in an object or in an archive library from which you build the main executable program file, that symbol is used by the main program file and all of the shared libraries that it uses.
If the symbol is not defined by the preceding step and is defined by one or more of the shared objects linked with the executable program, then the leftmost library on the link command line containing a definition is used.
If the libraries on the link command line were linked to be dependent on other libraries, then the dependencies of libraries are searched in a breadth-first fashion instead of being searched in a depth-first fashion. For example, as shown in the following diagram, executable program A is linked against shared library B and shared library D, and library B is linked against library C.
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.
If the symbol is not resolved in any of the previous steps, the symbol remains unresolved.
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.
4.2.4 Options to Determine Handling of Unresolved External Symbols
The default behavior of the linker when building executable programs differs from its default behavior when building shared libraries:
Executable programs When building executable programs, an unresolved symbol produces an error by default (-error_unresolved option). The link fails and the output file is not marked as executable.
Shared libraries When building shared libraries, an unresolved symbol produces only a warning message by default (-warning_unresolved option).
When building shared libraries and
-error_unresolved
is specified, an output file (.so
file) is produced even
if unresolved symbols are present, overwriting any pre-existing
.so
file with the same name.
See the
Programmer's Guide
for information
about how to control overwriting of linker output files.
You can control how the linker handles unresolved symbols by using the
following options to the
ld
command:
-expect_unresolved
pattern
-warning_unresolved
-error_unresolved
See
ld
(1)4.3 Linking with Shared Libraries
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 options to the
cc
command to allow flexibility in determining the search path for
a shared library.
For example, you can use the
-Ldir
option with the
cc
command to
change the search path by adding
dir
before the
default directories, as shown 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
option first with no arguments.
Then, specify it again with the directory
to search, followed by the
-l
option 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
must be in the
/usr/person
directory.
4.4 Turning Off Shared Libraries
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
option 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:
Applications that need to run in single-user mode cannot be
linked with shared libraries because the
/usr/shlib
directory
must be mounted to provide access to shared libraries.
Applications whose sole purpose is single-user benchmarks should not be linked with shared libraries.
You create shared libraries by using the
ld
command
with the
-shared
option.
You can create shared libraries
from object files or from existing archive libraries.
4.5.1 Creating Shared Libraries from Object Files
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
option tells the linker to resolve
symbols using only shared libraries.
The
-lc
option 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.
4.5.2 Creating Shared Libraries from Archive Libraries
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
option tells the linker
to link all the objects from the archive library
old.a
.
The
-none
option tells the linker to turn off the
-all
option.
Note that the
-no_archive
option
applies to the resolution of the
-lc
option but not to
old.a
(because
old.a
is explicitly mentioned).
4.6 Working with Private Shared Libraries
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, follow these steps:
Compile each C file that will be part of the library:
% cc -c io_util.c % cc -c defines.c % cc -c network.c
Create the shared library
libcommon.so
by using the
ld
command:
% ld -shared -no_archive \ -o libcommon.so io_util.o defines.o network.o -lc
Compile each C file that will be part of the application:
% 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
Copy
libcommon.so
into a directory pointed
to by
LD_LIBRARY_PATH
, if it is not already in that directory.
Run each compiled program (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 that 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.
To use quickstart, an object must meet the following conditions:
The object's actual run-time memory location must match the quickstart location. The run-time loader tries to use the quickstart location. However, if another library is already occupying that spot, the object will not be able to use it.
All depended-upon objects must be quickstarted.
All depended-upon objects must be unchanged since they were
linked.
If objects have changed, addresses of functions within the library
might have moved or new symbols might have been introduced that can affect
the loading.
(Note that you might still be able to quickstart objects that
have been modified since linking by running the
fixso
utility
on the changed objects.
See
fixso
(1)
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, give a unique quickstart address to each shared library that you build 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
option 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 the following 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
option 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 systemwide
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
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.
Specify all depended-upon libraries on the link line.
After
all the libraries are built, you can then build your applications.
4.7.1 Verifying That an Object Is Quickstarting
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.
4.7.2 Manually Tracking Down Quickstart Problems
To determine why an executable program
is not quickstarting, you can use the
fixso
utility, 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.
The executable program must be able to be quickstarted.
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:
The executable program was linked with unresolved symbols.
Make sure that the
ld
options
-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.
The executable program is not linked directly against all
of the libraries that it uses at run time.
Add the option
-transitive_link
to the
ld
options used when the executable program
is built.
The executable program's dependencies must be able to be quickstarted. Get a list of an executable program's dependencies. For example:
% 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 timestamp and checksum information must match for all dependencies.
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 shown 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
.
Each of the executable program's dependencies must also contain dependency lists with matching timestamp and checksum information.
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.
4.7.3 Tracking Down Quickstart Problems with the fixso Utility
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 program or shared library depends on other shared libraries
that cannot be quickstarted.
This restriction can be avoided by using
fixso
to repair shared libraries in bottom-up order.
New name conflicts are introduced after a program or shared library is created. Name conflicts result when the same global symbol name is exported by two or more shared library dependencies or by the program and one of its shared library dependencies.
The program's shared library dependencies are not all loaded
at their quickstart locations.
A shared library cannot be loaded at its quickstart
locations if other shared libraries are loaded at that location and are already
in use.
This rule applies systemwide, not just to individual processes.
To
avoid this restriction, use a common
so_locations
file
for registering unique addresses for shared libraries.
The program or shared library depends on an incompatible version
of another shared library.
This restriction can be avoided by instructing
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
option 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 you can use
fixso
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
option 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
4.8 Debugging Programs Linked with Shared Libraries
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.
See
Chapter 5
for more
information about using
dbx
.
4.9 Loading a Shared Library at Run Time
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 was 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
4.10 Protecting Shared Library Files
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:
The library's permissions are set to 600.
The other processes do not own the library or are not running with their UID set to the owner of that library.
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
options to put the data section in the same 8-MB 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 to
avoid any unauthorized sharing, enter the following line in the configuration
file:
segmentation 0
4.11 Shared Library Versioning
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.
4.11.1 Binary Incompatible Modifications
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 incompatible changes that might occur in shared libraries are the following:
Removal of documented interfaces
For example, if the
malloc()
function in
libc.so
was replaced with a function called (_
), programs that depend on the older function would fail due to
the missing
malloc
symbol.
Modification of documented interfaces
For example, if a second argument to the
malloc()
function in
libc.so
was 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.
Modification of global data definitions
For example, if the type of the
errno
symbol in
libc.so
was 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.
4.11.2 Shared Library Versions
You can maintain the backward compatibility of a shared library affected by 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 an 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 the
osf.1
version.
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 the
osf.1
version 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 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 options used
to effect version control of shared libraries.
Table 4-1: Linker Options That Control Shared Library Versioning
Option | 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 option, 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 option is only useful when building a
shared library (with
-shared ). |
-exact_version |
Sets an option 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 option 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
option 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
option, 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
4.11.3 Major and Minor Versions Identifiers
Tru64 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.
Tru64 UNIX shared libraries use a colon-separated list of version identifiers to provide the versioning features normally attained through minor versions.
The following sequence of library revisions shows 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 following sequence of library revisions shows 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.
4.11.4 Full and Partial Versions of Shared Libraries
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 an 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
4.11.5 Linking with Multiple Versions of Shared Libraries
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
option 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
option 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 shown in
Figure 4-2
and described in the following table.
Figure 4-2: Linking with Multiple Versions of Shared Libraries
Library | Version | Dependency | Dependent Version |
libA.so |
v1 | libcommon.so |
v1 |
libB.so |
v2 | libcommon.so |
v2 |
libcommon.so |
v1:v2 |
|
|
In the preceding table,
libA.so
was linked against
a version of
libcommon.so
that had a rightmost version
identifier of v1.
Unlike
libA.so
,
libB.so
was linked against a version of
libcommon.so
that had a
rightmost version identifier of v2.
Because the
libcommon.so
shown in the table 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 command 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.
4.11.6 Version Checking at Load Time
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
-exact_match
option on the link command line, 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
4.11.7 Multiple Version Checking at Load Time
Like the linker, the loader must distinguish between valid and invalid uses of multiple versions of shared libraries:
Valid uses of multiple versions occur when partial shared libraries that depend on other versions of the same libraries are loaded. In some cases, these partial shared libraries depend on different partial shared libraries, and the result can be complicated dependency relationships that the loader must interpret carefully to avoid reporting false errors.
Invalid uses of multiple versions occur when two different shared objects depend on different versions of another shared object. Partial shared library chains are an exception to this rule. For version-checking purposes, the first partial shared library in a chain defines a set of dependencies that overide similar dependencies in other members of the chain.
The following figures show 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.
Figure 4-3: Invalid Multiple Version Dependencies Among Shared Objects: Example 1
In
Figure 4-4, an application is linked with
a layered product that was built with an incompatible version of the base
system.
Figure 4-4: Invalid Multiple Version Dependencies Among Shared Objects: Example 2
In
Figure 4-5, an application is linked with
an incomplete set of backward-compatible libraries that are implemented as
partial shared libraries.
Figure 4-5: Invalid Multiple Version Dependencies Among Shared Objects: Example 3
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.
Figure 4-6: Valid Uses of Multiple Versions of Shared Libraries: Example 1
In
Figure 4-7, an application uses two backward-compatible
libraries, one of which depends on the other.
Figure 4-7: Valid Uses of Multiple Versions of Shared Libraries: Example 2
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.
4.13 Shared Library Restrictions
The use of shared libraries is subject to the following restrictions:
Shared libraries should not have any undefined symbols.
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.
Certain files (such as assembler files, older object files,
and C files) that were optimized at level
O3
might not
work with shared libraries.
C modules compiled with the Tru64 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.
Programs that are installed using the
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
).