search    
HP-UX Linker and Libraries User's Guide
Hewlett-Packard
Creating and Using Libraries

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:

  • For 32-bit mode: ld prog.o -lc

  • For 64-bit mode: ld prog.o -lc

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:

  1. The conf file is present
  2. 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
  1. Compile one or more source files to create object files containing relocatable object code.

  2. 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:

  1. 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.

  2. 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:

  1. A header string, "!<arch>\n", identifying the file as an archive file created by ar (\n represents the newline character)

  2. 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

  3. 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)

  4. 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

Delete 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:

  • You usually need super-user (system administrator) privileges to write to the directory.

  • You may inadvertently overwrite an HP-UX system library that resides in the directory.

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:

  1. 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. 

  2. 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:

  1. mark P, traverse Q

  2. mark Q, traverse B

  3. mark B, load B

  4. load Q

  5. traverse D

  6. mark D, traverse B

  7. B is already marked, so skip B, traverse Q

  8. Q is already marked, so skip Q

  9. load D

  10. mark A, load A

  11. 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:

  1. mark P, load P

  2. traverse P

  3. mark A, load A

  4. mark D, load D

  5. mark Q, load Q

  6. traverse D

  7. Q is already marked, so skip Q

  8. mark B, load B

  9. traverse Q

  10. 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.


Note

Using 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:

  1. mark B, load B

  2. mark A, traverse D, load D

  3. load C

  4. 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:

  1. mark A, traverse D, load D

  2. load C

  3. load A

  4. 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:

  1. mark B, load B

  2. mark A, traverse D, load D

  3. load C

  4. 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:

  1. 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.

  2. 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.
  3. 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
    	


Note

The +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.


Note

Beginning 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:

  1. 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. 
    		
  2. 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.
    		
  3. 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.


Note

For 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 chrooted 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 chrooted 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:


Note

Certain 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.


Note

The 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.


Note

Duplicate 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.


Note

If 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 -llibrary and +b path_name options. Or, you can use the -llibrary 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.


Note

If 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:

  1. 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 
    		
  2. Next, libA.o is linked to its dependent libraries and libA.so is built.

    		$ ld -b libA.o -lD -lE -lF -o libA.so 
    		
  3. 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.


Note

Embedded 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:

  1. ./libbar.so found

  2. ./libme.so not found

  3. /var/tmp/libme.so found

  4. ./libc.so not found>

  5. /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:

  1. LD_LIBRARY_PATH to find libbar.so not found

  2. SHLIB_PATH to find libbar.so not found

  3. ./libbar.so (./libbar.so) found

  4. LD_LIBRARY_PATH to find libme.so not found

  5. SHLIB_PATH to find libme.so not found

  6. /var/tmp/libme.so found

  7. LD_LIBRARY_PATH to find libc.so not found

  8. SHLIB_PATH to find libc.so not found

  9. ./libc.so not found

  10. /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:

  1. ./libbar.so found

  2. ./libme.so found

  3. ./libc.so not found

  4. /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:

  1. ./libbar.so found

  2. /var/tmp/libme.so found

  3. ./libc.so not found

  4. /usr/lib/libc.so found