|
Many libraries come with HP-UX. You can also create and use your own
libraries on HP-UX. This chapter discusses the following
topics:
|
Overview of Shared and Archive Libraries |
HP-UX supports two kinds of libraries: archive and
shared. A shared library is also called a dll
(dynamically linked library). Archive libraries are the more traditional
of the two, but use of shared libraries has increased dramatically, and
is the preferred method. The following table summarizes differences between
archive and shared libraries.
Comparing |
Archive |
Shared (or dll) |
file name suffix |
Suffix is .a . |
Suffix is .sl or .<version> on PA systems and .so
or .so.<version>
on Itanium-based systems, where version is the version number of the library. |
creation |
Combine object files with the ar command |
Combine object files with the ld command |
address binding |
Addresses of library subroutines and data are resolved at link time. |
Addresses of library subroutines are bound at run time. Addresses of
data in a.out are bound at link time; addresses of data in
shared libraries are bound at run time. |
a.out files
|
Contains all library routines or data (external references) referenced
in the program. An a.out file that does not use shared libraries
is known as a complete executable. |
Does not contain library routines; instead, contains a linkage
table that is filled in with the addresses of routines and shared
library data. An a.out that uses shared libraries is known
as an incomplete executable, and is almost always much
smaller than a complete executable. |
run time |
Each program has its own copy of archive library routines. |
Shared library routines are shared among all processes that use the
library. |
In Itanium-based systems, some of the system libraries are available both as a shared library and as
an archive library for 32-bit executables in the directory /usr/lib/hpux32/
and for 64-bit executables in /usr/lib/hpux64/ . Archive library
file names end with .a whereas shared library file names
end with .so . For example, for 32-bit executables, the archive
math library libm is /usr/lib/hpux32/libm.a
and the shared version is /usr/lib/hpux32/libm.so . For 64-bit
executables, the archive math library libm is /usr/lib/hpux64/libm.a
and the shared version is /usr/lib/hpux64/libm.so
If both shared and archived versions of a library exist, ld
uses the one that it finds first in the default library search path. If
both versions exist in the same directory, ld uses the shared
version. For example, compiling the C program prog.c causes
cc to invoke the linker with a command like this:
The -lc option instructs the linker to search the C library,
hpux32/libc or hpux64/libc , to resolve unsatisfied
references from prog.o . If a shared libc exists
(/usr/lib/hpux32/libc.so or /usr/lib/hpux64/libc.so ),
ld uses it instead of the archive libc (/usr/lib/hpux32/libc.a
or /usr/lib/hpux64/libc.a ). You can, however, override this
behavior and select the archive version of a library with the -a
option or the -l: option. These are described in Choosing
Archive or Shared Libraries with -a and Specifying
Libraries with -l and l: .
In addition to the system libraries provided on HP-UX, you can create
your own archive and shared libraries. To create archive libraries, combine
object files with the ar command, as described in Overview
of Creating an Archive Library . To create shared libraries, use ld
to combine object files as described in Creating
Shared Libraries.
For more information, see Caution
When Mixing Shared and Archive Libraries .
|
What are Archive Libraries? |
An archive library contains one or more object files,
and is created with the ar command. When linking an object
file with an archive library, ld searches the library for
global definitions that match with external references in the object
file. If a match is found, ld copies the object file containing
the global definition from the library into the a.out file.
In short, any routines or data a program needs from the library are copied
into the resulting a.out file.
Example
For example, suppose you write a C program that calls printf
from the libc library. Figure 6: Linking
with an Archive Library shows how the resulting a.out
file looks if you linked the program with the archive version of
libc .
Figure 6: Linking with an Archive Library
|
What are Shared Libraries? |
Like an archive library, a shared library contains
object code. However, ld treats shared libraries quite differently
from archive libraries. When linking an object file with a shared library,
ld does not copy object code from the library into the a.out
file; instead, the linker simply notes in the a.out file
that the code calls a routine in the shared library. An a.out
file that calls routines in a shared library is known as an incomplete
executable.
The Dynamic Loader
When an incomplete executable begins execution, the HP-UX dynamic
loader looks at the a.out file to see what libraries
the a.out file needs during execution.
The kernel activates the dynamic loader for an a.out .The
dynamic loader then loads and maps any required shared libraries into
the process's address space - known as attaching the
libraries. A program calls shared library routines indirectly through
a linkage table that the dynamic loader fills in with
the addresses of the routines. By default, the dynamic loader places the
addresses of shared library routines in the linkage table as the routines
are called - known as deferred binding. Immediate
binding is also possible - that is, binding all required symbols
in the shared library at program startup. In either case, any routines
that are already loaded are shared.
Consequently, linking with shared libraries generally results in smaller
a.out files than linking with archive libraries. Therefore,
a clear benefit of using shared libraries is that it can reduce disk space
and virtual memory requirements.
Note In prior releases, data defined by a shared library was copied into
the program file at link time. All references to this data, both in the
libraries and in the program file, referred to the copy in the executable
file.
With the HP-UX 10.0 release, however, this data copying is eliminated.
Data is accessed in the shared library itself. The code in the executable
file references the shared library data indirectly through a linkage pointer,
in the same way that a shared library references the data.
Default Behavior When Searching for
Libraries at Run Time
If the dynamic loader cannot find a shared library from
the list by default, it generates a run-time error and the program aborts. In PA-32
compatibility mode (with +compat), for example, suppose that during development,
a program is linked with the shared library liblocal.so in
your current working directory (say, /users/hyperturbo ):
$ ld /usr/lib/hpux32/crt0.o prog.o -lc liblocal.so
In PA-32 mode, the linker records the path name of liblocal.so
in the a.out file as /users/hyperturbo/liblocal.so .
When shipping this application to users, you must ensure that (1) they
have a copy of liblocal.so on their system, and (2) it is
in the same location as it was when you linked the final application.
Otherwise, when the users of your application run it, the dynamic loader
looks for /users/hyperturbo/liblocal.so , fail to find
it, and the program aborts.
In default mode, the linker records ./liblocal.so .
This is more of a concern with non-standard libraries-that is, libraries
not found in /usr/lib/hpux32 or /usr/lib/hpux64 .
There is little chance of the standard libraries not being in these directories.
Caution on Using Dynamic Library Searching
If different versions of a library exist on your system, be aware that
the dynamic loader may get the wrong version of the library when dynamic
library searching is enabled with SHLIB_PATH or +b .
For instance, you may want a program to use the PA1.1 libraries found
in the /usr/lib/pa1.1 directory; but through a combination
of SHLIB_PATH settings and +b options, the dynamic
loader ends up loading versions found in /usr/lib instead.
If this happens, make sure that SHLIB_PATH and +b
are set to avoid such conflicts.
Running setuid Programs
If SHLIB_PATH and LD_LIBRARY_PATH are set for setuid programs, the loader validates the contents of
the environment variables SHLIB_PATH and LD_LIBRARY_PATH against the contents of the configuration
file /etc/dld.sl.conf. The loader validates the contents only if:
- The conf file is present
- The conf file fulfils the following conditions:
- The superuser owns the conf file.
- Appropriate permissions are granted, that is, the conf file is writable only by the superuser.
- The conf file contains a list of absolute shared library search paths.
- Spaces are not present in the paths listed. The loader ignores everything on a line that
follows a space or # character. You can use the # character to comment out paths in the conf file.
If SHLIB_PATH / LD_LIBRARY_PATH contains any of the paths listed in the conf file,
the loader uses the paths in the order they are specified in the environment variables. If the contents
of the environment variables and the contents of the conf file do not overlap, the dynamic path lookup
is disabled and the search is restricted to the embedded path (RPATH).
You can turn off this feature by setting the environment variable _HP_DLDOPTS to -no_setuidpath.
If you turn off this feature, setuid programs do not carry out dynamic path searching.
Note The configuration file /etc/dld.sl.conf must contain a list of shared
library search paths (delimited by either colon or newline characters).
Any relative paths (paths not starting with /) in the path list is ignored by the loader.
To avoid redundant searches, ensure that a path appears only once in the file and only
once in the option SHLIB_PATH/LD_LIBRARY_PATH.
For PA32, this feature is enabled only if the /etc/dld.sl.conf file is present. The default
behavior of honoring these environment variables for setuid programs remains unchanged.
|
Example Program Comparing
Shared and Archive Libraries |
As an example, suppose two separate programs, prog1 and
prog2 , use shared libc routines heavily. Suppose
that the a.out portion of prog1 is 256Kb in
size, while the a.out portion of prog.2 is 128Kb.
Assume also that the shared libc is 512Kb in size. Figure
7: Two Processes Sharing libc shows how physical memory might look
when both processes run simultaneously. Notice that one copy of libc
is shared by both processes. The total memory requirement for these two
processes running simultaneously is 896Kb (256Kb + 128Kb + 512Kb).
Figure 7: Two Processes Sharing libc
Compare this with the memory requirements if prog1 and
prog2 are linked with the archive version of libc .
As shown in Figure 8: Two Processes with
Their Own Copies of libc , 1428Kb of memory is required (768Kb +
640Kb). The numbers in this example are made up, but it is true in general
that shared libraries reduce memory requirements.
Figure 8: Two Processes with Their Own Copies
of libc
|
Shared Libraries with Debuggers, Profilers,
and Static Analysis |
Debugging of shared libraries is supported by the by the WDB Debugger.
See the WDB documentation at: http://www.hp.com/go/wdb
Profiling Shared Libraries with gprof(1)
The gprof tool produces an execution profile about
an executable for application programs and shared libraries. See gprof(1)
for more information.
Use the following steps to profile a shared library:
You can only profile a single
shared library.
You can profile either the application or a shared
library. However, you cannot profile both the application and the shared
library together.
To profile applications, continue to use the existing
gprof method, in which you compile with -G option, but do
not set LD_PROFILE.
For example, to profile the shared library test.sl :
The following are the limitations for profiling shared
library using gprof:
|
Creating Archive Libraries |
Compile one or more source files to create object
files containing relocatable object code.
Combine these object files into a single archive
library file with the ar command.
Following are the commands you can use to create an archive library
called libunits.a :
$ cc -Aa -c length.c volume.c mass.c
$ ar r libunits.a length.o volume.o mass.o
These steps are described in detail in Overview
of Creating an Archive Library .
Other topics relevant to archive libraries are:
Overview of Creating an Archive
Library
To create an archive library:
Create one or more object files containing relocatable
object code. Typically, each object file contains one function, procedure,
or data structure, but an object file can have multiple routines and
data.
-
Combine these object files into a single archive library file with
the ar command. Invoke ar with the r
key.
("Keys" are like command line options, except that they
do not require a preceding - .)
Figure 9: Creating an Archive Library summarizes
the procedure for creating archive libraries from three C source files
(file1.c , file2.c , and file3.c ).
The process is identical for other languages, except that you use
a different compiler.
Figure 9: Creating an Archive Library
Contents of an Archive File
An archive library file consists of four main components:
A header string, "!<arch>\n ",
identifying the file as an archive file created by ar (\n
represents the newline character)
A symbol table, used by the linker
and other commands to find the location, size, and other information for
each routine or data item contained in the library
An optional string table used
by the linker to store file names that are greater than 15 bytes long
(only created if a long file name is encountered)
Object modules,
one for each object file specified on the ar command line
To see what object modules a library contains, run ar with
the t key, which displays a table of contents.
For example, to view the "table of contents" for libm.a :
$ ar t /usr/lib/hpux32/libm.a //Run ar with the t key.
acosh.o //Object modules are displayed.
erf.o
fabs.o
. . . .
This indicates that the library was built from object files named acosh.o ,
erf.o , fabs.o , and so forth. In other words,
module names are the same as the names of the object files from which
they were created.
Example of Creating an Archive Library
Suppose you are working on a program that does several conversions between
English and Metric units. The routines that do the conversions are contained
in three C-language files. Following are the three C-language files:
length.c - Routine to Convert Length
Units
float in_to_cm(float in) /* convert inches to centimeters */
{
return (in * 2.54);
}
volume.c - Routine to Convert Volume
Units
float gal_to_l(float gal) /* convert gallons to liters */
{
return (gal * 3.79);
}
mass.c - Routine to Convert Mass
Units
float oz_to_g(float oz) /* convert ounces to grams */
{
return (oz * 28.35);
}
During development, each routine is stored in a separate file. To make
the routines easily accessible to other programmers, they must be stored
in an archive library. To do this, first compile the source files, either
separately or together on the same command line:
$ cc -Aa -c length.c volume.c mass.c Compile them together.
length.c:
volume.c:
mass.c:
$ ls *.o List the .o files.
length.o mass.o volume.o
Then combine the .o files by running ar with
the r key, followed by the library name (say libunits.a ),
followed by the names of the object files to place in the library:
$ ar r libunits.a length.o volume.o mass.o
ar: creating libunits.a
To verify that ar created the library correctly, view its
contents:
$ ar t libunits.a Use ar with the t key.
length.o
volume.o
mass.o All the .o modules are included; it worked.
Now suppose you have written a program, called convert.c ,
which calls several of the routines in the libunits.a library.
You can compile the main program and link it to libunits.a
with the following cc command:
$ cc -Aa convert.c libunits.a
Note that the whole library name was given, and the -l
option was not specified. This is because the library was in the current
directory. If you move libunits.a to /usr/lib/hpux32
(IPF 32-bit mode) or /usr/lib (PA32 mode) before compiling,
the following command line works instead:
$ cc -Aa convert.c -lunits
Linking with archive libraries is covered in detail in Linker
Tasks.
Replacing, Adding, and Deleting
an Object Module
Occasionally you may want to replace an object module in a library,
add an object module to a library, or delete a module completely. For
instance, suppose you add some new conversion routines to length.c
(defined in the previous section), and want to include the new routines
in the library libunits.a . You must then replace
the length.o module in libunits.a .
Replacing or Adding an Object Module
To replace or add an object module, use the r key (the
same key you use to create a library). For example, to replace the length.o
object module in libunits.a :
$ ar r libunits.a length.o
Deleting an Object Module
To delete an object module from a library, use the d key.
For example, to delete volume.o from libunits.a :
$ ar d libunits.a volume.o Delete volume.o.
$ ar t libunits.a List the contents.
length.o mass.o volume.o is gone.
Summary of Keys to the ar(1) Command
When used to create and manage archive libraries, the syntax of ar is:
ar [-] keys archive [modules] ...
IN the syntax, archive is the name of the archive
library, modules is an optional list of object modules or files.
See ar(1) for the complete list of keys and options.
Useful ar Keys
Here are some useful ar keys and their modifiers:
- key
-
Description
d
-
D elete the modules
from the archive.
r
-
Replace or add the
modules to the archive. If archive exists,
ar replaces modules specified on the command line.
If archive does not exist, ar creates a new archive
containing the modules.
t
-
Display a table of contents for the archive.
u
-
Used with the r , this modifier tells ar
to replace only those modules with creation dates later than
those in the archive.
v
-
Display verbose output.
x
-
Extracts object modules from the library. Extracted
modules are placed in .o files in the current directory.
Once an object module is extracted, you can use nm to view
the symbols in the module.
For example, when used with the v flag, the t
flag creates a verbose table of contents - including such information
as module creation date and file size:
$ ar tv libunits.a
rw-rw-rw- 265/ 20 230 Feb 2 17:19 1990 length.o
rw-rw-rw- 265/ 20 228 Feb 2 16:25 1990 mass.o
rw-rw-rw- 265/ 20 230 Feb 2 16:24 1990 volume.o
The next example replaces length.o in libunits.a ,
only if length.o is more recent than the one already
contained in libunits.a :
$ ar ru libunits.a length.o
crt0.o
The crt0.o startup file is not needed for shared bound
links because dld.so does some of the startup duties previously
done by crt0.o . However, you still need to include crt0.o
on the link line for all fully archive links (ld -noshared ).
In PA-32 mode, crt0.o must always be included on the link
line.
Users who link by letting the compilers such as cc invoke
the linker do not have to include crt0.o on the link line.
Archive Library Location (IPF)
After creating an archive library, you can save it
in a location that is easily accessible to other programmers who want to use it.
The main choices for places to put the library are in
the 32-bit /usr/lib/hpux32 or 64-bit /user/lib/hpux64
directory
Using /usr/lib/hpux32 and /usr/lib/hpux64
Because the linker searches /usr/lib/hpux32 or /usr/lib/hpux64
by default, you may put your archive libraries in /usr/lib/hpux32 or
/usr/lib/hpux64 and thereby eliminate the task of entering the entire library path name each time
you compile or link.
The drawbacks of putting the libraries in /usr/lib/hpux32
or /usr/lib/hpux64 are:
Check with your system administrator before attempting to use /usr/lib/pux32
or /usr/lib/hpux64 .
Archive Library Location
After creating an archive library, you can save it in a
location that is easily accessible to other programmers
who may want to use it. There are two main choices for places to put
the library:
Using /usr/lib
and /usr/lib/pa20_64
Because the linker searches /usr/lib
or /usr/lib/pa20_64 by default, you may put your
archive libraries in /usr/lib
or /usr/lib/pa20_64 and thereby eliminate the task of entering the
entire library path name each time you compile or link.
The drawbacks of putting the libraries in /usr/lib
or /usr/lib/pa20_64 are:
Check with your system administrator before attempting
to use /usr/lib or /usr/lib/pa20_64 .
Using /usr/local/lib
or /usr/contrib/lib
The /usr/local/lib and /usr/local/lib/pa20_64
library typically contain libraries created locally - by programmers on
the system; /usr/contrib/lib and /usr/contrib/lib/pa20_64
contain libraries supplied with HP-UX but not supported by HP.
Programmers can create their own libraries for both 32-bit and 64-bit
code using the same library name. Although ld does not automatically
search these directories, they are still the best choice for locating
user-defined libraries because the directories are not write-protected.
Therefore, programmers can store the libraries in these directories without
super-user privileges. Use -L/usr/local/lib or -L/usr/contrib/lib
for 32-bit libraries, or -L/usr/local/lib/pa20_64 or -L/usr/contrib/lib/pa20_64
for 64-bit libraries to tell the linker to search these directories.
|
Creating Shared Libraries |
Two steps are required to create a shared library:
Compile one or more source files to create object
files. In PA-32 mode, it is necessary to use the +Z compiler option to create position-independent code.
Creating the Shared
Library with ld by linking with -b .
Following are the commands you can use to create a shared library
called libunits.so :
$ cc -Aa -c +z length.c volume.c mass.c
$ ld -b -o libunits.so length.o volume.o mass.o
Other topics relevant to shared libraries are:
Creating Position-Independent Code
(PIC)
In PA-32 mode, the first step in creating a shared library is to create
object files containing position-independent code (PIC).
There are two ways to create PIC object files:
In PA-32 mode, the +z and +Z options force
the compiler to generate PIC object files. In PA-64 and IPF mode, the
+Z option is the default.
Example Using +z
Suppose you have some C functions, stored in length.c ,
that convert between English and Metric length units. To compile these
routines and create PIC object files with the C compiler, you can use
this command:
$ cc -Aa -c +z length.c The +z option creates PIC.
You can then link it with other PIC object files to create a shared
library, as discussed in Creating the Shared
Library with ld .
Comparing +z and +Z
In PA-32 mode, the +z and +Z options are essentially
the same. Normally, you compile with +z . However, in some
instances - when the number of referenced symbols per shared library exceeds
a predetermined limit - you must recompile with the +Z option
instead. In this situation, the linker displays an error message and tells
you to recompile the library with +Z .
In PA-64 and IPF mode, +Z is the default and the compilers
ignore the options and generate PIC code.
Compiler Support for +z and +Z
In PA-32 mode, the C, C++, FORTRAN, and Pascal compilers support the
+z and +Z options.
In PA-64 and IPF mode, +Z is the default for the C and
C++ compilers.
Creating the Shared Library with
ld
To create a shared library from one or more PIC object files, use the
linker, ld , with the -b option. By default,
ld names the library a.out . You can change
the name with the -o option.
For example, suppose you have three C source files containing routines
to do length, volume, and mass unit conversions. They are named length.c ,
volume.c , and mass.c , respectively. To make
a shared library from these source files, first compile all three files,
then combine the resulting .o files with ld .
Following are the commands you can use to create a shared library
named libunits.so :
$ cc -Aa -c +z length.c volume.c mass.c
length.c:
volume.c:
mass.c:
$ ld -b -o libunits.so length.o volume.o mass.o
Once the library is created, ensure that it has read and execute permissions
for all users who use the library. For example, the following chmod
command allows read/execute permission for all users of the libunits.so
library:
$ chmod +r+x libunits.so
This library can now be linked with other programs. For example, if
you have a C program named convert.c that calls routines
from libunits.so , you can compile and link it with the
cc command:
$ cc -Aa convert.c libunits.so
In PA-32 mode, once the executable is created, the library must not
be moved because the absolute path name of the library is stored in the
executable. (In PA-64 and IPF mode, ./libunit.so is stored
in the executable.) For details, see Shared
Library Location .
For details on linking shared libraries with your programs, see Linker
Tasks.
Shared Library Dependencies
You can specify additional shared libraries on the ld command
line when creating a shared library. The created shared library is said
to have a dependency on the specified libraries, and
these libraries are known as dependent libraries or supporting
libraries. When you load a library with dependencies, all its
dependent libraries are loaded too. For example, suppose you create a
library named libdep.so using the command:
$ ld -b -o libdep.so mod1.o mod2.o -lcurses -lcustom
Thereafter, any programs that load libdep.so - either explicitly
with dlopen or shl_load or implicitly with the
dynamic loader when the program begins execution - also automatically
load the dependent libraries libcurses.so and libcustom.so .
There are two additional issues that may be important to some shared
library developers:
When a shared library with dependencies is loaded,
in what order are the dependent libraries loaded?
Where are all the dependent libraries placed in
relation to other already loaded libraries? That is, where are they placed
in the process's shared library search list used by the dynamic loader?
The Order in Which Libraries
are Loaded (Load Graph)
When a shared library with dependencies is loaded, the dynamic loader
builds a load graph to determine the order in which the dependent libraries
are loaded.
For example, suppose you create three libraries - libQ ,
libD , and libP - using the ld commands
below. The order in which the libraries are built is important because
a library must exist before you can specify it as a dependent library.
$ ld -b -o libQ.so modq.o -lB
$ ld -b -o libD.so modd.o -lQ -lB
$ ld -b -o libP.so modp.o -lA -lD -lQ
The dependency lists for these three libraries are:
libQ
depends on libB
libD
depends on libQ and libB
libP
depends on libA , libD , and libQ
+-->libA.so
|
libP.so-->libD------+
| | |
| v v
+-->libQ.so-->libB.so
For PA-32 compatibility mode
The loader uses the following algorithm in PA-32 mode:
if the library has not been visited then
mark the library as visited.
if the library has a dependency list then
traverse the list in reverse order.
Place the library at the head of the load list.
Shown below are the steps taken to form the load graph when libP
is loaded:
mark P , traverse Q
mark Q , traverse B
mark B , load B
load
Q
traverse D
mark D , traverse B
B
is already marked, so skip B , traverse Q
Q
is already marked, so skip Q
load
D
mark A , load A
load
P
The resulting load graph is:
libP -->libA -->libD --> libQ --> libB
The dynamic loader uses the following algorithm in PA-64 and IPF mode:
if the library has not been visited then
mark the library as visited;
append the library at the end of the list.
if the library has a dependency list then
traverse the list in order.
Shown below are the steps taken to form the load graph when libP
is loaded:
mark P , load P
traverse P
mark A , load A
mark D , load D
mark Q , load Q
traverse D
Q
is already marked, so skip Q
mark B , load B
traverse Q
B
is already marked, so skip B
The resulting load graph is:
libP -->libA -->libD --> libQ --> libB
Placing Loaded Libraries in
the Search List
The libraries must be added to the shared
library search list once a load graph is formed, thus binding their symbols to the program. If the
initial library is an implicitly loaded library (that is, a library that
is automatically loaded when the program begins execution), the libraries
in the load graph are appended to the library search list. For example,
if libP is implicitly loaded, the library search list is:
<current search list>--> libP --> libA --> libD --> libQ --> libB
The same behavior occurs for libraries that are explicitly loaded with
shl_load , but without the BIND_FIRST modifier
(see BIND_FIRST Modifier for details). If BIND_FIRST
is specified in the shl_load call, then the libraries
in the load graph are inserted before the existing search list.
For example, suppose libP is loaded with this call:
lib_handle = shl_load("libP.so", BIND_IMMEDIATE | BIND_FIRST, 0);
Then, the resulting library search list is:
libP --> libA --> libD --> libQ --> libB --><current search list>
Updating a Shared Library
The ld command cannot replace or delete object modules
in a shared library. Therefore, to update a shared library, you must relink
the library with all the object files you want the library to
include. For example, suppose you fix some routines in length.c
(from the previous section) that gave incorrect results. To update
the libunits.so library to include these changes, you must
use this series of commands:
$ cc -Aa -c length.c
$ ld -b -o libunits.so length.o volume.o mass.o
Any programs that use this library will now be using the new versions
of the routines. That is, you do not have to relink any programs that
use this shared library. This is because the routines in the library
are attached to the program at run time.
This is one of the advantages of shared libraries over archive libraries:
if you change an archive library, you must relink any programs that use
the archive library. With shared libraries, you need only recreate the
library.
Incompatible Changes to a Shared Library
If you make incompatible changes to a shared library, you can use library
versioning to provide both the old and the new routines to ensure that
programs linked with the old routines continue to work. See Version
Control with Shared Libraries for more information on version control
of shared libraries.
Shared Library Location (IPF)
You can place shared libraries in the same locations as archive libraries
(see Archive Library Location ). Again,
this is typically /usr/lib/hpux32 and /usr/lib/hpux64
for application libraries, and system libraries. However, these are just
suggestions.
A program can search a list of directories at run time for any required
libraries. Thus, libraries can be moved after an application has been
linked with them. To search for libraries at run time, a program must
know which directories to search. There are three ways to specify this
directory search information:
Store a directory path list in the program with
the linker option +b path_list.
Define the SHLIB_PATH environment
variable for use at run time.
Use the LD_LIBRARY_PATH environment
variable, and the +s option is enabled by default.
For details on the use of these options, see Moving
Libraries after Linking with +b and Moving
Libraries After Linking with +s and SHLIB_PATH .
Improving Shared Library Performance
This section describes methods you can use to improve the run-time performance
of shared libraries. If, after using the methods described here, you are
still not satisfied with the performance of your program with shared libraries,
try linking with archive libraries instead to see if it improves performance.
In general, though, archive libraries do not provide great performance
improvements over shared libraries.
Loading Shared Libraries with the
LD_PRELOAD Environment Variable
Note The LD_PRELOAD feature is disabled for seteuid/setegid
programs, such as passwd. See ld(1) for more details. This
feature is not available for fully-bound static executables.
The LD_PRELOAD environment variable allows you to load
additional shared libraries at program startup. The LD_PRELOAD
environment variable provides a colon-separated or space-separated list of shared libraries
that the dynamic loader can interpret. The dynamic loader, dld.so ,
loads the specified shared libraries as if the program is linked
explicitly with the shared libraries in LD_PRELOAD before
any other dependents of the program.
At startup time, the dynamic loader implicitly loads one or more libraries,
if found, specified in the LD_PRELOAD environment. It uses
the same load order and symbol resolution order as if the library is explicitly linked as the first library in the link line when building
the executable. For example, given an executable built with the following
link line:
$ ld ... lib2.so lib3.so lib4.so
If LD_PRELOAD="/var/tmp/lib1.so" , the dynamic
loader uses the same load order and symbol resolution order as if lib1.so
is specified as the first library in the link line:
$ ld ... /var/tmp/lib1.so lib2.so lib3.so lib4.so
In a typical command line use (with /usr/bin/sh ), where
LD_PRELOAD is defined as follows:
$ LD_PRELOAD=mysl.so application
The dynamic loader searches application according to $PATH ,
but searches mysl.so according to SHLIB_PATH
and/or LD_LIBRARY_PATH , and/or the embedded path (if enabled).
You can use the LD_PRELOAD environment variable to load
a shared library that contains thread-local storage to avoid the following
error when loading the library dynamically:
/usr/lib/hpux32/dld.so: Cannot
dlopen load module /usr/lib/hpux32/libpthread.so.1
The dynamic loader uses the LD_PRELOAD environment variable
even if you use the +noenvvar option in the link line. This ensures that LD_PRELOAD
is enabled even in a +compat link. The LD_PRELOAD
variable is always enabled except for setuid and setgid programs.
NoteUsing LD_PRELOAD can cause a core dump when used with applications
which mix shared and archived libraries, especially when both the shared
library and the application are built with aC++ or use libc.
You can specify multiple libraries as part of the LD_PRELOAD
environment variable. Separate the libraries by spaces or colons as in
LD_LIBRARY_PATH . (Multi-byte support is not provided as part
of parsing the LD_PRELOAD library list). You can specify
LD_PRELOAD libraries with absolute paths or relative paths.
The LD_PRELOAD libraries can also consist of just the library
names, in which case the dynamic loader uses the directory path list in
the environment variables LD_LIBRARY_PATH and/or SHLIB_PATH
or the embedded path list (if enabled) to search for the libraries.
The dynamic loader does not issue an error or warning message if it
cannot find a library specified by LD_PRELOAD . However, if
it does not find a dependent of the LD_PRELOAD libraries,
the dynamic loader issues the same error message as if the LD_PRELOAD
library is specified in the link line.
LD_PRELOAD Example
Consider a case where a.out has the following dependents:
a.out
/ \
libA.so libB.so
That is, a.out is built with commands like these:
$ cc -c ?.c
$ ld -b -o libB.so b.o
$ ld -b -o libA.so a.o
$ cc foo.c -L. -lA -lB
ldd(1) shows the order in which
the shared libraries are loaded:
$ ldd a.out
libA.so => ./libA.so
libB.so => ./libB.so
libc.so.1 => /usr/lib/hpux32/libc.so.1
libdl.so.1 => /usr/lib/hpux32/libdl.so.1
The symbol resolution order for the user libraries is:
a.out - ->libA.so --> libB.so
If the LD_PRELOAD environment variable is set to "./libC.so" ,
the symbol resolution order is:
$ export LD_PRELOAD=./libC.so
$ ldd a.out
./libC.so => ./libC.so
libA.so => ./libA.so
libB.so => ./libB.so
libc.so.1 => /usr/lib/hpux32/libc.so.1
libdl.so.1 => /usr/lib/hpux32/libdl.so.1
a.out -->libC.so -->libA.so -->libB.so
LD_PRELOAD Example (PA-RISC)
The PA64 linker toolset searches dependent libraries in a breadth-first
order for symbol resolution. The PA32 linker toolset searches in depth-first
order. Therefore, the library load order and symbol resolution order may
differ depending on which mode is used. Consider a case where a.out has
the following dependents:
a.out
/ \
libA.sl libB.sl
/ \
libC.sl libD.sl
That is, a.out is built with commands like these:
$ cc +DA2.0W -c +z ?.c
$ ld -b -o libB.sl b.o
$ ld -b -o libC.sl c.o
$ ld -b -o libD.sl d.o
$ ld -b -o libA.sl a.o -L. -lC -lD
$ cc foo.c -L. -lA -lB
64-bit Behavior
In 64-bit mode, ldd(1) shows the order in which the shared
libraries are loaded (siblings first, then dependents):
$ ldd a.out
libA.sl => ./libA.sl
libB.sl => ./libB.sl
libc.2 => /usr/lib/pa20_64/libc.2
libC.sl => ./libC.sl
libD.sl => ./libD.sl
libdl.1 => /usr/lib/pa20_64/libdl.1
Therefore, with LD_PRELOAD unset, the symbol resolution order for the
user libraries in 64-bit mode is:
a.out - -> libA.sl --> libB.sl --> libC.sl -->libD.sl
Case (i): LD_PRELOAD="./libB.sl"
In 64-bit mode, the symbol resolution order is:
$ export LD_PRELOAD="./libB.sl"
$ ldd a.out
./libB.sl => ./libB.sl
./libB.sl => ./libB.sl
libA.sl => ./libA.sl
libB.sl => ./libB.sl
libc.2 => /usr/lib/pa20_64/libc.2
libC.sl => ./libC.sl
libD.sl = ./libD.sl
libdl.1 => /usr/lib/pa20_64/libdl.1
a.out --> libD.sl --> libA.sl --> libB.sl --> libC.sl
Case (ii): LD_PRELOAD = "./libD.sl"
In 64-bit mode, the symbol resolution order is:
$ export LD_PRELOAD="./libD.sl"
$ ldd a.out
./libD.sl => ./libD.sl
./libD.sl => ./libD.sl
libA.sl => ./libA.sl
libB.sl => ./libB.sl
libc.2 => /usr/lib/pa20_64/libc.2
libC.sl => ./libC.sl
libD.sl => ./libD.sl
libdl.1 => /usr/lib/pa20_64/libdl.1
a.out --> libD.sl --> libA.sl --> libB.sl - -> libC.sl
If the same symbol is defined in libA.sl and libD.sl ,
the 64-bit linker toolset uses the symbol defined in libD.sl
because this library is loaded and searched before libA.sl
when LD_PRELOAD="./libD.sl" .
32-bit Behavior
With LD_PRELOAD unset, in 32-bit mode the dynamic loader
forms a load graph as follows:
mark B, load B
mark A, traverse D, load D
load C
load A
With this load graph, the symbol resolution order is built in reverse
order in 32-bit mode:
a.out --> libA.sl --> libC.sl --> libD.sl --> libB.sl
Case (i): LD_PRELOAD="./libB.sl"
In 32-bit mode, the dependents of a.out are treated as:
libB.sl , libA.sl , libB.sl . The
second libB.sl is ignored by the dynamic loader, because it
is a duplicate library. So, the effective dependents of a.out are treated
as: libB.sl , libA.sl . The load graph is:
mark A, traverse D, load D
load C
load A
load B
The symbol resolution order is:
a.out --> libB.sl--> libA.sl --> libC.sl --> libD.sl
Case (ii): LD_PRELOAD = "./libD.sl"
In 32-bit mode, the load graph is:
mark B, load B
mark A, traverse D, load D
load C
load A
The symbol resolution order is:
a.out --> libA.sl --> libC.sl --> libD.sl --> libB.sl
Loading Shared
Libraries with the LD_PRELOAD_ONCE Environment Variable
The LD_PRELOAD_ONCE feature is similar
to LD_PRELOAD except that the dynamic loader, dld.sl, unsets
LD_PRELOAD_ONCE after reading it, so that any applications
invoked by the current application do not have LD_PRELOAD_ONCE
set. This is useful in situations where the current application needs
certain libraries preloaded while the child application is adversely affected
if these are preloaded (e.g. ksh terminates abnormally if LD_PRELOAD
contains /usr/lib/libpthread.1).
The libraries specified by LD_PRELOAD_ONCE
are loaded before the ones specified by LD_PRELOAD . The effect
on symbol resolution is that the symbols from libraries specified by LD_PRELOAD_ONCE
take precedence over symbols from libraries specified by LD_PRELOAD .
Using Profile-Based
Optimization on Shared Libraries
You can perform profile-based optimization on
your shared libraries to improve their performance. See Profile-Based
Optimization for more information.
Exporting Only the Required
Symbols
Normally, all global variables and procedure definitions are exported
from a shared library. In other words, any procedure or variable defined
in a shared library is made visible to any code that uses this library.
In addition, the compilers generate "internal" symbols that
are exported. You may be surprised to find that a shared library exports
many more symbols than necessary for code that uses the library. These
extra symbols add to the size of the library's symbol table and can even
degrade performance (since the dynamic loader has to search a larger-than-necessary
number of symbols).
One possible way to improve shared library performance is to export
only those symbols that need exporting from a library. To control which
symbols are exported, use either the +e or the -h
option to the ld command. When +e options are
specified, the linker exports only those symbols specified by +e
options. The -h option causes the linker to hide the specified
symbols. For details on using these options, see Hiding
Symbols with -h and Exporting Symbols
with +e.
As an example, suppose you created a shared library that defines
the procedures init_prog and quit_prog and the
global variable prog_state . To ensure that only these symbols
are exported from the library, specify these options when creating the
library:
+e init_prog +e quit_prog +e prog_state
If you have to export many symbols, you may find it convenient to use
the -c file option, which allows you to specify
linker options in file. For instance, you canspecify the
above options in a file named export_opts as:
+e init_prog
+e quit_prog
+e prog_state
Then you must specify the following option on the linker command line:
-c export_opts
For details on the -c option, see Passing
Linker Options in a file with -c .
Placing Frequently-Called Routines
Together
When the linker creates a shared library, it places the object modules
into the library in the order in which they are specified on the linker
command line. The order in which the modules appear can greatly affect
performance. For instance, consider the following modules:
a.o
-
Calls routines in c.o heavily, and its routines are
called frequently by c.o .
b.o
-
A huge module, but contains only error routines that are seldom
called.
c.o
-
Contains routines that are called frequently by a.o ,
and calls routines in a.o frequently.
If you create a shared library using the following command line, the
modules are inserted into the library in alphabetical order:
$ ld -b -o libabc.so *.o
The potential problem with this ordering is that the routines in a.o
and c.o are spaced far apart in the library. Better virtual
memory performance can be attained by positioning the modules a.o
and c.o together in the shared library, followed by the module
b.o . The following command does this:
$ ld -b -o libabc.so a.o c.o b.o
One way to help determine the best order to specify the object files
is to gather profile data for the object modules; modules that are frequently
called must be grouped together on the command line.
Another way is to use the lorder(1) and tsort(1)
commands. Used together on a set of object modules, these commands determine
how to order the modules so that the linker only needs a single pass to
resolve references among the modules. A side-effect of this is that modules
that call each other may be positioned closer together than modules that
do not. For instance, suppose you have defined the following object modules:
- Module
-
Calls Routines in Module
a.o
-
x.o y.o
b.o
-
x.o y.o
d.o
-
none
e.o
-
none
x.o
-
d.o
y.o
-
d.o
Then the following command determines the one-pass link order:
$ lorder ?.o | tsort Pipe lorder's output to tsort. a.o
b.o
e.o
x.o
y.o
d.o
Notice that d.o is now closer to x.o and y.o ,
which call it. However, this is still not the best information to use
because a.o and b.o are separated from x.o
and y.o by the module e.o , which is not
called by any modules. The actual optimal order may be more
like this:
a.o b.o x.o y.o d.o e.o
Again, the use of lorder and tsort is not
perfect, but it may give you leads on how to best order the modules. You
may want to experiment to see what ordering gives the best performance.
Making Shared Libraries Non-Writable
You may get an additional performance gain by ensuring that no shared
libraries have write permissions. Programs that use more than one writable
library can experience significantly degraded loading time. The following
chmod command gives shared libraries the correct permissions
for best load-time performance:
$ chmod 555 libname
Using the +ESlit/+Olit=all Option to cc
The +ESlit compiler option is available only for programs on PA systems. For programs on Itanium-based
systems, use the +Olit=all compiler option.
Normally, the C compiler places constant data in the data space. If
such data is used in a shared library, each process receives its own copy
of the data. This may result in some performance degradation.
Use the +ESlit /+Olit=all option to
place constant data in the .text text segment instead of in
the data space. This results in one copy of the constant data being shared
among all processes that use the library.
+cond_rodata Command-Line Option
The compiler option +cond_rodata allows more data to be placed in the read-only section.
Normally, data with initializers that contain relocations are not placed in the read-only data sections.
This option enables the linker to compute the proper section for initialized constant data.
The +cond_rodata compiler option is available only for programs on Itanium-based systems.
Note The +ESlit /+Olit=all option requires that programs do not
write into constant strings and data. Structures with embedded initialized pointers
do not work because the pointers cannot be relocated. The pointers are in the
read-only $TEXT$ space (PA32 system) and text segment (Itanium-based system).
In this case, the linker outputs the error message "Invalid loader fixup needed ".
Using Filtered Shared Libraries (32-bit
Mode Only)
Filtered shared libraries allow developers to reduce the memory footprint
of their shared libraries by providing for deferred loading of shared
libraries (load-on- bind, referred to as ``lazy loading''). Filtering
divides up a large library into one filter and several implementation
libraries.
- filter
-
the library that exports a symbol, but does not contain implementation
or storage for that symbol.
- implementation
-
the library that contains implementation and storage for a symbol.
The user links with the filter library, but the real definitions of
data and functions reside in the implementation libraries. At run time,
only those implementation libraries that are actually used are loaded.
Filtered libraries can be nested; an implementation library can itself
be a filtered library containing other implementation libraries.
The linker provides the 32-bit +filter option, used with the -b option,
to enable this mechanism.
$ld -b...+filter
shared_library_pathname
If you divide a filtered shared library up into independent implementation
libraries, the total memory consumption is reduced significantly if the
application uses only a small portion of the library. This reduction is
most significant when the shared library contains a large amount of static
data which is not used by all applications.
It is important, when dividing a shared library into implementation
libraries, that you keep them independent of each other. If there are
dependencies between implementation libraries, the memory reduction benefits
cannot be realized. Filtered shared libraries preserve compatibility because
a filtered shared library appears as a single component to the application.
You need to link only to the filter library, regardless of how many implementation
libraries it is divided into.
Building Filtered Shared Libraries
Building filtered shared libraries requires several steps:
Build the independent implementation libraries
which constitute a filter set. These implementation libraries contain
the actual definitions of code and data symbols. Build these libraries
like ordinary shared libraries using the -b linker option.
-
Build the filter library using the +filter linker option. Each implementation
library which is part of the filter set is specified using the +filter
option. For example:
$ ld -b impl1.o -o libimpl1.sl
$ ld -b impl2.o -o libimpl2.sl
$ ld -b +filter libimpl1.sl +filter libimpl2.sl -o libfilt.sl
This builds a filtered shared library libfilt.sl with two
implementation libraries libimpl1.sl and libimpl2.sl .
-
To confirm which implementation libraries are in the filter set,
use odump to list the contents of the filter library:
$ odump
-filtertable libfilt.sl
Filtered Shared Library List Table for libfilt.sl:
Index String Table Offset Name
0 11 ./libimpl1.sl
1 24 ./libimpl2.sl
NoteThe +filter option takes effect only when you use the -b
option.
The +filter option can be used in conjunction with the -L linker
option or the LPATH environment variable. For example:
$ ld -b -L.
+filter impl1 +filter impl2 -o libfilt.sl
or
$ export LPATH=.
$ ld -b -L. +filter impl1 +filter impl2 -o libfilt.sl
The filtered shared library itself can contain definitions of some symbols
which are not required to be ``lazy loaded'' (typically those symbols
that are always used, for example, exit(2) in case of libc).
For example:
$ ld -b a.o
b.o...+filter libimpl1.sl +filter libimpl2.sl -o libfilt.sl
Building application programs linked
to filtered shared libraries
When applications use filtered shared libraries, they link only to the
filter library and not the individual implementation libraries. At link
time, the implementation libraries are transparent to the application
linking with the filter. At run time, the dynamic loader searches for
symbol definitions. If it finds a match with a symbol in the filter library,
it loads the appropriate implementation library that contains the actual
definition of the required symbol. The following example demonstrates
compiling an application program with a filtered library:
$ cc prog.c
libfilt.sl -o prog
You can explicitly link your program with an implementation library
as well as with the parent filtered library, but you lose any advantage
offered by the filter library feature. The implementation library is loaded
as your program is run.
Run time behavior of filtered shared
libraries
Symbols defined in implementation libraries are not directly available
to the application. They are accessible only via their parent filter library.
Users of shl_* and dl* APIs need to use the handle for the filter library
to query or define symbols. This is to preserve compatibility for existing
applications.
Initializers
If declared as part of implementation libraries, initializers are called
only when the implementation library is loaded (as part of a symbol binding).
The handle argument to the initializer points to the filter library so
that it can be used for shl_findsym(3X) or dlsym(3C). For example, you
declare your initializers as follows:
#include <dlfcn.h>
void initializer(void *handle, int loading)
{
...
ptr = dlsym(handle, "symname");
...
}
Dynamic Path Lookup
If you specify an embedded path while building the filtered shared library
using the +b linker option, the dynamic loader attempts to use this
dynamic path when it searches for the implementation library to load.
For example:
$ ld -b impl1.o -o libimpl1.sl
$ ld -b +b /path/to/implementation/libs +filter\ ./libimpl1.sl -o libfilt.sl
$ chatr +b enable libfilt.sl
$ cc prog.c libfilt.sl -o prog
$ mv libimpl1.sl /path/to/implementation/libs
$ ./prog
Thread-Private Data (TLS)
You can use TLS in the implementation libraries even though they are
"`lazy-loaded". When the filter library is loaded, space is
reserved for the TLS of its implementation libraries even before they
are loaded. Because the space is reserved, filtering is not an appropriate
way to reduce TLS consumption.
Maintaining filtered shared libraries
To maintain your filtered shared libraries, you must rebuild them when
you make the following changes:
Add or remove symbols from the set of exported
symbols from the implementation libraries.
Change symbol types, for example, when an uninitialized
data symbol (BSS symbol) is changed to an initialized data symbol (or
vice-versa), or a text symbol into a data symbol.
-
Change the TLS (Thread Local Storage) size of an implementation
library Even though the newly-added TLS symbols may not be exported outside
the implementation library, you must rebuild the filter library so
the correct amount of TLS space is reserved.
To see the TLS size, use the following command:
$ odump
-sldlheader shared_library_pathname | fgrep tdsize
Function Level Versioning
A new type qualifier with syntax '__attribute__((version_id("<some version id string>"))' is
supported that can be used to facilitate shared libraries to have different versions of the same
function based on platform (11.0, 11.11 etc.) or any other factor. Existing user applications
using these shared libraries do not require recompilation when migrated to next version of the OS.
Binaries will continue to run with the older version of the function which is part of the shared library.
Example usage:
extern int y __attribute__((version_id("11.22")));
|
Version Control with Shared Libraries |
HP-UX provides a method to support incompatible versions of shared
library routines. Library-Level Versioning
describes how to create multiple versions of a shared library.
NoteBeginning with the HP-UX 11.00 release, the linker toolset supports
only library-level versioning. Previous releases supported inter-library
version control.
When to Use Shared Library Versioning
For the most part, updates to a shared library must be completely
upward-compatible; that is, updating a shared library does not usually cause
problems for programs that use the library. But sometimes - for example,
if you add a new parameter to a routine - updates cause undesirable side-effects
in programs that call the old version of the routine. In such cases, it
is desirable to retain the old version as well as the new. This way, old
programs continue to run and new programs can use the new version
of the routine.
Here are some guidelines to keep in mind when making changes to
a library:
When creating the first version of a shared
library, carefully consider whether or not you need versioning. It
is easier to use library-level versioning from the start.
-
When creating future revisions of a library, you must determine
when a change represents an incompatible change, and thus deserves a new
version. Some examples of incompatible changes are as follows:
As a general rule, when an exported function
is changed such that calls to the function from previously compiled object
files must not resolve to the new version, the change is incompatible.
If the new version can be used as a wholesale replacement for the old
version, the change is compatible.
For exported data, any change in either
the initial value or the size represents an incompatible change.
Any function that is changed to take advantage
of an incompatible change in another module must be considered incompatible.
Maintaining Old Versions of Library Modules
When using shared library versioning, you need to save the old versions
of modules in the library:
With library-level versioning, when an incompatible
change is made to a module, the entire old version of the library must
be retained along with the new version.
With intra-library versioning, when an incompatible
change is made to a module, all the old versions of the module must
be retained along with the new version. The new version number must
correspond to the date the change was made. If several modules are changed
incompatibly in a library, it is a good idea to give all modules the same
version date.
Library-Level Versioning
HP-UX 10.0 adds a new library-level versioning scheme that allows
you to maintain multiple versions of shared libraries when you make incompatible
changes to the library. By maintaining multiple versions, applications
linked with the older versions continue to run with the older libraries,
while new applications link and run with the newest version of the library.
Library-level versioning is very similar to the library versioning on
UNIX System V Release 4.
How to Use Library-Level Versioning
To use library-level versioning, complete the following steps:
-
Name the first version of your shared library with an extension
of .0 (that's the number zero), for example, libA.0 .
Use the +h option to designate the internal name of the library,
for example, libA.0 :
$ ld -b *.o -o libA.0 +h libA.0 Creates the shared library libA.0.
-
Since the linker still looks for libraries ending in .so
with the -l option, create a symbolic link from the usual
name of the library ending in .so to the actual library.
For example, libA.so points to libA.0 :
$ ln -s libA.0 libA.so libA.so is a symbolic link to libA.0.
-
Link applications as usual, using the -l option
to specify libraries. The linker searches for libA.so .
However, if the library it finds has an internal name, the linker
places the internal name of the library in the executable's shared library
dependency list. When you run the application, the dynamic loader loads
the library named by this internal name. For example:
$ ld prog.o -lA -lc Binds a.out with libA.0.
Creating a New, Incompatible Version of the Library
When you create a new version of the library with incompatible changes,
repeat the above steps except increment the number in the suffix of the
shared library file name. That is, create libA.1 rather than
libA.0 , and set the symbolic link libA.so to
point to libA.1 . Applications linked with libA.0
continue to run with that library while new applications link and run
with libA.1 . Continue to increment the suffix number for
subsequent incompatible versions, for example, libA.2 , libA.3
and so forth.
Migrating an Existing Library to Use Library-Level
Versioning
If you have an existing library, you can start using library-level
versioning. First rename the existing library to have the extension .0 .
Then create the new version of the library with the extension .1
and an internal name using the .1 extension. Create a symbolic
link with the extension .so to point to the .1
library.
When you run a program that uses shared libraries and was linked
before HP-UX 10.0, the dynamic loader first attempts to open the shared
library ending in .0 . If it cannot find that library, it
attempts to open the library ending in .so .
The +h Option and Internal Names
The +h option gives a library an internal name for
library-level versioning. Use +h to create versioned libraries:
+h internal_name
The internal_name is typically
the same name as the library file itself, for example, libA.1
as in +h libA.1 . When you link with a library that has an
internal name, the linker puts the internal_name in the shared
library dependency list of the executable or shared library being created.
When running the resulting executable or shared library, it is the library
named by this internal name that the dynamic loader loads.
You can include a relative or absolute path with the internal name,
but you must ensure that the dynamic loader can find the library from this
name using its normal search process.
For PA-32 compatibility mode (with +compat ), if internal_name
includes a relative path (that is, a path not starting with / ),
the internal name stored by the linker is the concatenation of the actual
path where the library was found and internal_name, including
the relative path. When you run the resulting program, if the dynamic loader
cannot find the library at this location, the program does not run.
If internal_name includes an absolute path (that is,
a path starting with / ), the internal name stored by the
linker is simply the internal_name, including the absolute
path. When the resulting program is run, if the dynamic loader cannot
find the library at this location, the program will not run.
See Internal Name Processing for more information.
File System Links to Shared Libraries
This section discusses the situation where you have used the ln(1)
command to create file system links to shared library files. For example:
$ ld -b -o /X/libapp.so *.o Create shared library.
$ ln -s /X/libapp.so /tmp/libmine.so Make the link.
Figure 10: Example for Creating File System Link to a Shared Library File
During a link, the linker records the file name of the opened library
in the shared library list of the output file. However, if the shared
library is a file system link to the actual library, the linker does
not record the name of the library the file system link points to.
Rather it records the name of the file system link.
For example, if /tmp/libmine.so is a file system link
to /X/libapp.so , the following command records /tmp/libmine.so
in a.out , not /X/libapp.so as may be expected:
$ ld main.o -L /tmp -lmine -lc
To use library-level versioning in this situation, you must set
up corresponding file system links to make sure older applications linked
with the older libraries run with these libraries. Otherwise, older applications
can end up running with newer shared libraries. In addition, you must
include the absolute path name in the internal name of the new library.
For example, in PA-32 mode, to make the above example work correctly
with library-level versioning, first implement library-level versioning
with the actual library /X/libapp.so and include the absolute
path in the internal name of the new library:
$ mv /X/libapp.so /X/libapp.0 Rename old version.
$ ld -b -o /X/libapp.1 +h /X/libapp.1 *.o Create new version.
$ ln -s /X/libapp.1 /X/libapp.so Set up symbolic link.
Then set up the corresponding file system links:
$ ln -s /X/libapp.0 /tmp/libmine.0 Link to old version.
$ ln -s /X/libapp.1 /tmp/libmine.1 Link to new version.
$ rm /tmp/libmine.so Remove old link.
$ ln -s /X/libapp.so /tmp/libmine.so Link to the link.
Figure 11: Example for Creating File System Link to a Shared Library File in PA-32 Mode
With these links in place, the loader loads /X/libapp.0
while running the a.out file created above. New applications
link and run with /X/libapp.1 .
For IPF/PA-64 mode programs, the dynamic loader only loads the library
recorded in the dynamic load table. You must use library-level versioning,
and create your PA-64 and IPF shared library with an internal name unless
the library is not versioned in future.
Using shl_load(3X) with Library-Level Versioning
Once library level versioning is used, calls to shl_load(3X)
must specify the actual version of the library to be loaded. For example,
if libA.so is now a symbolic link to libA.1 ,
then calls to dynamically load this library must specify the latest
version available when the application is compiled, as shown below:
shl_load("libA.1", BIND_DEFERRED, 0);
This insures that when the application is migrated to a system that
has a later version of libA available, the actual version
desired is the one that is dynamically loaded.
Intra-Library Versioning (PA-RISC only)
Intra-library versioning is a second method of maintaining multiple
incompatible versions of shared library routines. HP recommends library-level versioning
over intra-library versioning.
This section discusses the following topics:
The Version Number Compiler Directive
With intra-library versioning, you assign a version number
to any module in a shared library. The version number applies to all global
symbols defined in the module's source file. The version number is a date,
specified with a compiler directive in the source file. The syntax of
the version number directive depends on the language:
- C and C++:
-
#pragma HP_SHLIB_VERSION
"date"
- FORTRAN:
-
$SHLIB_VERSION 'date'
- Pascal:
-
$SHLIB_VERSION 'date'$
The date argument in all three directives is of the form
month/ year. The month must
be 1 through 12 , corresponding to January through
December. The year can be specified as either the last two
digits of the year (94 for 1994) or a full year specification
(1994 ). Two-digit year codes from 00 through
40 represent the years 2000 through 2040.
This directive must be used only if incompatible changes are made
to a source file. If a version number directive is not present in a source
file, the version number of all symbols defined in the object module defaults
to 1/90 .
Shared Library Dependencies and Version Control
A shared library as a whole can be thought of as having a version
number itself. This version number is the most recent of the versioned
symbols in the library and any dependent libraries.
When a shared library is built with a dependent shared library,
the version number of the dependent library used during the link is recorded
with the dependency.
When shl_load(3X) is called to load a shared library,
the latest version of the library is loaded. If that library has dependencies,
the dynamic loader (dld.sl(5)) loads the versions of
the dependent libraries that were recorded in the dependency list. Note
that these are not necessarily the most recent versions of the dependent
libraries. When dld.sl loads a particular version of a shared
library, it loads the same version of any dependent libraries.
If a shared library lists a second shared library as a dependency,
dld.sl generates an error if the second shared library
has a version number which is older than the version number recorded with
the dependency. This means that the first library was built using a more
recent version of the second library than the version that dld.sl
currently finds.
Adding New Versions to a Shared Library
To rebuild a shared library with new versions of object
files, run ld again with the newly compiled object files.
For example, suppose you want to add new functionality to the routines
in length.c , making them incompatible with existing programs
that call libunits.sl . Before making the changes, make a
copy of the existing length.c and name it oldlength.c .
Then change the routines in length.c with the version directive
specifying the current month and date. The following shows the new length.c
file:
#pragma HP_SHLIB_VERSION "11/93" /* date is November 1993 */
/*
* New version of "in_to_cm" also returns a character string
* "cmstr" with the converted value in ASCII form.
*/
float in_to_cm(float in, char *cmstr) /* convert in to cm */
{
. . . /* build "cmstr" */
return(in * 2.54);
}
. . . /* other length conversion routines */
To update libunits.sl to include the new length.c
routines, copy the old version of length.o to oldlength.o .
Then compile length.c and rebuild the library with the new
length.o and oldlength.o :
$ cp length.c oldlength.c Save the old source.
$ mv length.o oldlength.o Save old length.o.
. . . Make new length.c.
$ cc -Aa -c +z length.c Make new length.o.
$ ld -b -o libunits.sl oldlength.o \ Relink the library.
volume.o mass.o length.o
Thereafter, any programs linked with libunits.sl use
the new versions of length-conversion routines defined in length.o .
Programs linked with the old version of the library still use those routines
from oldlength.o . For details on linking with shared libraries,
see Linker Tasks.
Specifying a Version Date
When adding modules to a library for a particular release of the
library, it is best to give all modules the same version date. For example,
if you complete file1.o on 04/93, file2.o on
05/93, and file3.o on 07/93, it is best to give all
the modules the same version date, say 07/93.
The reason for doing this is best illustrated with an example. Suppose
in the previous example you gave each module a version date corresponding
to the date it was completed: 04/93 for file1.o , 05/93 for
file2.o , and 07/93 for file3.o . You then build
the final library on 07/93 and link an application a.out
with the library. Now suppose that you introduce an incompatible change
to function foo found in file1.o , set the version
date to 05/93, and rebuild the library. If you run a.out
with the new version of the library, a.out gets the new,
incompatible version of foo because its version date is still
earlier than the date the application was linked with the original library.
|
Switching from Archive to Shared Libraries |
There are cases where a program may behave differently when linked
with shared libraries than when linked with archive libraries. These are
the results of subtle differences in the algorithms the linker uses to
resolve symbols and combine object modules. This section covers these
considerations. (See also Caution
When Mixing Shared and Archive Libraries .)
Relying on Undocumented Linker Behavior
Occasionally, programmers may take advantage of linker behavior
that is undocumented but has traditionally worked. With shared libraries,
such programming practices may not work or may produce different results.
If the old behavior is absolutely necessary, linking with archive libraries
alone (-a archive ) produces the old behavior.
For example, suppose several definitions and references of a symbol
exist in different object and archive library files. By specifying the
files in a particular link order, you can cause the linker to use one
definition over another. But doing so requires an understanding of the
subtle (and undocumented) symbol resolution rules used by the linker,
and these rules are slightly different for shared libraries. So make
files or shell scripts that took advantage of such linker behavior prior
to the support of shared libraries may not work as expected with shared
libraries.
More commonly, programmers may take advantage of undocumented linker
behavior to minimize the size of routines copied into the a.out
files from archive libraries. This is no longer necessary if all libraries
are shared.
Although it is impossible to characterize the new resolution rules
exactly, the following rules always apply:
If a symbol is defined in two shared libraries,
the definition used at run time is the one that appeared first regardless
of where the reference was.
The linker treats shared libraries more like
object files.
As a consequence of the second rule, programs that call wrapper
libraries may become larger. (A wrapper library is a
library that contains alternate versions of C library functions, each
of which performs some bookkeeping and then calls the actual C function.
For example, each function in the wrapper library may update a counter
of how many times the actual C routine is called.) With archive libraries,
if the program references only one routine in the wrapper library, then
only the wrapper routine and the corresponding routine from the C library
are copied into the a.out file. If, on the other hand, a
shared wrapper library and archive C library are specified, in that order,
then all routines that can be referenced by any routine in the wrapper
library are copied from the C library. To avoid this, link with archive
or shared versions for both the wrapper library and C library, or use
an archive version of the wrapper library and a shared version of the
C library.
Absolute Virtual Addresses
Writing code that relies on the linker to locate a symbol in a particular
location or in a particular order in relation to other symbols is known
as making an implicit address dependency. Because of
the nature of shared libraries, the linker cannot always preserve the
exact ordering of symbols declared in shared libraries. In particular,
variables declared in a shared library may be located far from the main
program's virtual address space, and they may not reside in the same relative
order within the library as they were linked. Therefore, code that has
implicit address dependencies may not work as expected with shared libraries.
An example of an implicit address dependency is a function that
assumes that two global variables that were defined adjacently in the
source code is actually adjacent in virtual memory. Because the linker
may rearrange data in shared libraries, this is no longer guaranteed.
Another example is a function that assumes variables it declares statically
(for example, C static variables) reside below the reserved
symbol _end in memory (see end(3)). In general,
it is a bad idea to depend on the relative addresses of global variables,
because the linker may move them around.
In assembly language, using the address of a label to calculate
the size of the immediately preceding data structure is not affected:
the assemblers still calculate the size correctly.
Stack Usage
To load shared libraries, a program must have a copy of the dynamic
loader (dld.so ) mapped into its address space. This copy
of the dynamic loader shares the stack with the program. The dynamic loader
uses the stack during startup and whenever a program calls a shared library
routine for the first time. If you specify -B immediate ,
the dynamic loader only uses the stack at startup and for explicit calls
to loader routines, such as dlopen .
NoteFor PA-32 compatibility mode (with +compat ) only:
Although it is not a recommended programming practice, some programs
may use stack space "above" the program's current stack. To
preserve the contents "above" the program's logical top of the
stack, the dynamic loader attempts to use stack space far away from program's
stack pointer. If a program is doing its own stack manipulations, such
as those implemented by a "threads" package, the dynamic loader
may inadvertently use stack space that the program had reserved for another
thread. Programs doing such stack manipulations must link with archive
libraries, or at least use immediate binding and avoid calling loader
routines, if this could potentially cause problems.
Also be aware that if a program sets its stack pointer to memory
allocated in the heap, the dynamic loader may use the space directly "above"
the top of this stack.
Version Control
You can maintain multiple versions of a shared library using library-level
versioning. This allows you to make incompatible changes to shared libraries
and ensure programs linked with the older versions continue to run. (See
Library-Level Versioning for more information.)
Debugger Limitations
You can debug shared libraries just like archive libraries with
few exceptions. Support is provided by the WDB Debugger. See the
WDB documentation at: http://www.hp.com/go/wdb
Using the chroot Command with Shared Libraries
Some users may use the chroot super-user command while
developing and using shared libraries. This affects the path name that
the linker stores in the executable file. For example, if you chroot
to the directory /users/hyperturbo and develop an application
there that uses the shared library libhype.so in the same
directory, ld records the path name of the library as /libhype.so .
If you then exit from the chroot ed directory and attempt
to run the application, the dynamic loader cannot find the shared library
because it is actually stored in /users/hyperturbo/libhype.so ,
not in /libhype.so .
Conversely, if you move a program that uses shared libraries into
a chroot ed environment, you must have a copy of the dynamic
loader, dld.so , and all required shared libraries in the
correct locations.
Profiling Limitations
Profiling with the prof (1) and gprof (1)
commands and the monitor library function is only possible
on a contiguous chunk of the main program (a.out ). Since
shared libraries are not contiguous with the main program in virtual memory,
they cannot be profiled. You can still profile the main program, though.
If profiling of libraries is required, relink the application with the
archive version of the library, using the -a archive option.
|
Summary of HP-UX Libraries |
What libraries your system has depends on what components were purchased.
For example, if you did not purchase Starbase Display List, you cannot have
the Starbase Display List library on your system.
HP-UX library routines are described in detail in sections 2 and
3 of the HP-UX Reference. Routines in section 2 are known
as system calls, because they provide low-level system
services; they are found in libc . Routines in section 3 are
other "higher-level" library routines and are found in several
different libraries including libc .
Each library routine, or group of library routines, is documented
on a man page. Man pages are sorted alphabetically by
routine name and have the general form routine(nL),
where:
- routine
-
is the name of the routine, or group of closely related routines,
being documented.
- n
-
is the HP-UX Reference section number: 2 for system
calls, 3 for other library routines.
- L
-
is a letter designating the library in which the routine is
stored.
For example, the printf(3S) manpage describes the standard
input/output libc routines printf , nl_printf ,
fprintf , nl_fprintf , sprintf , and
nl_sprintf . And the pipe(2) manpage describes
the pipe system call.
The major library groups defined in the HP-UX Reference
are shown below:
NoteCertain language-specific libraries are not documented in the HP-UX
Reference; instead, they are documented with the appropriate language
documentation. For example, all FORTRAN intrinsics (MAX ,
MOD , and so forth) are documented in the HP FORTRAN/9000
Programmer's Reference.
- Group
-
Description
- (2)
-
These functions are known as system calls. They provide low-level
access to operating system services, such as opening files, setting up
signal handlers, and process control. These routines are located in libc .
- (3C)
-
These are standard C library routines located in libc .
- (3E)
-
These functions constitute the ELF access library (libelf )
which lets a program manipulate ELF (Executable and Linking Format)
object files, archive files, and archive members. The linker searches
this library if the -lelf option is specified. The header
file <libelf.h > provides type and function declarations
for all library services (described in elf(3E).
- (3S)
-
These functions comprise the Standard input/output
routines (see stdio(3S)). They are located in libc .
- (3M)
-
These functions comprise the Math library. The linker
searches this library under the -lm option (for the SVID
math library) or the -lM option (for the POSIX math library).
- (3G)
-
These functions comprise the Graphics library.
- (3I)
-
These functions comprise the Instrument support library.
- (3X)
-
Various specialized libraries. The names of the libraries in
which these routines reside are documented on the manpage.
The routines marked by (2), (3C), and (3S) comprise the standard
C library libc . The C, C++, and FORTRAN compilers automatically
link with this library when creating an executable program.
For more information on these libraries, see C, A Reference
Manual by Samual P. Harbison and Guy L. Steele Jr., published in
1991 by Prentice-Hall, or UNIX System V Libraries by Baird
Peterson, published in 1992 by Van Nostrand Reinhold, or C Programming
for UNIX by John Valley, published in 1992 by Sams Publishing.
For more information on system calls, see Advanced UNIX Programming
by Marc J. Rochkind, published in 1985 by Prentice-Hall or Advanced
Programming in the UNIX Environment by W. Richard Stevens, published
in 1992 by Addison-Wesley.
|
Caution When Mixing Shared and Archive Libraries |
Mixing shared and archive libraries in an application is not recommended
and must be avoided. That is, an application must use only
shared libraries or only archive libraries.
Mixing shared and archive libraries can lead to: unsatisfied symbols,
hidden definitions, duplicate definitions, and cause an application
to abort or exhibit incorrect behavior at run time. The following examples
illustrate some of these problems.
NoteThe examples in this section apply only to PA-32 compatibility mode.
Example 1: Unsatisfied Symbols
This example (in PA-32 and PA-64/IPF(+compat mode)
shows how mixing shared and archive libraries can cause a program to abort.
Suppose you have a main program, main() , and three functions,
f1() , f2() , and f3() each in a
separate source file. The program main() calls f1() and
f3() but not f2() :
$ cc -c main.c f1.c f2.c //Compile to relocatable object code.
$ cc -c +z f3.c //Compile to position-independent code
Figure 12: Example 1: Unsatisfied Symbols: Compiling
Next, suppose you put f3.o into the shared library lib3.so
and f1.o and f2.o into the archive library lib12.a :
$ ld -b -o lib3.so f3.o Create a shared library.
$ ar qvc lib12.a f1.o f2.o Create an archive library.
Figure 13: Example 1: Unsatisfied Symbols: Mixing Shared and Archive Libraries
Now link the main with the libraries, and create the executable a.out :
$ cc main.o lib12.a lib3.so Link the program .
Figure 14: Example 1: Unsatisfied Symbols: Creating the Executable
When you run a.out , it runs correctly. Now, suppose
you need to modify f3() to call f2() :
Figure 15: Example 1: Unsatisfied Symbols: Modifying a Function
Compile the new f3() and rebuild the shared library
lib3.so :
$ cc -c +z f3.c Compile to relocatable code.
$ ld -b -o lib3.so f3.o Create a new shared library
Figure 16: Example 1: Unsatisfied Symbols: Rebuilding the Shared Library
Problem
Here is where the problem can occur. If you do not relink
the application, main.o , and just run a.out
with the new version of lib3.so , the program aborts because
f2() is not available in the application. The reference to
f2() from f3() remains unsatisfied, producing
an error in PA-32 mode:
Figure 17: Example 1: Unsatisfied Symbols: Problem of Unsatisfied Symbol
$ a.out
/usr/lib/dld.so: Unresolved symbol: f2 (code) from
/users/steve/dev/lib3.so
Abort(coredump)
Example 2: Using shl_load(3X)
This example (in PA-32 and PA-64/IPF+compat mode) shows
how mixing archive libraries and shared libraries using shl_load(3X)
can lead to unsatisfied symbols and cause a program to abort.
If a library being loaded depends on a definition that does not
exist in the application or any of the dependent shared libraries, the
application aborts with an unsatisfied definition at run time. This
seems obvious enough when an application is first created. However, over
time, as the shared libraries evolve, new symbol imports may be introduced
that were not originally anticipated. This problem can be avoided by ensuring
that shared libraries maintain accurate dependency lists.
Suppose you have a main program, main() , and three
functions, f1() , f2() , and f3()
each in a separate source file. The program main() calls f1()
and uses shl_load() to call f3() . The program main()
does not call f2() :
$ cc -c main.c f1.c f2.c Compile to relocatable object code
$ cc -c +z f3.c Compile to position-independent code
Figure 18: Example 2: Using shl_load(3X): Compiling
Next, suppose you put f3.o into the shared library lib3.so
and f1.o and f2.o into the archive library lib12.a :
$ ld -b -o lib3.so f3.o Create a shared library.
$ ar qvc lib12.a f1.o f2.o Create an archive library.
Figure 19: Example 2: Using shl_load(3X): Mixing Shared and Archive Libraries
Now link the main with the archive library, and create the executable
a.out :
$ cc main.o lib12.a -ldld Link the program.
Figure 20: Example 2: Using shl_load(3X): Creating the Executable
When you run a.out , it runs correctly. Now suppose
you need to modify f3() to call f2() :
Figure 21: Example 2: Using shl_load(3X): Modifying a Function
Problem
Here is where a problem can be introduced. If you compile the new
f3() and rebuild the shared library lib3.so
>without specifying the dependency on a shared library containing
f2() , calls to f3() aborts.
$ cc -c +z f3.c Compile to position-independent code.
$ ld -b -o lib3.so f3.o Error! Missing library containing f2().
Figure 22: Example 2: Using shl_load(3X): Rebuilding the Shared Library
Here's where the problem shows up. If you do not relink
the application, main.o , and just run a.out
with the new version of lib3.so , the program aborts since
f2() is not available in the program's address space. The
reference to f2() from f3() remains unsatisfied,
generating the PA-32 error message:
Figure 23: Example 2: Using shl_load(3X): Problem of Unsatisfied Symbol
$ a.out
Illegal instruction (coredump)
Example 3: Hidden Definitions
This example shows how mixing archive libraries and shared libraries
can lead to multiple definitions in the application and unexpected results.
If one of the definitions happens to be a data symbol, the results can
be catastrophic. If any of the definitions are code symbols, different
versions of the same routine can end up being used in the application.
This can lead to incompatibilities.
Duplicate definitions can occur when a dependent shared library
is updated to refer to a symbol contained in the program file, but not
visible to the shared library. The new symbol import must be satisfied
somehow by either adding the symbol to the library or by updating the
shared library dependency list. Otherwise the application must be relinked.
Using an archive version of libc in an application
using shared libraries is the most common cause of duplicate definitions.
Remember that symbols not referenced by a shared library at link time
are not exported by default.
NoteDuplicate definitions can be avoided if any or all symbols that
may be referenced by a shared library are exported from the application
at link time. Shared libraries always reference the first occurrence of
a definition. In the following example, the first definition is in the
executable file, a.out . See the -E option and
+e symbol option described in ld(1)
and Exporting Symbols from main with -E , Exporting Symbols with +ee, and Exporting
Symbols with +e.
The following example illustrates this situation. Suppose you have
a main program, main() , and three functions, f1() ,
f2() , and f3() each in a separate source file.
main() calls f1() , f2() , and f3() .
$ cc -c main.c Compile to relocatable code.
$ cc -c +z f1.c f2.c f3.c Compile to position-independent code.
Figure 24: Example 3: Hidden Definitions: Compiling
Next suppose you put f3.o into the shared library lib3.so
and f1.o and f2.o into the archive library lib12.a .
Also put f1.o and f2.o into the shared library
lib12.so :
$ ld -b -o lib3.so f3.o Create a shared library.
$ ld -b -o lib12.so f1.o f2.o Create a shared library.
$ ar qvc lib12.a f1.o f2.o Create an archive library.
Figure 25: Example 3: Hidden Definitions: Mixing Shared and Archive Libraries
Now link the main with the archive library lib12.a
and the shared library lib3.so , and create the executable
a.out :
$ cc main.o lib12.a lib3.so Link the program.
Figure 26: Example 3: Hidden Definitions: Creating the Executable
When you run a.out , it runs correctly. Now, suppose
you need to modify f3() to call f2() :
Figure 27: Example 3: Hidden Definitions: Modifying a Function
Compile the new f3() , and rebuild the shared library
lib3.so including the new dependency on f2()
in lib12.so :
$ cc -c +z f3.c Compile to PIC.
$ ld -b -o lib3.so f3.o -L . -l12 Create library with dependency.
Figure 28: Example 3: Hidden Definitions: Rebuilding the Shared Library
Problem
Here is where the problem can occur in PA-32 +compat
modes. If you do not relink the application, main.o ,
and just run a.out with the new version of lib3.so ,
the program executes successfully, but it executes two different
versions of f2() . main() calls f2()
in the program file a.out and f3() calls f2()
in lib12.so . Even though f2() is contained within
the application, it is not visible to the shared library, lib3.so .
Figure 29: Example 3: Hidden Definitions: Problem of Unsatisfied Symbol
Summary of Mixing Shared and Archive Libraries
Applications that depend on shared libraries must not use archive
libraries to satisfy symbol imports in a shared library. This suggests
that only a shared version of libc must be used in applications
using shared libraries. If an archive version of a dependent library must
be used, all of its definitions must be explicitly exported with the
-E or +e options to the linker to avoid multiple
definitions.
Providers of shared libraries must make every effort to prevent
these kinds of problems. In particular, if a shared library provider allows
unsatisfied symbols to be satisfied by an archive version of libc ,
the application that uses the library may fail if the shared library is
later updated and any new libc dependencies are introduced.
New dependencies in shared libraries can be satisfied by maintaining accurate
dependency lists. However, this can lead to multiple occurrences of the
same definition in an application if the definitions are not explicitly
exported.
|
Using Shared Libraries in Default Mode |
HP provides an industry-standard linker toolset for programs linked
in IPF mode. The new toolset consists of a linker, dynamic loader, object
file class library, and an object file tool collection. Although compatibility
between the current and previous toolset is an important goal, some differences
exist between these toolsets.
The IPF linker toolset introduces different types of shared libraries.
(In SVR4 Unix, shared libraries are sometimes called dlls.)
Compatibility
mode shared library: Using the IPF linker, a compatibility mode
shared library is basically a library built with ld -b +compat
that has dependent shared libraries. The +compat option affects
the way the linker and loader search for dependent libraries of a shared
library and records their names.
Standard
mode shared library: A standard mode shared library is a library
built with ld -b or ld -b +std with dependent
shared libraries.
NoteIf you specify ld -b +compat with no dependent libraries,
you must create a shared library that has no mode - neither compatibility mode
nor standard mode.
The linker handles these libraries in different way with regard
to internal naming and library search modes.
Internal Name Processing
For both PA-32 mode and IPF/PA-64 mode, you must specify shared library
internal names using ld +h name However, their
dependent libraries' internal names may not be recorded the same way in
a standard mode link.
The linker treats shared library names as follows:
If the dependent shared library has an internal
name, it is recorded in the DT_NEEDED entry.
If the dependent shared library is specified
with the -l or -l: option, only the libname.ext
is recorded in the DT_NEEDED entry.
The path of the dependent shared library as
seen on the link line is recorded in the DT_NEEDED entry.
All DT_NEEDED entries with no
"/" in the libname are subject to dynamic path lookup.
In an ld +compat compatibility-mode link, the linker
treats the internal names like it does in PA-32 mode:
If the dependent library's internal name is
rooted (starts with "/"), it is inserted as is in the DT_HP_NEEDED
entry. If it was specified with -l , the dynamic path bit
is set to TRUE in the DT_HP_NEEDED entry.
If the dependent library's internal name contains
a relative path, the internal name is inserted at the end of the path
where the shared library is found at link time, replacing the library's
filename in the DT_HP_NEEDED entry. If the library is specified
with -l , the dynamic path bit is set to TRUE .
If the dependent library's internal name contains
no path, it is inserted at the end of the path where the shared library
is found at link time, replacing the library's filename. If the library
is specified with -l , the dynamic path bit is set to TRUE .
If the dependent shared library does not have
an internal name, the path where the library is found and the library
filename is recorded in the DT_HP_NEEDED entry. If specified
with -l , the dynamic path bit is set to TRUE .
If the shared libraries are specified with
a relative or no path in this mode, the linker expands the current working
directory when constructing the DT_HP_NEEDED entry. So instead
of getting something like ./libdk.so , you get /home/your_dir/libdk.so .
All DT_HP_NEEDED entries with
the dynamic path bit set are subject to dynamic path lookup.
Dynamic Path Searching for Shared Libraries
Any library whose name has no "/ " character
in it becomes a candidate for dynamic path searching. Also, the linker
always uses the LD_LIBRARY_PATH and the SHLIB_PATH
environment variable to add directories to the run time search path for
shared libraries, unless the ld +noenvvar option is set.
In PA-32 compatibility mode of the linker toolset (selected by the
+compat option), the linker enables run-time dynamic path searching when
you link a program with the -l library and +b
path_name options. Or, you can use the -l library
option with the +s path_name option. When programs
are linked with +s , the dynamic loader searches directories
specified by the SHLIB_PATH environment variable to find
shared libraries.
The following example shows dynamic path searching changes for default
mode.
$ ld main.o \ Subject to
-lfoo -o main dynamic path searching.
The dynamic loader searches for libfoo.so in the directories
specified by the LD_LIBRARY_PATH and SHLIB_PATH
environment variables.
Shared Library Symbol Binding Semantics
Symbol binding resolution, both at link time and run time, changes
slightly in the IPF and PA-64 HP-UX linker toolset. The symbol binding
policy is more compatible with other open systems.
This section covers the following topics:
Link-time symbol resolution in shared libraries
Resolution of unsatisfied shared library references
Promotion of uninitialized global variables
Symbol searching in dependent libraries
Link-Time Symbol Resolution in Shared Libraries
In the IPF and PA-64 linker toolset, the linker remembers all symbols
in a shared library for the duration of the link. Global definitions satisfy
trailing references, and unsatisfied symbols remaining in shared libraries
are reported.
The PA-32 linker does not remember the definition of a procedure
in a shared library unless it was referenced in previously scanned object
files.
If you have function names that are duplicated in a shared and archive
library, the IPF and PA-64 linker may reference a different version of
a procedure than is referenced by the PA-32 mode linker. This change can
lead to unexpected results.
For example, given these source files:
sharedlib.c
void afunc()
{
printf("\tin SHARED library procedure 'afunc'\n");
}
unsat.c
void bfunc()
{
afunc();
}
archive.c
void afunc()
{
printf ("\tin ARCHIVE library procedure 'afunc'\n");
}
main.c
main()
{
bfunc();
}
If these files are compiled and linked as:
$ cc -c main.c unsat.c archive.c
$ cc -c +z sharedlib.c
$ ld -b sharedlib.o -o libA.so
$ ar rv libB.a archive.o
$ cc main.o libA.so unsat.o libB.a -o test1
The PA-32 linker toolset produces:
$ test1
in ARCHIVE library procedure `afunc'
At link time, there is an outstanding unsatisfied symbol for afunc()
when libB is found. The exported symbol for afunc()
is not remembered after libA.so is scanned. At
run time, the afunc() symbol that is called is the one that
came from the archive library, which resides in test1 .
The IPF and PA-64 linker toolset produces:
$ test1
in SHARED library procedure `afunc'
The IPF and PA-64 linker remembers the symbol for afunc() ,
and archive.o is not pulled out of libB.a .
The shared library version of afunc is called during execution.
This behavior is consistent with other SVR4 systems.
Resolution of Unsatisfied Shared Library References
In the IPF and PA-64 linker toolset, the dynamic loader requires
that all symbols referenced by all loaded libraries be satisfied at the
appropriate time. This is consistent with other SVR4 systems.
The PA-32 linker toolset accepts unresolved symbols in some cases.
For example, if an entry point defined in an object file is never reachable
from the main program file, the unresolved symbol is allowed. You can
use the +vshlibunsats linker option to find unresolved symbols
in shared libraries.
For example, given these source files:
lib1.c
void a()
{
}
lib2.c
extern int unsat;
void b()
{
unsat = 14;
}
main.c
main()
{
a();
}
If these files are compiled and linked as:
$ cc -c main.c
$ cc -c +z lib1.c lib2.c
$ ld -b lib1.o lib2.o -o liba.so
$ cc main.o liba.so -o test2
Using the PA-32 linker, test2 executes without error.
The module in liba.so created from lib2.o is
determined to be unreachable during execution, so the global symbol for
unsat (in lib2.o ) is not bound.
The IPF and PA-64 linker toolset reports an unsatisfied symbol error
for unsat at link time or at run time if the program were
made executable.
Promotion of Uninitialized Global Data Items
In the IPF and PA-64 linker toolset, the linker expands and promotes
uninitialized global data symbols in object files linked into a shared
library to global data objects, exactly like executable files. This is
standard with other SVR4 systems.
The result of this change is that load-time symbol resolution for
one of these objects stops at the first one encountered, instead of continuing
through all loaded libraries to see if an initialized data object exists.
For example, given these source files:
a.c
int object; /* Uninitialized global data symbol */
void a()
{
printf ("\tobject is %d\n", object);
}
b.c
int object =1; /* Initialized global data symbol */
void b()
{
}
main.c
main()
{
a();
}
If these files are compiled and linked as:
$ cc -c main.c
$ cc -c +z a.c b.c
$ ld -b a.o -o libA.so
$ ld -b b.o -o libB.so
$ cc main.o libA.so libB.so -o test3
The PA-32 linker toolset produces:
$ test3
object is 1
The PA-32 linker toolset defines the object global
variable in libA.so as a storage export symbol.
The dynamic loader, when searching for a definition of object
to satisfy the import request in libA.so , does not stop with
the storage export in that library. It continues to see if there is a
data export symbol for the same symbol definition.
The IPF linker and PA-64 toolset produces:
$ test 3
object is 0
The IPF and PA-64 linker toolset does not allow storage exports
from a shared library. The uninitialized variable called object
in a.o turns into a data export in libA.so ,
with an initial value of 0. During symbol resolution for the import request
from that same library, the dynamic loader stops with the first seen definition.
Symbol Searching in Dependent Libraries
In the IPF and PA-64 linker toolset, the linker searches dependent
libraries in a breadth-first order for symbol resolution. This
means it searches libraries linked to the main program file before libraries
linked to shared libraries. This behavior change is consistent with other
SVR4 systems. The linker also searches siblings of a dependent shared
library before its children. For example, using the structure described
in Figure 30: Search Order of Dependent Libraries,
if libD had dependent libraries libDK and libLH, libD, libE,
libF, then
libDK, libLH is searched in that order. The dlopen
library management routines and executable files (a.out )
created by the linker with the +std option (default) use
the breadth-first search order.
The PA-32 linker toolset searches dependent libraries in a depth-first
order. This means it searches dependent shared library files in the order
in which they are linked to shared libraries. The shl_load
library management routines and executable files (a.out )
created by the linker with the +compat option use the depth-first
search order.
NoteIf you have data or function names that are duplicated in different
shared libraries, the IPF and PA-64 linker may link in a different version
of a procedure than the current release. This can lead to unexpected results.
Figure 30: Search Order of Dependent Libraries
shows an example program with shared libraries (the shaded boxes are libA.so
dependent libraries; and the example does not consider libDK
or libLH ) and compares the two search methods:
Figure 30: Search Order of Dependent
Libraries
The commands to build the libraries and the executable in Figure
30: Search Order of Dependent Libraries are given below. Note the
link order of libraries in steps 2 and 3:
-
First, the dependent shared libraries for libA
are built. (Other libraries are also built.)
$ ld -b libDK.o -o libDK.so
$ ld -b libLH.o -o libLH.so
$ ld -b libD.o -IDK -ILH -o libD.so libA dependent shared library
$ ld -b libE.o -o libE.so libA dependent shared library
$ ld -b libF.o -o libF.so libA dependent shared library
$ ld -b libB.o -o libB.so
$ ld -b libC.o -o libC.so
-
Next, libA.o is linked to its dependent libraries
and libA.so is built.
$ ld -b libA.o -lD -lE -lF -o libA.so
-
Finally, main.o is linked to its shared libraries.
$ cc main.o -lA -lB -lC -o main
If a procedure called same_name() is defined in libD.so
and libB.so , main calls same_name()
in libB.so .
If you use mixed mode shared libraries, the search mechanism may
produce unexpected results.
For the following command, libA.so and its dependent
libB.so are compatibility mode libraries, and libC.so
and libD.so are standard mode libraries.
$ ld -b libF.o +compat -L.-lA -lC -o libF.so
The libF.so library is a compatibility
mode library, but its dependent libC.so is a standard mode
library. The linker uses depth-first searching mechanisms because the
highest-level library is in compatibility mode.
Mixed Mode Shared Libraries
A mixed mode shared library is a library whose children are all
of one type (for example, +compat), but whose grandchildren may be of
the other mode. This poses some problems when linking and loading the
libraries. To help resolve this, the linker treats each library as having
any mode. Therefore, when it sees a compatibility mode library, it searches
for it using the PA-32-style algorithms. For any standard mode library,
it uses the IPF/PA-64-style algorithms. Only the load order of the libraries
themselves is fixed between depth-first or breadth-first.
If you use mixed mode shared libraries, the behavior is based on
the first mode encountered. At runtime, the dynamic loader does a depth-first
search if the dependent libraries at the highest level are compatibility
mode libraries. Otherwise, it does breadth-first searching. This applies
to all dependent libraries of the incomplete executable file. The loader
cannot toggle back and forth between depth-first and breadth-first at
the library level, so the first dependent library it sees determines which
search method to use.
Example:
# build standard mode dlls
# libfile1.so is a dependent of libfile2.so
$ ld -b file1.o -o libfile1.so +h libfile1.1
$ ld -b file2.o -o libfile2.so +h libfile2.1 -L. -lfile1
# build compatibility mode dlls
# libfile3.so is a dependent of libfile4.so
$ ld -b file3.o -o libfile3.so +h libfile3.1
$ ld -b file4.o -o libfile4.so +h libfile4.1 -L. -lfile3 +compat
$ ln -s libfile1.so libfile1.1
$ ln -s libfile3.so libfile3.1
# build a dll using both standard and compatibility mode dependent dlls
# since we didn't specify +compat, the resulting dll is a standard mode dll
$ ld -b file5.o -o libfile5.so +h libfile5.1 -L. -lfile4 -lfile2
$ ln -s libfile4.so libfile4.1
$ ln -s libfile2.so libfile2.1
$ ld main.o -L. -lfile5 -lc
The resulting a.out has standard mode dependents, libfile5.so
and libc.so . libfile5.so have two dependents,:
libfile4.so and libfile2.so . The libfile4.so
library is a compatibility mode library, and has a dependent, libfile3.so .
libfile2.so is a standard mode library, and has a dependent,
libfile1.so . The dynamic loader does a breadth-first search
of all dependent libraries needed by a.out because the link
was done without +compat and libfile5.so is
a standard mode library. The loader uses IPF/PA-64 search techniques on
all libraries except for libfile3.so , in which case it uses
PA-32 search techniques.
NoteEmbedded path inheritance is not applied to any mixed mode shared
library and its descendents. It is only applied to libraries in an a.out
linked with +compat . Embedded path inheritance does not apply
to a breadth-first search mechanism.
IPF Library Examples
The examples demonstrate the behavior of compatibility and standard
mode shared libraries created by the IPF linker toolset:
Library Example: Creating an IPF Compatibility Mode
Shared Library
The following example creates a compatibility mode shared library:
$ ld -b file1.o -o libfile1.so +h libfile1.1
$ ld -b file2.o -o libfile2.so +h ./libfile2.1
$ ld -b file3.o -o libfile3.so +h /var/tmp/libfile3.1
$ ld -b file4.o -o libfile4.so
$ ld -b +compat file3a.o -o libfile3a.so -L. -lfile -lfile3 +h libfile3a.1
$ ld -b +compat file2a.o -o libfile2a.so libfile2.so ./libfile4.so +b /var/tmp
$ elfdump -L libfile3a.so libfile2a.so
libfile3a.so:
*** Dynamic Section ***
Index Tag Value
0 HPNeeded 1:./libfile1.1
1 HPNeeded 1:/var/tmp/libfile3.1
2 Soname libfile3a.1
...
libfile2a.so:
*** Dynamic Section ***
Index Tag Value
0 HPNeeded 0:/home/knish/./libfile2.1
1 HPNeeded 0:./libfile4.so
2 Rpath /var/tmp
...
Library Example: Creating an IPF Standard Mode Shared
Library
The following example builds a standard mode library:
$ ld -b file1.o -o libfile1.so +h libfile1.1
$ ld -b file2.o -o libfile2.so +h ./libfile2.1
$ ld -b file3.o -o libfile3.so +h /var/tmp/libfile3.1
$ ld -b file4.o -o libfile4.so
$ ld -b file3a.o -o libfile3a.so -L. -lfile1 -lfile3 +h libfile3a.1
$ ld -b file2a.o -o libfile2a.so libfile2.so ./libfile4.so +b /var/tmp
$ elfdump -L libfile3a.so libfile2a.so
libfile3a.so:
*** Dynamic Section ***
Index Tag Value/Ptr
0 Needed libfile1.1
1 Needed /var/tmp/libfile3.1
2 Soname libfile3a.1
3 Rpath .
...
libfile2a.so:
*** Dynamic Section ***
Index Tag Value/Ptr0 Needed ./libfile2.1
1 Needed ./libfile4.so
2 Rpath /var/tmp
...
The dynamic loader does dynamic path searching for libfile1.so .
It does not do dynamic path searching for libfile2.so , libfile3.so ,
and libfile4.so .
Library example: IPF Dynamic Path Searching
This example of dynamic path searching demonstrates differences
between compatibility mode and standard mode dependent shared libraries.
The example builds standard mode libraries and does a standard mode link.
By default, the dynamic loader looks at the environment variables LD_LIBRARY_PATH
and SHLIB_PATH to find the shared libraries.
# build standard mode shared libraries
#libfile1.so is a dependent of libfile2.so
$ ld -b file1.o -o libfile1.so +h libfile1.1
$ ld -b file2.o -o libfile2.so +h libfile2.1 -L. -lfile1
$ ld main.o -L. -lfile2 -lc
# move dependent lib so dld can't find it
# dld won't find library because we didn't set the environment
# variable LD_LIBRARY_PATH and SHLIB_PATH
# By default, dld will look at the environment variables
# LD_LIBRARY_PATH and
# SHLIB_PATH when doing dynamic path searching unless +noenvvar
# is specified
$ mv libfile2.so /var/tmp
$ ln -s /var/tmp/libfile2.so /var/tmp/libfile2.1
$ a.out
dld.so: Unable to find library 'libfile2.1'
$ export SHLIB_PATH=/var/tmp
$ a.out
in file1
in file2
Library Example: IPF Compatibility Mode Link
This example builds a compatibility mode library and does a compatibility
mode link. The +s option is not specified at link time, so
the dynamic loader does not look at any environment variables to do dynamic
path searching.
# build compatibility mode dlls
# libfile1.so is a dependent of libfile2.so
ld -b file1.o -o libfile1.so +h libfile1.1
ld -b file2.o -o libfile2.so +h libfile2.1 -L. -lfile1 +compat
ln -s libfile1.so libfile1.1
ld main.o +compat -L. -lfile2 -lc
# move dependent lib so dld can't find it. Even when we specify SHLIB_PATH dld won't be
# able to find the dependent because we didn't link with +s
mv libfile2.so /var/tmp
ln -s /var/tmp/libfile2.so /var/tmp/libfile2.1
a.out
dld.so: Unable to find library '1:./libfile2.1'
export SHLIB_PATH=/var/tmp
a.out
dld.so: Unable to find library '1:./libfile2.1'
You can use chatr +s to enable a.out in
file1 and file2 :
$ chatr +s enable a.out
Library Example: Using IPF Compatibility and Standard
Shared Libraries
This example mixes compatibility and standard mode shared libraries.
It uses PA-32-style linking and loading for the compatibility mode libraries
and IPF/PA-64-style linking and loading for standard mode libraries.
# build standard mode dlls
# libfile1.so is a dependent of libfile2
$ ld -b file1.o -o libfile1.so +h libfile1.1
$ mkdir TMP
$ ld -b +b $pwd/TMP file2.o -o libfile2.so +h libfile2.1 -L. -lfile1
# build compatibility mode dlls
# libfile3.so is a dependent of libfile4
$ ld -b file3.o -o libfile3.so +h libfile3.1
$ ld -b file4.o -o libfile4.so +b $pwd/TMP +h libfile4.1 +compat -L. -lfile3
$ ln -s libfile1.so libfile1.1
$ ln -s libfile3.so libfile3.1
$ mv libfile1.so TMP
$ mv libfile3.so TMP
$ cd TMP
$ ln -s libfile1.so libfile1.1
$ ln -s libfile3.so libfile3.1
$ cd ..# link with +b so ld will use RPATH at link time to find
# libfile1.so (standard mode dll)
# the linker will not use RPATH to find libfile3.so
# (compatibility mode dll)
# Note that this is true in both a standard mode link and a
# compatibility mode link. The
# linker never uses RPATH to find any compatibility mode dlls
$ ld -b +b pwd/TMP main.o -o libfile5.so +h libfile5.1 -L. -lfile2 -lfile4
ld: Can't find dependent library "./libfile3.so"
$ ld -b +b pwd/TMP main.o -o libfile5a.so +h libfile5.1 -L. -lfile2 -lfile4 +compat
ld: Can't find dependent library "./libfile3.so"
Comparing Breadth-first and Depth-first Search in IPF/PA-64
Mode
For the following libraries with dependencies:
lib1.so has dependents lib2.so, lib3.so, and lib4.so
lib2.so has dependents lib2a.so and lib2b.so
lib3.so has dependents lib3a.so and lib3b.so
lib3a.so has dependent lib3aa.so
+-->lib2a.so
|
+-->lib2.so-->lib2b.so
|
lib1.so-->lib3.so.so-->lib3a.so-->lib3aa.so
| |
| +-->lib3b.so
+-->lib4.so
In breadth-first searching, the load order is siblings before children:
lib1.so->lib2.so->lib3.so->lib4.so->lib2a.so->lib2b.so->lib3a.so->lib3b.so->lib3aa.so
In depth-first searching, the load order is children before siblings:
lib1.so->lib2.so->lib2a.so->lib2b.so->lib3.so->lib3a.so->lib3aa.so->lib3b.so->lib4.so
Library Example: Using RPATH with Standard
Mode Shared Library
In the following example, the linker uses the embedded RPATH at
link time to find the dependent library. For compatibility mode shared
libraries, embedded RPATHs are ignored.
$ ld -b bar.o -o libbar.so
$ ld -b foo.o -o libfoo.so -L. -lbar +b /var/tmp
# ld should look in /var/tmp to find libbar.so since libfoo.so
# has an embedded RPATH of
# /var/tmp
$ mv libbar.so /var/tmp
$ ld main.o -L. -lfoo -lc
# For compatibility mode dlls, embedded RPATHs are ignored
$ ld -b bar.o -o libbar.so
$ ld -b foo.o -o libfoo.so +compat -L. -lbar +b /var/tmp
# ld won't find libbar.so since it does not look at embedded RPATHs
$ mv libbar.so /var/tmp
$ ld main.o -L. -lfoo +compat -lc
ld: Can't find dependent library "libbar.so"
Fatal error.
Linking Libraries with +b pathlist
The following examples compare PA-32 and IPF/PA-64 linking
with the ld +b pathlist option. If you do not use the +b
option, the dynamic loader uses the directory specified by the -L option at link time
for dynamic library lookup at run time.
Library Example: Linking to Libraries with +b
path_list in IPF/PA-64 Mode
In this example, the program main calls a shared library
routine in libbar.so . The routine in libbar.so
in turn calls a routine in the shared library libme.so . The
+b linker option indicates the search path for libme.so
while linking libbar.so . (You use +b path_list
with libraries specified with the -l library or
-l:library options.)
$ cc -c me.c
$ ld -b me.o -o libme.so
$ ld -b bar.o -o libbar.so -L. -lme +b /var/tmp
$ mv libme.so /var/tmp
$ ld main.o -L. -lbar -lc
In IPF/PA-64 mode, the linker finds libme.so in /var/tmp
because the +b /var/tmp option is used when linking libbar.so .
Because -lme was specified while linking libbar.so ,
libme.so is subject to run-time dynamic path searching.
When linking main.o , the link order in the above example
is:
./libbar.so
found
./libme.so
not found
/var/tmp/libme.so
found
./libc.so
not found>
/usr/lib/hpux32/libc.so
found
In the above example, if you type:
$ ld main.o
-L. -lbar -lc
$ mv libme.so /var/tmp
instead of:
$ mv libme.so
/var/tmp
$ ld main.o -L. -lbar -lc
the linker finds libme.so in ./ at link
time, and the dynamic loader finds libme.so in /var/tmp
at run time.
At run time, the dynamic loader searches paths to resolve external
references made by main in the following order:
LD_LIBRARY_PATH
to find libbar.so not found
SHLIB_PATH
to find libbar.so not found
./libbar.so
(./libbar.so ) found
LD_LIBRARY_PATH
to find libme.so not found
SHLIB_PATH
to find libme.so not found
/var/tmp/libme.so
found
LD_LIBRARY_PATH
to find libc.so not found
SHLIB_PATH
to find libc.so not found
./libc.so
not found
-
/usr/lib/hpux32/libc.so
found
Library Example: Linking to Libraries with +b
path_list in PA-32 Mode
This example is the same as Library Example:
Linking to Libraries with +b path_list in IPF/PA-64
Mode, but this time the program is compiled in PA-32 mode.
$ cc -c
+DD32 me.c bar.c main.c
$ ld -b me.o -o libme.so
$ ld +compat -b bar.o -o libbar.so -L. -lme +b /var/tmp \PA-32 mode
library created
$ ld main.o -L. -lbar -lc
$ mv libme.so /var/tmp
When linking main.o , the link order is:
./libbar.so
found
./libme.so
found
./libc.so
not found
/usr/lib/libc.so
found
In the above example, if you type:
$ mv libme.so
/var/tmp
$ ld main.o -L. -lbar -lc
instead of:
$ ld main.o
-L. -lbar -lc
$ mv libme.so /var/tmp
the linker issues the following error message:
ld: Can't find dependent library ./libme.so
Fatal Error
The linker does not look in /var/tmp to find shared
libraries because in PA-32 mode the directories specified by +b
pathname are only searched at run time.
Because libme.so is specified with the -l
option, it is subject to dynamic path searching.
At run time, the dynamic loader looks for shared libraries used
by main in the following order:
./libbar.so
found
/var/tmp/libme.so
found
./libc.so
not found
/usr/lib/libc.so
found
|
|