Jump to content United States-English
HP.com Home Products and Services Support and Drivers Solutions How to Buy
» Contact HP
More options
HP.com home
HP-UX Linker and Libraries User's Guide: HP 9000 Computers > Chapter 3 Linker Tasks

Using Linker commands

» 

Technical documentation

Complete book in PDF
» Feedback
Content starts here

 » Table of Contents

 » Glossary

 » Index

This section describes linker commands for the 32-bit and 64-bit linker.

NOTE: Unless otherwise noted, all examples show 32-bit behavior.

Linking with the 32-bit crt0.o Startup File

In 32-bit mode, you must always include crt0.o on the link line.

In 64-bit mode, you must include crt0.o on the link line for all fully archive links (ld -noshared) and in compatibility mode (+compat). You do not need to include the crt0.o startup file on the ld command line for shared bound links. In 64-bit mode, the dynamic loader, dld.sl, does some of the startup duties previously done by crt0.o.

See “The crt0.o Startup File”, and the crt0(3) man page for more information.

Changing the Default Library Search Path with -L and LPATH

You can change or override the default linker search path by using the LPATH environment variable or the -L linker option.

Overriding the Default Linker Search Path with LPATH

The LPATH environment variable allows you to specify which directories ld should search. If LPATH is not set, ld searches the default directory /usr/lib. If LPATH is set, ld searches only the directories specified in LPATH; the default directories are not searched unless they are specified in LPATH.

If set, LPATH should contain a list of colon-separated directory path names ld should search. For example, to include /usr/local/lib in the search path after the default directories, set LPATH as follows:

$ LPATH=/usr/lib:/usr/local/lib     Korn and Bourne shell syntax.
$ export LPATH

Augmenting the Default Linker Search Path with -L

The -L option to ld also allows you to add additional directories to the search path. If -L libpath is specified, ld searches the libpath directory before the default places.

For example, suppose you have a locally developed version of libc, which resides in the directory /usr/local/lib. To make ld find this version of libc before the default libc, use the -L option as follows:

$ ld /opt/langtools/lib/crt0.o prog.o -L /usr/local/lib -lc

Multiple -L options can be specified. For example, to search /usr/contrib/lib and /usr/local/lib before the default places:

$ ld /opt/langtools/lib/crt0.o prog.o -L /usr/contrib/lib  \
-L /usr/local/lib -lc

If LPATH is set, then the -L option specifies the directories to search before the directories specified in LPATH.

Changing the Default Shared Library Binding with -B

You might want to force immediate binding — that is, force all routines and data to be bound at startup time. With immediate binding, the overhead of binding occurs only at program startup, rather than across the program's execution. One possibly useful characteristic of immediate binding is that it causes any possible unresolved symbols to be detected at startup time, rather than during program execution. Another use of immediate binding is to get better interactive performance, if you don't mind program startup taking a little longer.

Example Using -B immediate

To force immediate binding, link an application with the -B immediate linker option. For example, to force immediate binding of all symbols in the main program and in all shared libraries linked with it, you could use this ld command:

$ ld -B immediate /opt/langtools/lib/crt0.o prog.o -lc -lm 

Nonfatal Shared Library Binding with -B nonfatal

The linker also supports nonfatal binding, which is useful with the -B immediate option. Like immediate binding, nonfatal immediate binding causes all required symbols to be bound at program startup. The main difference from immediate binding is that program execution continues even if the dynamic loader cannot resolve symbols. Compare this with immediate binding, where unresolved symbols cause the program to abort.

To use nonfatal binding, specify the -B nonfatal option along with the -B immediate option on the linker command line. The order of the options is not important, nor is the placement of the options on the line. For example, the following ld command uses nonfatal immediate binding:

$ ld /opt/langtools/lib/crt0.o prog.o -B nonfatal \
-B immediate -lm -lc

Note that the -B nonfatal modifier does not work with deferred binding because a symbol must have been bound by the time a program actually references or calls it. A program attempting to call or access a nonexistent symbol is a fatal error.

Restricted Shared Library Binding with -B restricted

The linker also supports restricted binding, which is useful with the -B deferred and -B nonfatal options. The -B restricted option causes the dynamic loader to restrict the search for symbols to those that were visible when the library was loaded. If the dynamic loader cannot find a symbol within the restricted set, a run-time symbol binding error occurs and the program aborts.

The -B nonfatal modifier alters this behavior slightly: If the dynamic loader cannot find a symbol in the restricted set, it looks in the global symbol set (the symbols defined in all libraries) to resolve the symbol. If it still cannot find the symbol, then a run-time symbol-binding error occurs and the program aborts.

When is -B restricted most useful? Consider a program that creates duplicate symbol definitions by either of these methods:

  • The program uses shl_load with the BIND_FIRST flag to load a library that contains symbol definitions that are already defined in a library that was loaded at program startup.

  • The program calls shl_definesym to define a symbol that is already defined in a library that was loaded at program startup.

If such a program is linked with -B immediate, references to symbols will be bound at program startup, regardless of whether duplicate symbols are created later by shl_load or shl_definesym.

But what happens when, to take advantage of the performance benefits of deferred binding, the same program is linked with -B deferred? If a duplicate, more visible symbol definition is created prior to referencing the symbol, it binds to the more visible definition, and the program might run incorrectly. In such cases, -B restricted is useful, because symbols bind the same way as they do with -B immediate, but actual binding is still deferred.

Improving Shared Library Performance with -B symbolic

The linker supports the -B symbolic option which optimizes call paths between procedures when building shared libraries. It does this by building direct internal call paths inside a shared library. In linker terms, import and export stubs are bypassed for calls within the library.

A benefit of -B symbolic is that it can help improve application performance and the resulting shared library will be slightly smaller. The -B symbolic option is useful for applications that make a lot of calls between procedures inside a shared library and when these same procedures are called by programs outside of the shared library.

NOTE: The -B symbolic option applies only to function, but not variable, references in a shared library.

Example Using -B symbolic

For example, to optimize the call path between procedures when building a shared library called lib1.sl, use -B symbolic as follows:

$ ld -B symbolic -b func1.o func2.o -o lib1.sl
NOTE: The +e option overrides the -B symbolic option. For example, you use +e symbol, only symbol is exported and all other symbols are hidden. Similarly, if you use +ee symbol, only symbol is exported, but other symbols exported by default remain visible.

Since all internal calls inside the shared library are resolved inside the shared library, user-supplied modules with the same name are not seen by routines inside the library. For example, you could not replace internal libc.sl malloc() calls with your own version of malloc() if libc.sl was linked with -B symbolic.

Comparing -B symbolic with -h and +e

Similar to the -h (hide symbol) and +e (export symbol) linker options, -B symbolic optimizes call paths in a shared library. However, unlike -h and +e, all functions in a shared library linked with -B symbolic are also visible outside of the shared library.

Case 1: Building a Shared Library with -B symbolic

Suppose you have two functions to place in a shared library. The convert_rtn() calls gal_to_liter().

  1. Build the shared library with -b. Optimize the call path inside the shared library with -B symbolic.

    $ ld -B symbolic -b convert.o volume.o -o libunits.sl
  2. Two main programs link to the shared library. main1 calls convert_rtn() and main2 calls gal_to_liter().

    $ cc -Aa main1.c libunits.sl -o main1
    $ cc -Aa main1.c libunits.sl -o main2

Figure 3-1 shows that a direct call path is established between convert_rtn() and gal_to_liter() inside the shared library. Both symbols are visible to outside callers.

Figure 3-1 Symbols inside a Shared Library Visible with -B symbolic

Symbols inside a Shared Library Visible with -B symbolic
Case 2: Building a Shared Library with -h or +e

The -h (hide symbol) and +e (export symbol) options can also optimize the call path in a shared library for symbols that are explicitly hidden. However, only the exported symbols are visible outside of the shared library.

For example, you could hide the gal_to_liter symbol as shown:

$ ld -b convert.o -h gal_to_liter volume.o -o libunits.sl

or export the convert_rtn symbol:

$ ld -b +e convert_rtn convert.o volume.o -o libunits.sl

In both cases, main2 will not be able to resolve its reference to gal_to_liter() because only the convert_rtn() symbol is exported as shown below:

Choosing Archive or Shared Libraries with -a

If both an archive and shared version of a particular library reside in the same directory, ld links with the shared version. Occasionally, you might want to override this behavior.

As an example, suppose you write an application that will run on a system on which shared libraries may not be present. Since the program could not run without the shared library, it would be best to link with the archive library, resulting in executable code that contains the required library routines. See also “Caution When Mixing Shared and Archive Libraries ”.

Option Settings to -a

The -a option tells the linker what kind of library to link with. It applies to all libraries (-l options) until the end of the command line or until the next -a option. Its syntax is:

-a {archive | shared | default | archive_shared | shared_archive}

The different option settings are:

-a archive

Select archive libraries. If the archive library does not exist, ld generates an error message and does not generate the output file.

-a shared

Select shared libraries. If the shared library does not exist, ld generates an error message and does not generate the output file.

-a default

This is the same as -a shared_archive.

-a archive_shared

Select the archive library if it exists; otherwise, select the shared library. If the library cannot be found in either version, ld generates an error message and does not generate the output file.

-a shared_archive

Select the shared library if it exists; otherwise, select the archive library. If the library cannot be found in either version, ld generates an error message and does not generate the output file.

The -a shared and -a archive options specify only one type of library to use. An error results if that type is not found. The other three options specify a preferred type of library and an alternate type of library if the preferred type is not found.

CAUTION: You should avoid mixing shared libraries and archive libraries in the same application. For more information see “Caution When Mixing Shared and Archive Libraries ”.

Example Using -a

The following command links with the archive versions of libcurses, libm and libc:

$ ld /opt/langtools/lib/crt0.o prog.o -a archive -lcurses -lm -lc 

Dynamic Linking with -A and -R

This section describes how to do dynamic linking — that is, how to add an object module to a running program. Conceptually, it is very similar to loading a shared library and accessing its symbols (routines and data). In fact, if you require such functionality, you should probably use shared library management routines (see Chapter 6 “Shared Library Management Routines ”).

However, be aware that dynamic linking is incompatible with shared libraries. That is, a running program cannot be linked to shared libraries and also use ld -A to dynamically load object modules.

NOTE: Another reason to use shared library management routines instead of dynamic linking is that dynamic linking may not be supported in a future release. See “Linker Compatibility Warnings” and “Changes in Future Releases” for additional future changes.

Topics in this section include:

Overview of Dynamic Linking

The implementation details of dynamic linking vary across platforms. To load an object module into the address space of a running program, and to be able to access its procedures and data, follow these steps on all HP9000 computers:

  1. Determine how much space is required to load the module.

  2. Allocate the required memory and obtain its starting address.

  3. Link the module from the running application.

  4. Get information about the module's text, data, and bss segments from the module's header.

  5. Read the text and data into the allocated space.

  6. Clear (fill with zeros) the bss segment.

  7. Flush the text from the data cache before executing code from the loaded module.

  8. Get the addresses of routines and data that are referenced in the module.

Step 1: Determine how much space is required to load the module

There must be enough contiguous memory to hold the module's text, data, and bss segments. You can make a liberal guess as to how much memory is needed, and hope that you've guessed correctly. Or you can be more precise by pre-linking the module and getting size information from its header.

Step 2: Allocate the required memory and obtain its starting address

Typically, you use malloc(3C) to allocate the required memory. You must modify the starting address returned by malloc to ensure that it starts on a memory page boundary (address MOD 4096 == 0).

Step 3: Link the module from the running application

Use the following options when invoking the linker from the program:

-o mod_name

Name of the output module that will be loaded by the running program.

-A base_prog

Tells the linker to prepare the output file for incremental loading. Also causes the linker to include symbol table information from base_prog in the output file.

-R hex_addr

Specifies the hexadecimal address at which the module will be loaded. This is the address calculated in Step 2.

-N

Causes the data segment to be placed immediately after the text segment.

-e entry_pt

If specified (it is optional), causes the symbol named entry_pt to be the entry point into the module. The location of the entry point is stored in the module's header.

Step 4: Get information about the module's text, data, and bss segments from the module's header

There are two header structures stored at the start of the file: struct header (defined in <filehdr.h>) and struct som_exec_auxhdr (defined in <aouthdr.h>). The required information is stored in the second header, so to get it, a program must seek past the first header before reading the second one.

The useful members of the som_exec_auxhdr structure are:

.exec_tsize

Size of text (code) segment.

.exec_tmem

Address at which to load the text (already adjusted for offset specified by the -R linker option).

.exec_tfile

Offset into file (location) where text segment starts.

.exec_dsize

Size of data segment.

.exec_dmem

Address at which to load the data (already adjusted).

.exec_dfile

Offset into file (location) where data segment starts.

.exec_bsize

Size of bss segment. It is assumed to start immediately after the data segment.

.exec_entry

Address of entry point (if one was specified by the -e linker option).

Step 5: Read the text and data into the allocated space

Once you know the location of the required segments in the file, you can read them into the area allocated in Step 2.

The location of the text and data segments in the file is defined by the .exec_tfile and .exec_dfile members of the som_exec_auxhdr structure. The address at which to place the segments in the allocated memory is defined by the .exec_tmem and .exec_dmem members. The size of the segments to read in is defined by the .exec_tsize and .exec_dsize members.

Step 6: Clear (zero out) the bss segment

The bss segment starts immediately after the data segment. To zero out the bss, find the end of the data segment and use memset (see memory(3C)) to zero out the size of the bss.

The end of the data segment can be determined by adding the .exec_dmem and .exec_dsize members of the som_exec_auxhdr structure. The bss's size is defined by the .exec_bsize member.

Step 7: Flush the text from the data cache before executing code from the loaded module

Before executing code in the allocated space, a program should flush the instruction and data caches. Although this is really only necessary on systems that have instruction and data caches, it is easiest just to do it on all systems for ease of portability.

Use an assembly language routine named flush_cache (see “The flush_cache Function ” in this chapter). You must assemble this routine separately (with the as command) and link it with the main program.

Step 8: Get the addresses of routines and data that are referenced in the module

If the -e linker option was used, the module's header will contain the address of the entry point. The entry point's address is stored in the .exec_entry member of the som_exec_auxhdr structure.

If the module contains multiple routines and data that must be accessed from the main program, the main program can use the nlist(3C) function to get their addresses.

Another approach that can be used is to have the entry point routine return the addresses of required routines and data.

An Example Program

To illustrate dynamic linking concepts, this section presents an example program, dynprog. This program loads an object module named dynobj.o, which is created by dynamically linking two object files file1.o and file2.o (see “file1.o and file2.o ”).

The program allocates space for dynobj.o by calling a function named alloc_load_space (see “The alloc_load_space Function ” later in this chapter). The program then calls a function named dyn_load to dynamically link and load dynobj.o (see “The dyn_load Function ” later in this chapter). Both functions are defined in a file called dynload.c (see “dynload.c ”).

As a return value, dyn_load provides the address of the entry point in dynobj.o — in this case, the function foo. To get the addresses of the function bar and the variable counter, the program uses the nlist(3C) function.

The Build Environment

Before seeing the program's source code, it may help to see how the program and the various object files were built. The following shows the makefile used to generate the various files.

Example 3-1 Makefile Used to Create Dynamic Link Files

CFLAGS = -Aa -D_POSIX_SOURCE
dynprog: dynprog.o dynload.o flush_cache.o
# Compile line:
cc -o dynprog dynprog.o dynload.o flush_cache.o -Wl,-a,archive

file1.o: file1.c dynprog.c
file2.o: file2.c

# Create flush_cache.o:
flush_cache.o:
as flush_cache.s

This makefile assumes that the following files are found in the current directory:

dynload.c

The file containing the alloc_load_space and dyn_load functions.

dynprog.c

The main program that calls functions from dynload.c and dynamically links and loads file1.o and file2.o. Also contains the function glorp, which is called by foo and bar.

file1.c

Contains the functions foo and bar.

file2.c

Contains the variable counter, which is incremented by foo, bar, and main.

flush_cache.s

Assembly language source for function flush_cache, which is called by the dyn_load function.

To create the executable program dynprog from this makefile, you would simply run:

$ make dynprog file1.o file2.o 
cc -Aa -D_POSIX_SOURCE -c dynprog.c
cc -Aa -D_POSIX_SOURCE -c dynload.c
cc -o dynprog dynprog.o dynload.o -Wl,-a,archive
cc -Aa -D_POSIX_SOURCE -c file1.c
cc -Aa -D_POSIX_SOURCE -c file2.c
as -o flush_cache flush_cache.s

Note that the line CFLAGS = causes any C files to be compiled in ANSI mode (-Aa) and causes the compiler to search for routines that are defined in the Posix standard (-D_POSIX_SOURCE).

For details on using make refer to make(1).

Source for dynprog

Here is the source file for the dynprog program.

Example 3-2 dynprog.c — Example Dynamic Link and Load Program

#include <stdio.h>
#include <nlist.h>

extern void * alloc_load_space(const char * base_prog,
const char * obj_files,
const char * dest_file);


extern void * dyn_load(const char * base_prog,
unsigned int addr,
const char * obj_files,
const char * dest_file,
const char * entry_pt);

const char * base_prog = "dynprog"; /* this executable's name */
const char * obj_files = "file1.o file2.o"; /* .o files to combine */
const char * dest_file = "dynobj.o"; /* .o file to load */
const char * entry_pt = "foo"; /* define entry pt name */

void glorp (const char *); /* prototype for local function */
void (* foo_ptr) (); /* pointer to entry point foo */
void (* bar_ptr) (); /* pointer to function bar */
int * counter_ptr; /* pointer to variable counter [file2.c] */
main()
{
unsigned int addr; /* address at which to load dynobj.o */
struct nlist nl[3]; /* nlist struct to retrieve addresse */

/*
STEP 1: Allocate space for module:
*/
addr = (unsigned int) alloc_load_space(base_prog,
obj_files, dest_file);

/*
STEP 2: Load the file at the address, and get address of entry point:
*/
foo_ptr = (void (*)()) dyn_load(base_prog, addr, obj_files,
dest_file, entry_pt);


/*
STEP 3: Get the addresses of all desired routines using nlist(3C):
*/

nl[0].n_name = "bar";
nl[1].n_name = "counter";
nl[2].n_name = NULL;
if (nlist(dest_file, nl)) {
fprintf(stderr, "error obtaining namelist for %s\n", dest_file);
exit(1);
}
/*
* Assign the addresses to meaningful variable names:
*/
bar_ptr = (void (*)()) nl[0].n_value;
counter_ptr = (int *) nl[1].n_value;

/*
* Now you can call the routines and modify the variables:
*/
glorp("main");
(*foo_ptr) ();
(*bar_ptr) ();
(*counter_ptr) ++;
printf("counter = %d\n", *counter_ptr);
}

void glorp(const char * from)
{
printf("glorp called from %s\n", from);
}
file1.o and file2.o

Example 3-3 “Source for file1.c and file2.c” shows the source for file1.o and file2.o. Notice that foo and bar call glorp in dynprog.c. Also, both functions update the variable counter in file2.o; however, foo updates counter through the pointer (counter_ptr) defined in dynprog.c.

Example 3-3 Source for file1.c and file2.c

/****************************************************************
* file1.c - Contains routines foo() and bar().
****************************************************************/

extern int * counter_ptr; /* defined in dynprog.c */
extern int counter; /* defined in file2.c */
extern void glorp(const char * from); /* defined in dynprog.c */

void foo()
{
glorp("foo");
(*counter_ptr) ++; /* update counter indirectly with global pointer */
}

void bar()
{
glorp("bar");
counter ++; /* update counter directly */
}

/****************************************************************
* file2.c - Global counter variable referenced by dynprog.c
* and file1.c.
****************************************************************/
int counter = 0;
Output of dynprog

Now that you see how the main program and the module it loads are organized, here is the output produced when dynprog runs:

glorp called from main
glorp called from foo
glorp called from bar
counter = 3
dynload.c

The dynload.c file contains the definitions of the functions alloc_load_space and dyn_load. Example 3-4 “Include Directives for dynload.c ” shows the #include directives must appear at the start of this file.

Example 3-4 Include Directives for dynload.c

#include <stdio.h>
#include <stdlib.h>
#include <nlist.h>
# include <filehdr.h>
# include <aouthdr.h>
# define PAGE_SIZE 4096 /* memory page size */
The alloc_load_space Function

The alloc_load_space function returns a pointer to space (allocated by malloc) into which dynprog will load the object module dynobj.o. It syntax is:

void * alloc_load_space(const char * base_prog,
const char * obj_files,
const char * dest_file)
base_prog

The name of the program that is calling the routine. In other words, the name of the program that will dynamically link and load dest_file.

obj_files

The name of the object file or files that will be linked together to create dest_file.

dest_file

The name of the resulting object module that will by dynamically linked and loaded by base_prog.

As described in Step 1 in “Overview of Dynamic Linking ” at the start of this section, you can either guess at how much space will be required to load a module, or you can try to be more accurate. The advantage of the former approach is that it is much easier and probably adequate in most cases; the advantage of the latter is that it results in less memory fragmentation and could be a better approach if you have multiple modules to load throughout the course of program execution.

The alloc_load_space function allocates only the required amount of space. To determine how much memory is required, alloc_load_space performs these steps:

  1. Pre-link the specified obj_files to create base_prog.

  2. Get text, data, and bss segment location and size information to determine how much space to allocate.

  3. Return a pointer to the space. (The address of the space is adjusted to begin on a memory page boundary — that is, a 4096-byte boundary.)

Example 3-5 “C Source for alloc_load_space Function ” shows the source for this function.

Example 3-5 C Source for alloc_load_space Function

void * alloc_load_space(const char * base_prog,
const char * obj_files,
const char * dest_file)
{
char cmd_buf[256]; /* linker command line */
int ret_val; /* value returned by various lib calls */
size_t space; /* size of space to allocate for module */
size_t addr; /* address of allocated space */
size_t bss_size; /* size of bss (uninitialized data) */
FILE * destfp; /* file pointer for dest_file */

struct som_exec_auxhdr aux_hdr; /* file header */
unsigned int tdb_size; /* size of text, data, and bss combined */

/* ---------------------------------------------------------------
* STEP 1: Pre-link the destination module so we can get its size:
*/
sprintf(cmd_buf, "/bin/ld -a archive -R80000 -A %s -N %s -o %s -lc",
base_prog, obj_files, dest_file);
if (ret_val = system(cmd_buf)) {
fprintf(stderr, "link failed: %s\n", cmd_buf);
exit(ret_val);
}
/* ---------------------------------------------------------------
* STEP 2: Get the size of the module's text, data, and bss segments
* from the auxiliary header for dest_file; add them together to
* determine size:
*/
if ((destfp = fopen(dest_file, "r")) == NULL) {
fprintf(stderr, "error opening %s\n", dest_file);
exit(1);
}

/*
* Must seek past SOM "header" to get to the desired
* "som_exec_auxhdr":
*/
if (fseek(destfp, sizeof(struct header), 0)) {
fprintf(stderr, "error seeking past header in %s\n", dest_file);
exit(1);
}
if (fread(&aux_hdr, sizeof(aux_hdr), 1, destfp) <= 0) {
fprintf(stderr, "error reading som aux header from %s\n", dest_file);
exit(1);
}

/* allow for page-alignment of data segment */

space = aux_hdr.exec_tsize + aux_hdr.exec_dsize
+ aux_hdr.exec_bsize + 2 * PAGE_SIZE;

fclose(destfp); /* done reading from module file */
/* ---------------------------------------------------------------
* STEP 3: Call malloc(3C) to allocate the required memory and get
* its address; then return a pointer to the space:
*/
addr = (size_t) malloc(space);
/*
* Make sure allocated area is on page-aligned address:
*/
if (addr % PAGE_SIZE != 0) addr += PAGE_SIZE - (addr % PAGE_SIZE);

return((void *) addr);
}
The dyn_load Function

The dyn_load function dynamically links and loads an object module into the space allocated by the alloc_load_space function. In addition, it returns the address of the entry point in the loaded module. Its syntax is:

void * dyn_load(const char * base_prog,
unsigned int addr,
const char * obj_files,
const char * dest_file,
const char * entry_pt)

The base_prog, obj_files, and dest_file parameters are the same parameters supplied to alloc_load_space. The addr parameter is the address returned by alloc_load_space, and the entry_pt parameter specifies a symbol name that you want to act as the entry point in the module.

To dynamically link and load dest_file into base_prog, the dyn_load function performs these steps:

  1. Dynamically link base_prog with obj_files, producing dest_file. The address at which dest_file will be loaded into memory is specified with the -R addr option. The name of the entry point for the file is specified with -e entry_pt.

  2. Open dest_file and get its header information on the text, data, and bss segments. Read this information into a som_exec_auxhdr structure, which starts immediately after a header structure.

  3. Read the text and data segments into the area allocated by alloc_load_space. (The text and data segments are read separately.)

  4. Initialize (fill with zeros) the bss, which starts immediately after the data segment.

  5. Flush text from the data cache before execution, using the flush_cache routine. (See “The flush_cache Function ” later in this chapter.)

  6. Return a pointer to the entry point, specified by the -e option in Step 1.

Example 3-6 C Source for dyn_load Function

void * dyn_load(const char * base_prog,
unsigned int addr,

const char * obj_files,
const char * dest_file,
const char * entry_pt)
{
char cmd_buf[256]; /* buffer holding linker command */
int ret_val; /* holds return value of library calls */
FILE * destfp; /* file pointer for destination file */
unsigned int bss_start; /* start address of bss in VM */
unsigned int bss_size; /* size of bss */
unsigned int entry_pt_addr; /* address of entry point */

struct som_exec_auxhdr aux_hdr; /* som file auxiliary header */
unsigned int tdb_size; /* size of text, data, and bss combined*/

/* -----------------------------------------------------------------
* STEP 1: Dynamically link the module to be loaded:
*/
sprintf(cmd_buf,
"/bin/ld -a archive -A %s -R %x -N %s -o %s -lc -e %s",
base_prog, addr, obj_files, dest_file, entry_pt);

if (ret_val = system(cmd_buf))
{
fprintf(stderr, "link command failed: %s\n", cmd_buf);
exit(ret_val);
}

/* -----------------------------------------------------------------
* STEP 2: Open dest_file. Read its auxiliary header for text, data,
* and bss info:
*/
if ((destfp = fopen(dest_file, "r")) == NULL)
{
fprintf(stderr, "error opening %s for loading\n", dest_file);
exit(1);
}

/*
* Get auxiliary header information from "som_exec_auxhdr" struct,
* which is after SOM header.
*/

if (fseek(destfp, sizeof(struct header), 0))
{
fprintf(stderr, "error seeking past header in %s\n", dest_file);
exit(1);
}

if (fread(&aux_hdr, sizeof(aux_hdr), 1, destfp) <= 0)
{
fprintf(stderr, "error reading som aux header from %s\n", dest_file);
exit(1);
}
/* -----------------------------------------------------------------
* STEP 3: Read the text and data segments into the buffer area:
*/

/*
* Read text and data separately. First load the text:
*/

if (fseek(destfp, aux_hdr.exec_tfile, 0))
{
fprintf(stderr, "error seeking start of text in %s\n", dest_file);
exit(1);
}

if ((fread(aux_hdr.exec_tmem, aux_hdr.exec_tsize, 1, destfp)) <= 0)
{
fprintf(stderr, "error reading text from %s\n", dest_file);
exit(1);
}
/*
* Now load the data, if any:
*/
if (aux_hdr.exec_dsize) {
if (fseek(destfp, aux_hdr.exec_dfile, 0))
{
fprintf(stderr, "error seeking start of data in %s\n", dest_file);
exit(1);
}

if ((fread(aux_hdr.exec_dmem, aux_hdr.exec_dsize, 1, destfp))<= 0)
{
fprintf(stderr, "error reading data from %s\n", dest_file);
exit(1);
}
}

fclose(destfp); /* done reading from module file */
/* -----------------------------------------------------------------
* STEP 4: Zero out the bss (uninitialized data segment):
*/

bss_start = aux_hdr.exec_dmem + aux_hdr.exec_dsize;
bss_size = aux_hdr.exec_bsize;

memset(bss_start, 0, bss_size);

/* -----------------------------------------------------------------
* STEP 5: Flush the text from the data cache before execution:
*/

/*
* The flush_cache routine must know the exact size of the
* text, data, and bss, computed as follows:
* Size = (Data Addr - Text Addr) + Data Size + BSS Size
* where (Data Addr - Text Addr) = Text Size + alignment between
* Text and Data.
*/
tdb_size = (aux_hdr.exec_dmem - aux_hdr.exec_tmem) +
aux_hdr.exec_dsize + aux_hdr.exec_bsize;
flush_cache(addr, tdb_size);

/* -----------------------------------------------------------------
* STEP 6: Return a pointer to the entry point specified by -e:
*/

entry_pt_addr = (unsigned int) aux_hdr.exec_entry;
return ((void *) entry_pt_addr);
}
The flush_cache Function

Since there is no existing routine to flush text from the data cache before execution, you must create one. Below is the assembly language source for such a function.

Example 3-7 Assembly Language Source for flush_cache Function

; flush_cache.s
;
; Routine to flush and synchronize data and instruction caches
; for dynamic loading
;
; Copyright Hewlett-Packard Co. 1985,1991, 1995
;
; All HP VARs and HP customers have a non-exclusive royalty-free
; license to copy and use this flush_cashe() routine in source
; code and/or object code.

.code

; flush_cache(addr, len) - executes FDC and FIC instructions for
; every cache line in the text region given by starting addr and
; len. When done, it executes a SYNC instruction and then enough
; NOPs to assure the cache has been flushed.
;
; Assumption: Cache line size is at least 16 bytes. Seven NOPs
; is enough to assure cache has been flushed. This routine is
; called to flush the cache for just-loaded dynamically linked
; code which will be executed from SR5 (data) space.

; %arg0=GR26, %arg1=GR25, %arg2=GR24, %arg3=GR23, %sr0=SR0.
; loop1 flushes data cache. arg0 holds address. arg1 holds offset.
; SR=0 means that SID of data area is used for fdc.
; loop2 flushes inst cache. arg2 holds address. arg3 holds offset.
; SR=sr0 means that SID of data area is used for fic.
; fdc x(0,y) -> 0 means use SID of data area.
; fic x(%sr0,y) -> SR0 means use SR0 SID (which is set to data area).

.proc
.callinfo
.export flush_cache,entry
flush_cache
.enter
ldsid (0,%arg0),%r1 ; Extract SID (SR5) from address
mtsp %r1,%sr0 ; SID -> SR0
ldo -1(%arg1),%arg1 ; offset = length -1
copy %arg0,%arg2 ; Copy address from GR26 to GR24
copy %arg1,%arg3 ; Copy offset from GR25 to GR23

fdc %arg1(0,%arg0) ; Flush data cache @SID.address+offset
loop1
addib,>,n -16,%arg1,loop1 ; Decrement offset by cache line size
fdc %arg1(0,%arg0) ; Flush data cache @SID.address+offset
; flush first word at addr, to handle arbitrary cache line boundary
fdc 0(0,%arg0)
sync

fic %arg3(%sr0,%arg2) ; Flush inst cache @SID.address+offset
loop2
addib,>,n -16,%arg3,loop2 ; Decrement offset by cache line size
fic %arg3(%sr0,%arg2) ; Flush inst cache @SID.address+offset
; flush first word at addr, to handle arbitrary cache line boundary
fic 0(%sr0,%arg2)

sync
nop
nop
nop
nop
nop
nop
nop
.leave
.procend
.end

Exporting Symbols with +e

The +e option allow you to hide and export symbols. Exporting a symbol makes the symbol a global definition, which can be accessed by any other object modules or libraries. The +e option exports symbol and hides from export all other global symbols not specified with +e. In essence, -h and +e provide two different ways to do the same thing.

The syntax of the +e option is:

+e symbol

Example Using +e

Suppose you want to build a shared library from an object file that contains the following symbol definitions as displayed by the nm command:

$ nm -p sem.o
0000000000 U $global$
1073741824 d $THIS_DATA$
1073741864 b $THIS_BSS$
0000000004 cS sem_val
0000000000 T check_sem_val
0000000036 T foo
0000000000 U printf
0000000088 T bar
0000000140 T sem

In this example, check_sem_val, foo, bar, and sem are all global definitions. To create a shared library where check_sem_val is a hidden, local definition, you could use either of the following commands:

$ ld -b -h check_sem_val sem.o          One -h option.      
$ ld -b +e foo +e bar +e sem sem.o Three +e options.

In contrast, suppose you want to export only the check_sem_val symbol. Either of the following commands would work:

$ ld -b -h foo -h bar -h sem sem.o     Three -h options. 
$ ld -b +e check_sem_val sem.o One +e option.

When to use -h versus +e

How do you decide whether to use -h or +e? In general, use -h if you simply want to hide a few symbols. And use +e if you want to export a few symbols and hide a large number of symbols.

You should not combine -h and +e options on the same command line. For instance, suppose you specify +e sem. This would export the symbol sem and hide all other symbols. Any additional -h options would be unnecessary. If both -h and +e are used on the same symbol, the -h overrides the +e option.

The linker command line could get quite lengthy and difficult to read if several such options were specified. And in fact, you could exceed the maximum HP-UX command line length if you specify too many options. To get around this, use ld linker option files, described under “Passing Linker Options in a file with -c ”. You can specify any number of -h or +e options in this file.

You can use -h or +e options when building a shared library (with -b) and when linking to create an a.out file. When combining .o files with -r, you can still use only the -h option.

Exporting Symbols with +ee

Like the +e option, the +ee option allows you to export symbols. Unlike the +e option, the option does not alter the visibility of any other symbols in the file. It exports the specified symbol, and does not hide any of the symbols exported by default.

Exporting Symbols from main with -E

By default, the linker exports from a program only those symbols that were imported by a shared library. For example, if a shared executable's libraries do not reference the program's main routine, the linker does not include the main symbol in the a.out file's export list. Normally, this is a problem only when a program calls shared library management routines (described in Chapter 6 “Shared Library Management Routines ”). To make the linker export all symbols from a program, invoke ld with the -E option.

The +e option allows you to be more selective about which symbols are exported, resulting in better performance. For details on +e, see “Exporting Symbols with +e”.

Hiding Symbols with -h

The -h option allows you to hide symbols. Hiding a symbol makes the symbol a local definition, accessible only from the object module or library in which it is defined. Use -h if you simply want to hide a few symbols.

You can use -h option when building a shared library (with -b) and when linking to create an a.out file. When combining .o files with -r, you can use the -h option.

The syntax of the -h option is:

-h symbol

The -h option hides symbol. Any other global symbols remain exported unless hidden with -h.

Example Using -h

Suppose you want to build a shared library from an object file that contains the following symbol definitions as displayed by the nm command:

$ nm -p sem.o
0000000000 U $global$
1073741824 d $THIS_DATA$
1073741864 b $THIS_BSS$
0000000004 cS sem_val
0000000000 T check_sem_val
0000000036 T foo
0000000000 U printf
0000000088 T bar
0000000140 T sem

In this example, check_sem_val, foo, bar, and sem are all global definitions. To create a shared library where check_sem_val is a hidden, local definition, you could do the following:

$ ld -b -h check_sem_val sem.o

Tips on Using -h

You should not combine -h and +e options on the same command line. For instance, suppose you specify +e sem. This would export the symbol sem and hide all other symbols. Any additional -h options would be unnecessary. If both -h and +e are used on the same symbol, the -h overrides the +e option.

The linker command line could get quite lengthy and difficult to read if several such options were specified. And in fact, you could exceed the maximum HP-UX command line length if you specify too many options. To get around this, use ld linker option files, described under “Passing Linker Options in a file with -c ”. You can specify any number of -h or +e options in this file.

Hiding and Exporting Symbols When Building a Shared Library

When building a shared library, you might want to hide a symbol in the library for several reasons:

  • It can improve performance because the dynamic loader does not have to bind hidden symbols. Since most symbols need not be exported from a shared library, hiding selected symbols can have a significant impact on performance.

  • It ensures that the definition can only be accessed by other routines in the same library. When linking with other object modules or libraries, the definition will be hidden from them.

  • When linking with other libraries (to create an executable), it ensures that the library will use the local definition of a routine rather than a definition that occurs earlier in the link order.

Exporting a symbol is necessary if the symbol must be accessible outside the shared library. But remember that, by default, most symbols are global definitions anyway, so it is seldom necessary to explicitly export symbols. In C, all functions and global variables that are not explicitly declared as static have global definitions, while static functions and variables have local definitions. In FORTRAN, global definitions are generated for all subroutines, functions, and initialized common blocks.

When using +e, be sure to export any data symbols defined in the shared library that will be used by another shared library or the program, even if these other files have definitions of the data symbols. Otherwise, your shared library will use its own private copy of the global data, and another library or the program file will not see any change.

One example of a data symbol that should almost always be exported from a shared library is errno. errno is defined in every shared library and program; if this definition is hidden, the value of errno will not be shared outside of the library.

Hiding Symbols When Combining .o Files with the -r Option

The -r option combines multiple .o files, creating a single .o file. The reasons for hiding symbols in a .o file are the same as the reasons listed above for shared libraries. However, a performance improvement will occur only if the resulting .o file is later linked into a shared library.

Hiding and Exporting Symbols When Creating an a.out File

By default, the linker exports all of a program's global definitions that are imported by shared libraries specified on the linker command line. For example, given the following linker command, all global symbols in crt0.o and prog.o that are referenced by libm or libc are automatically exported:

$ ld /usr/ccs/lib/crt0.o prog.o -lm -lc

With libraries that are explicitly loaded with shl_load, this behavior may not always be sufficient because the linker does not search explicitly loaded libraries (they aren't even present on the command line). You can work around this using the -E or +e linker option.

As mentioned previously in the section “Exporting Symbols from main with -E ”, the -E option forces the export of all symbols from the program, regardless of whether they are referenced by shared libraries on the linker command line. The +e option allows you to be more selective in what symbols are exported. You can use +e to limit the exported symbols to only those symbols you want to be visible.

For example, the following ld command exports the symbols main and foo. The symbol main is referenced by libc. The symbol foo is referenced at run time by an explicitly loaded library not specified at link time:

$ ld /usr/ccs/lib/crt0.o prog.o +e main +e foo -lm -lc -ldld

When using +e, be sure to export any data symbols defined in the program that may also be defined in explicitly loaded libraries. If a data symbol that a shared library imports is not exported from the program file, the program uses its own copy while the shared library uses a different copy if a definition exists outside the program file. In such cases, a shared library might update a global variable needed by the program, but the program would never see the change because it would be referencing its own copy.

One example of a data symbol that should almost always be exported from a program is errno. errno is defined in every shared library and program; if this definition is hidden, the value of errno will not be shared outside of the program in which it is hidden.

Moving Libraries after Linking with +b

A library can be moved even after an application has been linked with it. This is done by providing the executable with a list of directories to search at run time for any required libraries. One way you can store a directory path list in the program is by using the +b path_list linker option.

Note that dynamic path list search works only for libraries specified with -l on the linker command line (for example, -lfoo). It won't work for libraries whose full path name is specified (for example, /usr/contrib/lib/libfoo.sl). However, it can be enabled for such libraries with the -l option to the chatr command (see “Changing a Program's Attributes with chatr(1) ”).

Specifying a Path List with +b

The syntax of the +b option is

+b path_list

where path_list is the list of directories you want the dynamic loader to search at run time. For example, the following linker command causes the path .:/app/lib:: to be stored in the executable. At run time, the dynamic loader would search for libfoo.sl, libm.sl, and libc.sl in the current working directory (.), the directory /app/lib, and lastly in the location in which the libraries were found at link time (::):

$ ld /opt/langtools/lib/crt0.o +b .:/app/lib:: prog.o -lfoo \
-lm -lc

If path_list is only a single colon, the linker constructs a path list consisting of all the directories specified by -L, followed by all the directories specified by the LPATH environment variable. For instance, the following linker command records the path list as /app/lib:/tmp:

$ LPATH=/tmp ; export LPATH
$ ld /opt/langtools/lib/crt0.o +b : -L/app/lib prog.o -lfoo \
-lm -lc

The Path List

Whether specified as a parameter to +b or set as the value of the SHLIB_PATH environment variable, the path list is simply one or more path names separated by colons (:), just like the syntax of the PATH environment variable. An optional colon can appear at the start and end of the list.

Absolute and relative path names are allowed. Relative paths are searched relative to the program's current working directory at run time.

Remember that a shared library's full path name is stored in the executable. When searching for a library in an absolute or relative path at run time, the dynamic loader uses only the basename of the library path name stored in the executable. For instance, if a program is linked with /usr/local/lib/libfoo.sl, and the directory path list contains /apps/lib:xyz, the dynamic loader searches for /apps/lib/libfoo.sl, then ./xyz/libfoo.sl.

The full library path name stored in the executable is referred to as the default library path. To cause the dynamic loader to search for the library in the default location, use a null directory path (). When the loader comes to a null directory path, it uses the default shared library path stored in the executable. For instance, if the directory path list in the previous example were /apps/lib::xyz, the dynamic loader would search for /apps/lib/libfoo.sl, /usr/local/lib/libfoo.sl, then ./xyz/libfoo.sl.

If the dynamic loader cannot find a required library in any of the directories specified in the path list, it searches for the library in the default location () recorded by the linker.

Moving Libraries After Linking with +s and SHLIB_PATH

A library can be moved even after an application has been linked with it. Linking the program with +s, enables the program to use the path list defined by the SHLIB_PATH environment variable at run time.

Specifying a Path List with +s and SHLIB_PATH

When a program is linked with +s, the dynamic loader will get the library path list from the SHLIB_PATH environment variable at run time. This is especially useful for application developers who don't know where the libraries will reside at run time. In such cases, they can have the user or an install script set SHLIB_PATH to the correct value.

For More Information:

Passing Linker Options in a file with -c

The -c file option causes the linker to read command line options from the specified file. This is useful if you have many -h or +e options to include on the ld command line, or if you have to link with numerous object files. For example, suppose you have over a hundred +e options that you need when building a shared library. You could place them in a file named eopts and force the linker to read options from the file as follows:

$ ld -o libmods.sl -b -c eopts mod*.o 
$ cat eopts Display the file.
+e foo
+e bar
+e reverse_tree
+e preorder_traversal
+e shift_reduce_parse
.
.
.

Note that the linker ignores lines in that option file that begin with a pound sign (#). You can use such lines as comment lines or to temporarily disable certain linker options in the file. For instance, the following linker option file for an application contains a disabled -O option:

# Exporting only the "compress" symbol resulted
# in better run-time performance:
+e compress
# When the program is debugged, remove the pound sign
# from the following optimization option:
# -O

Passing Linker Options with LDOPTS

If you use certain linker options all the time, you may find it useful to specify them in the LDOPTS environment variable. The linker inserts the value of this variable before all other arguments on the linker command line. For instance, if you always want the linker to display verbose information (-v) and a trace of each input file (-t), set LDOPTS as follows:

$ LDOPTS="-v -t"    Korn and Bourne shell syntax. 
$ export LDOPTS

Thereafter, the following commands would be equivalent:

$ ld /opt/langtools/lib/crt0.o -u main prog.o -lc
$ ld -v -t /opt/langtools/lib/crt0.o -u main prog.o -lc

Specifying Libraries with -l and l:

To direct the linker to search a particular library, use the -lname option. For example, to specify libc, use -lc; to specify libm, use -lm; to specify libXm, use -lXm.

Specifying Libraries (-l)

When writing programs that call routines not found in the default libraries linked at compile time, you must specify the libraries on the compiler command line with the -lx option. For example, if you write a C program that calls POSIX math functions, you must link with libm.

The x argument corresponds to the identifying portion of the library path name — the part following lib and preceding the suffix .a or .sl. For example, for the libm.sl or libm.a library, x is the letter m:

$ cc -Aa mathprog.c -lm

The linker searches libraries in the order in which they are specified on the command line (that is, the link order). In addition, libraries specified with -l are searched before the libraries that the compiler links by default.

Using the -l: option

The -l: option works just like the -l option with one major difference: -l: allows you to specify the full basename of the library to link with. For instance, -l:libm.a causes the linker to link with the archive library /usr/lib/libm.a, regardless of whether -a shared was specified previously on the linker command line.

The advantage of using this option is that it allows you to specify an archive or shared library explicitly without having to change the state of the -a option. (See also “Caution When Mixing Shared and Archive Libraries ”.)

For instance, suppose you use the LDOPTS environment variable (see “Passing Linker Options with LDOPTS ”) to set the -a option that you want to use by default when linking. And depending on what environment you are building an application for, you might set LDOPTS to -a archive or -a shared. You can use -l: to ensure that the linker will always link with a particular library regardless of the setting of the -a option in the LDOPTS variable.

Example Using -l:

For example, even if LDOPTS were set to -a shared, the following command would link with the archive libfoo.a in the directory /usr/mylibs, the archive libm.a and libc.a:

$ ld /opt/langtools/lib/crt0.o -u main prog.o -L/usr/mylibs \
-l:libfoo.a -l:libc.a -l:libm.a

Stripping Symbol Table Information from the Output File with -s and -x

The a.out file created by the linker contains symbol table, relocation, and (if debug options were specified) information used by the debugger. Such information can be used by other commands that work on a.out files, but is not actually necessary to make the file run. ld provides two command line options for removing such information and, thus, reducing the size of executables:

-s

Strips all such information from the file. The executable becomes smaller, but difficult or impossible to use with a symbolic debugger. You can get much the same results by running the strip command on an executable (see strip(1)). In some cases, however, -s rearranges the file to save more space than strip.

-x

Strips only local symbols from the symbol table. It reduces executable file size with only a minimal affect on commands that work with executables. However, using this option may still make the file unusable by a symbolic debugger.

These options can reduce the size of executables dramatically. Note, also, that these options can also be used when generating shared libraries without affecting shareability.

Printable version
Privacy statement Using this site means you accept its terms Feedback to webmaster
© 1997 Hewlett-Packard Development Company, L.P.