Linker and Libraries Guide

Chapter 2 Link-Editor

The link-editing process creates an output file from one or more input files. Output file creation is directed by the options that are supplied to the link-editor and the input sections provided by the input files.

All files are represented in the executable and linking format (ELF). For a complete description of the ELF format see Chapter 7, Object File Format. For this introduction, two ELF structures are introduced, sections and segments.

Sections are the smallest indivisible units that can be processed within an ELF file. Segments are a collection of sections that represent the smallest individual units that can be mapped to a memory image by exec(2) or by the runtime linker ld.so.1(1).

Although many types of ELF section exist, sections fall into two categories with respect to the link-editing phase.

Basically, the link-editor concatenates the program data sections into the output file. The link-editing information sections are interpreted by the link-editor to modify other sections. The information sections are also used to generate new output information sections used in later processing of the output file.

The following simple breakdown of link-editor functionality introduces the topics that are covered in this chapter.

The process of concatenating like sections and associating sections to segments is carried out using default information within the link-editor. The default section and segment handling provided by the link-editor is usually sufficient for most link-edits. However, these defaults can be manipulated using the -M option with an associated mapfile. See Appendix E, System V Release 4 (Version 1) Mapfiles.

Invoking the Link-Editor

You can either run the link-editor directly from the command line or have a compiler driver invoke the link-editor for you. In the following two sections the description of both methods are expanded. However, using the compiler driver is the preferred choice. The compilation environment is often the consequence of a complex and occasionally changing series of operations known only to compiler drivers.


Note –

Starting with Oracle Solaris 11 Express, various compilation components have been moved from /usr/ccs/bin and /usr/ccs/lib, to /usr/bin and /usr/lib. However, applications exist that refer to the original ccs names. Symbolic links have been used to maintain compatibility.


Direct Invocation

When you invoke the link-editor directly, you have to supply every object file and library required to create the intended output. The link-editor makes no assumptions about the object modules or libraries that you meant to use in creating the output. For example, the following command instructs the link-editor to create a dynamic executable that is named a.out using only the input file test.o.


$ ld test.o

Typically, a dynamic executable requires specialized startup code and exit processing code. This code can be language or operating system specific, and is usually provided through files supplied by the compiler drivers.

Additionally, you can also supply your own initialization code and termination code. This code must be encapsulated and be labeled correctly for the code to be correctly recognized and made available to the runtime linker. This encapsulation and labeling can also be provided through files supplied by the compiler drivers.

When creating runtime objects such as executables and shared objects, you should use a compiler driver to invoke the link-editor. Direct invocation of the link-editor is recommended only when creating intermediate relocatable objects when using the -r option.

Using a Compiler Driver

The conventional way to use the link-editor is through a language-specific compiler driver. You supply the compiler driver, cc(1), CC(1), and so forth, with the input files that make up your application. The compiler driver adds additional files and default libraries to complete the link-edit. These additional files can be seen by expanding the compilation invocation.


$ cc -# -o prog main.o
/usr/bin/ld -dy /opt/COMPILER/crti.o /opt/COMPILER/crt1.o \
/usr/lib/values-Xt.o -o prog main.o \
-YP,/opt/COMPILER/lib:/usr/lib:/usr/lib -Qy -lc \
/opt/COMPILER/crtn.o

Note –

The actual files included by your compiler driver and the mechanism used to display the link-editor invocation might differ.


Cross Link-Editing

The link-editor is a cross link-editor, able to link 32–bit objects or 64–bit objects, for SPARC or x86 targets. The mixing of 32–bit objects and 64–bit objects is not permitted. Similarly, only objects of a single machine type are allowed.

Typically, no command line option is required to distinguish the link-edit target. The link-editor uses the ELF machine type of the first relocatable object on the command line to govern the mode in which to operate. Specialized link-edits, such as linking solely from a mapfile or an archive library, are uninfluenced by the command line object. These link-edits default to a 32–bit native target. To explicitly define the link-edit target use the -z target option.

Specifying the Link-Editor Options

Most options to the link-editor can be passed through the compiler driver command line. For the most part, the compiler and the link-editor options do not conflict. Where a conflict arises, the compiler drivers usually provide a command line syntax that you can use to pass specific options to the link-editor. You can also provide options to the link-editor by setting the LD_OPTIONS environment variable.


$ LD_OPTIONS="-R /home/me/libs -L /home/me/libs" cc -o prog main.c -lfoo

The -R and -L options are interpreted by the link-editor. These options precede any command line options that are received from the compiler driver.

The link-editor parses the entire option list for any invalid options or any options with invalid associated arguments. When either of these cases are found, a suitable error message is generated. If the error is deemed fatal, the link-edit terminates. In the following example, the illegal option -X, and the illegal argument to the -z option, are caught by the link-editor's checking.


$ ld -X -z sillydefs main.o
ld: illegal option -- X
ld: fatal: option -z has illegal argument `sillydefs'

If an option that requires an associated argument is specified twice, the link-editor produces a suitable warning and continue with the link-edit.


$ ld -e foo .... -e bar main.o
ld: warning: option -e appears more than once, first setting taken

The link-editor also checks the option list for any fatal inconsistencies.


$ ld -dy -a main.o
ld: fatal: option -dy and -a are incompatible

After processing all options, if no fatal error conditions have been detected, the link-editor proceeds to process the input files.

See Appendix A, Link-Editor Quick Reference for the most commonly used link-editor options, and ld(1) for a complete description of all link-editor options.

Input File Processing

The link-editor reads input files in the order in which the files appear on the command line. Each file is opened and inspected to determine the files ELF type, and therefore determine how the file must be processed. The file types that apply as input for the link-edit are determined by the binding mode of the link-edit, either static or dynamic.

Under static mode, the link-editor accepts only relocatable objects or archive libraries as input files. Under dynamic mode, the link-editor also accepts shared objects.

Relocatable objects represent the most basic input file type to the link-editing process. The program data sections within these files are concatenated into the output file image being generated. The link-edit information sections are organized for later use. Information sections do not become part of the output file image, as new information sections are generated to take their place. Symbols are gathered into an internal symbol table for verification and resolution. This table is then used to create one or more symbol tables in the output image.

Although input files can be specified directly on the link-edit command line, archive libraries and shared objects are commonly specified using the -l option. See Linking With Additional Libraries. During a link-edit, the interpretation of archive libraries and shared objects are quite different. The next two sections expand upon these differences.

Archive Processing

Archives are built using ar(1). Archives usually consist of a collection of relocatable objects with an archive symbol table. This symbol table provides an association of symbol definitions with the objects that supply these definitions. By default, the link-editor provides selective extraction of archive members. The link-editor uses unresolved symbolic references to select objects from the archive that are required to complete the binding process. You can also explicitly extract all members of an archive.

The link-editor extracts a relocatable object from an archive under the following conditions.

Under selective archive extraction, a weak symbol reference does not extract an object from an archive unless the -z weakextract option is in effect. See Simple Resolutions for more information.


Note –

The options -z weakextract, -z allextract, and -z defaultextract enable you to toggle the archive extraction mechanism among multiple archives.


With selective archive extraction, the link-editor makes multiple passes through an archive. Relocatable objects are extracted as needed to satisfy the symbol information being accumulated in the link-editor internal symbol table. After the link-editor has made a complete pass through the archive without extracting any relocatable objects, the next input file is processed.

By extracting only the relocatable objects needed when an archive is encountered, the position of the archive on the command line can be significant. See Position of an Archive on the Command Line.


Note –

Although the link-editor makes multiple passes through an archive to resolve symbols, this mechanism can be quite costly. Especially, for large archives that contain random organizations of relocatable objects. In these cases, you should use tools like lorder(1) and tsort(1) to order the relocatable objects within the archive. This ordering reduces the number of passes the link-editor must carry out.


Shared Object Processing

Shared objects are indivisible whole units that have been generated by a previous link-edit of one or more input files. When the link-editor processes a shared object, the entire contents of the shared object become a logical part of the resulting output file image. This logical inclusion means that all symbol entries defined in the shared object are made available to the link-editing process.

The shared object's program data sections and most of the link-editing information sections are unused by the link-editor. These sections are interpreted by the runtime linker when the shared object is bound to generate a runnable process. However, the occurrence of a shared object is remembered. Information is stored in the output file image to indicate that this object is a dependency that must be made available at runtime.

By default, all shared objects specified as part of a link-edit are recorded as dependencies in the object being built. This recording is made regardless of whether the object being built actually references symbols offered by the shared object. To minimize the overhead of runtime linking, only specify those dependencies that resolve symbol references from the object being built. The link-editor's debugging facility, and ldd(1) with the -u option, can be used to determine unused dependencies. The link-editor's -z ignore option can be used to suppress the dependency recording of any unused shared objects.

If a shared object has dependencies on other shared objects, these dependencies can also be processed. This processing occurs after all command line input files have been processed, to complete the symbol resolution process. However, the shared object names are not recorded as dependencies in the output file image being generated.

Although the position of a shared object on the command line has less significance than archive processing, the position can have a global effect. Multiple symbols of the same name are allowed to occur between relocatable objects and shared objects, and between multiple shared objects. See Symbol Resolution.

The order of shared objects processed by the link-editor is maintained in the dependency information that is stored in the output file image. In the absence of lazy loading, the runtime linker loads the specified shared objects in the same order. Therefore, the link-editor and the runtime linker select the first occurrence of a symbol of a multiply-defined series of symbols.


Note –

Multiple symbol definitions, are reported in the load map output generated using the -m option.


Linking With Additional Libraries

Although the compiler drivers often ensure that appropriate libraries are specified to the link-editor, frequently you must supply your own. Shared objects and archives can be specified by explicitly naming the input files required to the link-editor. However, a more common and more flexible method involves using the link-editor's -l option.

Library Naming Conventions

By convention, shared objects are usually designated by the prefix lib and the suffix .so. Archives are designated by the prefix lib and the suffix .a. For example, libfoo.so is the shared object version of the “foo” implementation that is made available to the compilation environment. libfoo.a is the library's archive version.

These conventions are recognized by the -l option of the link-editor. This option is commonly used to supply additional libraries to a link-edit. The following example directs the link-editor to search for libfoo.so. If the link-editor does not find libfoo.so, a search for libfoo.a is made before moving on to the next directory to be searched.


$ cc -o prog file1.c file2.c -lfoo

Note –

A naming convention exists regarding the compilation environment and the runtime environment use of shared objects. The compilation environment uses the simple .so suffix, whereas the runtime environment commonly uses the suffix with an additional version number. See Naming Conventions and Coordination of Versioned Filenames.


When link-editing in dynamic mode, you can choose to link with a mix of shared objects and archives. When link-editing in static mode, only archive libraries are acceptable for input.

In dynamic mode, when using the -l option, the link-editor first searches the given directory for a shared object that matches the specified name. If no match is found, the link-editor looks for an archive library in the same directory. In static mode, when using the -l option, only archive libraries are sought.

Linking With a Mix of Shared Objects and Archives

The library search mechanism in dynamic mode searches a given directory for a shared object, and then searches for an archive library. Finer control of the search is possible through the -B option.

By specifying the -B dynamic and -B static options on the command line, you can toggle the library search between shared objects or archives respectively. For example, to link an application with the archive libfoo.a and the shared object libbar.so, issue the following command.


$ cc -o prog main.o file1.c -Bstatic -lfoo -Bdynamic -lbar

The -B static and -B dynamic options are not exactly symmetrical. When you specify -B static, the link-editor does not accept shared objects as input until the next occurrence of -B dynamic. However, when you specify -B dynamic, the link-editor first looks for shared objects and then archive library's in any given directory.

The precise description of the previous example is that the link-editor first searches for libfoo.a. The link-editor then searches for libbar.so, and if that search fails, searches for libbar.a.

Position of an Archive on the Command Line

The position of an archive on the command line can affect the output file being produced. The link-editor searches an archive only to resolve undefined or tentative external references that have previously been encountered. After this search is completed and any required members have been extracted, the link-editor moves onto the next input file on the command line.

Therefore by default, the archive is not available to resolve any new references from the input files that follow the archive on the command line. For example, the following command directs the link-editor to search libfoo.a only to resolve symbol references that have been obtained from file1.c. The libfoo.a archive is not available to resolve symbol references from file2.c or file3.c.


$ cc -o prog file1.c -Bstatic -lfoo file2.c file3.c -Bdynamic

Interdependencies between archives can exist, such that the extraction of members from one archive must be resolved by extracting members from another archive. If these dependencies are cyclic, the archives must be specified repeatedly on the command line to satisfy previous references.


$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA

The determination, and maintenance, of repeated archive specifications can be tedious. The -z rescan-now option makes this process simpler. The -z rescan-now option is processed by the link-editor immediately when the option is encountered on the command line. All archives that have been processed from the command line prior to this option are immediately reprocessed. This processing attempts to locate additional archive members that resolve symbol references. This archive rescanning continues until a pass over the archive list occurs in which no new members are extracted. The previous example can be simplified as follows.


$ cc -o prog .... -lA -lB -lC -z rescan-now

Alternatively, the -z rescan-start and -z rescan-end options can be used to group mutually dependent archives together into an archive group. These groups are reprocessed by the link-editor immediately when the closing delimiter is encountered on the command line. Archives found within the group are reprocessed in an attempt to locate additional archive members that resolve symbol references. This archive rescanning continues until a pass over the archive group occurs in which no new members are extracted. Using archive groups, the previous example can be written as follows.


$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end

Note –

You should specify any archives at the end of the command line unless multiple-definition conflicts require you to do otherwise.


Directories Searched by the Link-Editor

All previous examples assume the link-editor knows where to search for the libraries listed on the command line. By default, when linking 32–bit objects, the link-editor knows of only three standard directories in which to look for libraries, /usr/ccs/lib, followed by /lib, and finally /usr/lib. When linking 64–bit objects, only two standard directories are used, /lib/64 followed by /usr/lib/64. All other directories to be searched must be added to the link-editor's search path explicitly.

You can change the link-editor search path by using a command line option, or by using an environment variable.

Using a Command-Line Option

You can use the -L option to add a new path name to the library search path. This option alters the search path at the point the option is encountered on the command line. For example, the following command searches path1, followed by /usr/ccs/lib, /lib, and finally /usr/lib, to find libfoo. The command searches path1 and then path2, followed by /usr/ccs/lib, /lib, and /usr/lib, to find libbar.


$ cc -o prog main.o -Lpath1 file1.c -lfoo file2.c -Lpath2 -lbar

Path names that are defined by using the -L option are used only by the link-editor. These path names are not recorded in the output file image being created. Therefore, these path names are not available for use by the runtime linker.


Note –

You must specify -L if you want the link-editor to search for libraries in your current directory. You can use a period (.) to represent the current directory.


You can use the -Y option to change the default directories searched by the link-editor. The argument supplied with this option takes the form of a colon separated list of directories. For example, the following command searches for libfoo only in the directories /opt/COMPILER/lib and /home/me/lib.


$ cc -o prog main.c -YP,/opt/COMPILER/lib:/home/me/lib -lfoo

The directories that are specified by using the -Y option can be supplemented by using the -L option. Compiler drivers often use the -Y option to provide compiler specific search paths.

Using an Environment Variable

You can also use the environment variable LD_LIBRARY_PATH to add to the link-editor's library search path. Typically, LD_LIBRARY_PATH takes a colon-separated list of directories. In its most general form, LD_LIBRARY_PATH can also take two directory lists separated by a semicolon. These lists are searched before and after the -Y lists supplied on the command line.

The following example shows the combined effect of setting LD_LIBRARY_PATH and calling the link-editor with several -L occurrences.


$ LD_LIBRARY_PATH=dir1:dir2;dir3
$ export LD_LIBRARY_PATH
$ cc -o prog main.c -Lpath1 .... -Lpath2 .... -Lpathn -lfoo

The effective search path is dir1:dir2:path1:path2.... pathn:dir3:/usr/ccs/lib:/lib:/usr/lib.

If no semicolon is specified as part of the LD_LIBRARY_PATH definition, the specified directory list is interpreted after any -L options. In the following example, the effective search path is path1:path2.... pathn:dir1:dir2:/usr/ccs/lib:/lib:/usr/lib.


$ LD_LIBRARY_PATH=dir1:dir2
$ export LD_LIBRARY_PATH
$ cc -o prog main.c -Lpath1 .... -Lpath2 .... -Lpathn -lfoo

Note –

This environment variable can also be used to augment the search path of the runtime linker. See Directories Searched by the Runtime Linker. To prevent this environment variable from influencing the link-editor, use the -i option.


Directories Searched by the Runtime Linker

The runtime linker looks in two default locations for dependencies. When processing 32–bit objects, the default locations are /lib and /usr/lib. When processing 64–bit objects, the default locations are /lib/64 and /usr/lib/64. All other directories to be searched must be added to the runtime linker's search path explicitly.

When a dynamic executable or shared object is linked with additional shared objects, the shared objects are recorded as dependencies. These dependencies must be located during process execution by the runtime linker. When linking a dynamic object, one or more search paths can be recorded in the output file. These search paths are referred to as a runpath. The runtime linker uses the runpath of an object to locate the dependencies of that object.

Specialized objects can be built with the -z nodefaultlib option to suppress any search of the default location at runtime. Use of this option implies that all the dependencies of an object can be located using its runpaths. Without this option, no matter how you augment the runtime linker's search path, the last search paths used are always the default locations.


Note –

The default search path can be administrated by using a runtime configuration file. See Configuring the Default Search Paths. However, the creator of a dynamic object should not rely on the existence of this file. You should always ensure that an object can locate its dependencies with only its runpaths or the default locations.


You can use the -R option, which takes a colon-separated list of directories, to record a runpath in a dynamic executable or shared object. The following example records the runpath /home/me/lib:/home/you/lib in the dynamic executable prog.


$ cc -o prog main.c -R/home/me/lib:/home/you/lib -Lpath1 \ 
    -Lpath2 file1.c file2.c -lfoo -lbar

The runtime linker uses these paths, followed by the default location, to obtain any shared object dependencies. In this case, this runpath is used to locate libfoo.so.1 and libbar.so.1.

The link-editor accepts multiple -R options. These multiple specifications are concatenate together, separated by a colon. Thus, the previous example can also be expressed as follows.


$ cc -o prog main.c -R/home/me/lib -Lpath1 -R/home/you/lib \ 
    -Lpath2 file1.c file2.c -lfoo -lbar

For objects that can be installed in various locations, the $ORIGIN dynamic string token provides a flexible means of recording a runpath. See Locating Associated Dependencies.


Note –

A historic alternative to specifying the -R option is to set the environment variable LD_RUN_PATH, and make this available to the link-editor. The scope and function of LD_RUN_PATH and -R are identical, but when both are specified, -R supersedes LD_RUN_PATH.


Initialization and Termination Sections

Dynamic objects can supply code that provides for runtime initialization and termination processing. The initialization code of a dynamic object is executed once each time the dynamic object is loaded in a process. The termination code of a dynamic object is executed once each time the dynamic object is unloaded from a process or at process termination. This code can be encapsulated in one of two section types, either an array of function pointers or a single code block. Each of these section types is built from a concatenation of like sections from the input relocatable objects.

The sections .pre_initarray, .init_array and .fini_array provide arrays of runtime pre-initialization, initialization, and termination functions, respectively. When creating a dynamic object, the link-editor identifies these arrays with the .dynamic tag pairs DT_PREINIT_[ARRAY/ARRAYSZ], DT_INIT_[ARRAY/ARRAYSZ], and DT_FINI_[ARRAY/ARRAYSZ] accordingly. These tags identify the associated sections so that the sections can be called by the runtime linker. A pre-initialization array is applicable to dynamic executables only.


Note –

Functions that are assigned to these arrays must be provided from the object that is being built.


The sections .init and .fini provide a runtime initialization and termination code block, respectively. The compiler drivers typically supply .init and .fini sections with files they add to the beginning and end of your input file list. These compiler provided files have the effect of encapsulating the .init and .fini code from your relocatable objects into individual functions. These functions are identified by the reserved symbol names _init and _fini respectively. When creating a dynamic object, the link-editor identifies these symbols with the .dynamic tags DT_INIT and DT_FINI accordingly. These tags identify the associated sections so they can be called by the runtime linker.

For more information about the execution of initialization and termination code at runtime see Initialization and Termination Routines.

The registration of initialization and termination functions can be carried out directly by the link-editor by using the -z initarray and -z finiarray options. For example, the following command places the address of foo() in an .init_array element, and the address of bar() in a .fini_array element.


$ cat main.c
#include    <stdio.h>

void foo()
{
        (void) printf("initializing: foo()\n");
}

void bar()
{
        (void) printf("finalizing: bar()\n");
}

void main()
{
        (void) printf("main()\n");
}

$ cc -o main -zinitarray=foo -zfiniarray=bar main.c
$ main
initializing: foo()
main()
finalizing: bar()

The creation of initialization and termination sections can be carried out directly using an assembler. However, most compilers offer special primitives to simplify their declaration. For example, the previous code example can be rewritten using the following #pragma definitions. These definitions result in a call to foo() being placed in an .init section, and a call to bar() being placed in a .fini section.


$ cat main.c
#include    <stdio.h>

#pragma init (foo)
#pragma fini (bar)

....
$ cc -o main main.c
$ main
initializing: foo()
main()
finalizing: bar()

Initialization and termination code, spread throughout several relocatable objects, can result in different behavior when included in an archive library or shared object. The link-edit of an application that uses this archive might extract only a fraction of the objects contained in the archive. These objects might provide only a portion of the initialization and termination code spread throughout the members of the archive. At runtime, only this portion of code is executed. The same application built against the shared object will have all the accumulated initialization and termination code executed when the dependency is loaded at runtime.

To determine the order of executing initialization and termination code within a process at runtime is a complex issue that involves dependency analysis. Limit the content of initialization and termination code to simplify this analysis. Simplified, self contained, initialization and termination code provides predictable runtime behavior. See Initialization and Termination Order for more details.

Data initialization should be independent if the initialization code is involved with a dynamic object whose memory can be dumped using dldump(3C).

Symbol Processing

During input file processing, all local symbols from the input relocatable objects are passed through to the output file image. All global symbols from the input relocatable objects, together with globals symbols from shared object dependencies, are accumulated internally within the link-editor.

Each global symbol supplied by an input file is searched for within this internal symbol table. If a symbol with the same name has already been encountered from a previous input file, a symbol resolution process is called. This resolution process determines which of two entries from relocatable objects are kept. This resolution process also determines how external references to shared object dependencies are established.

On completion of input file processing, and providing no fatal symbol resolution errors have occurred, the link-editor determines if any unresolved symbol references remain. Unresolved symbol references can cause the link-edit to terminate.

Finally, the link-editor's internal symbol table is added to the symbol tables of the image being created.

The following sections expand upon symbol resolution and undefined symbol processing.

Symbol Resolution

Symbol resolution runs the entire spectrum, from simple and intuitive to complex and perplexing. Most resolutions are carried out silently by the link-editor. However, some relocations can be accompanied by warning diagnostics, while others can result in a fatal error condition.

The most common simple resolutions involve binding symbol references from one object to symbol definitions within another object. This binding can occur between two relocatable objects, or between a relocatable object and the first definition found in a shared object dependency. Complex resolutions typically occur between two or more relocatable objects.

The resolution of two symbols depends on their attributes, the type of file that provides the symbol, and the type of file being generated. For a complete description of symbol attributes, see Symbol Table Section. For the following discussions, however, three basic symbol types are identified.

In its simplest form, symbol resolution involves the use of a precedence relationship. This relationship has defined symbols dominate tentative symbols, which in turn dominate undefined symbols.

The following example of C code shows how these symbol types can be generated. Undefined symbols are prefixed with u_. Tentative symbols are prefixed with t_. Defined symbols are prefixed with d_.


$ cat main.c
extern int      u_bar;
extern int      u_foo();

int             t_bar;
int             d_bar = 1;

int d_foo()
{
        return (u_foo(u_bar, t_bar, d_bar));
}
$ cc -o main.o -c main.c
$ elfdump -s main.o

Symbol Table Section:  .symtab
     index    value      size      type bind oth ver shndx          name
     ....
       [7]  0x00000000 0x00000000  FUNC GLOB  D    0 UNDEF          u_foo
       [8]  0x00000010 0x00000040  FUNC GLOB  D    0 .text          d_foo
       [9]  0x00000004 0x00000004  OBJT GLOB  D    0 COMMON         t_bar
      [10]  0x00000000 0x00000004  NOTY GLOB  D    0 UNDEF          u_bar
      [11]  0x00000000 0x00000004  OBJT GLOB  D    0 .data          d_bar

Simple Resolutions

Simple symbol resolutions are by far the most common. In this case, two symbols with similar characteristics are detected, with one symbol taking precedence over the other. This symbol resolution is carried out silently by the link-editor. For example, with symbols of the same binding, a symbol reference from one file is bound to a defined, or tentative symbol definition, from another file. Or, a tentative symbol definition from one file is bound to a defined symbol definition from another file. This resolution can occur between two relocatable objects, or between a relocatable object and the first definition found in a shared object dependency.

Symbols that undergo resolution can have either a global or weak binding. Within relocatable objects, weak bindings have lower precedence than global binding. Relocatable object symbols with different bindings are resolved according to a slight alteration of the basic rules.

Weak symbols can usually be defined through the compiler, either individually or as aliases to global symbols. One mechanism uses a #pragma definition.


$ cat main.c
#pragma weak    bar
#pragma weak    foo = _foo

int             bar = 1;

int _foo()
{
        return (bar);
}
$ cc -o main.o -c main.c
$ elfdump -s main.o
Symbol Table Section:  .symtab
     index    value      size      type bind oth ver shndx          name
     ....
       [7]  0x00000010 0x00000018  FUNC GLOB  D    0 .text          _foo
       [8]  0x00000010 0x00000018  FUNC WEAK  D    0 .text          foo
       [9]  0x00000000 0x00000004  OBJT WEAK  D    0 .data          bar

Notice that the weak alias foo is assigned the same attributes as the global symbol _foo. This relationship is maintained by the link-editor and results in the symbols being assigned the same value in the output image. In symbol resolution, weak defined symbols are silently overridden by any global definition of the same name.

Another form of simple symbol resolution, interposition, occurs between relocatable objects and shared objects, or between multiple shared objects. In these cases, when a symbol is multiply-defined, the relocatable object, or the first definition between multiple shared objects, is silently taken by the link-editor. The relocatable object's definition, or the first shared object's definition, is said to interpose on all other definitions. This interposition can be used to override the functionality provided by another shared object. Multiply-defined symbols that occur between relocatable objects and shared objects, or between multiple shared objects, are treated identically. A symbols weak binding or global binding is irrelevant. By resolving to the first definition, regardless of the symbols binding, both the link-editor and runtime linker behave consistently.

The combination of weak symbols defined within a shared object together with symbol interposition over the same shared object, can provide a useful programming technique. For example, the standard C library provides several services that you are allowed to redefine. However, ANSI C defines a set of standard services that must be present on the system. These services cannot be replaced in a strictly conforming program.

The function fread(3C), for example, is an ANSI C library function. The system function read(2) is not an ANSI C library function. A conforming ANSI C program must be able to redefine read(2) and still use fread(3C) in a predictable way.

The problem here is that read(2) underlies the fread(3C) implementation in the standard C library. Therefore, a program that redefines read(2) might confuse the fread(3C) implementation. To guard against this occurrence, ANSI C states that an implementation cannot use a name that is not reserved for the implementation. Use the following #pragma directive to define just such a reserved name. Use this name to generate an alias for the function read(2).


#pragma weak read = _read

Thus, you can quite freely define your own read() function without compromising the fread(3C) implementation, which in turn is implemented to use the _read() function.

The link-editor has no difficulty with this redefinition of read(), either when linking against the shared object or archive version of the standard C library. In the former case, interposition takes its course. In the latter case, the fact that the C library's definition of read(2) is weak allows that definition to be quietly overridden.

Use the link-editor's -m option to write a list of all interposed symbol references, along with section load address information, to the standard output.

Complex Resolutions

Complex resolutions occur when two symbols of the same name are found with differing attributes. In these cases, the link-editor generates a warning message, while selecting the most appropriate symbol. This message indicates the symbol, the attributes that conflict, and the identity of the file from which the symbol definition is taken. In the following example, two files with a definition of the data item array have different size requirements.


$ cat foo.c
int array[1];

$ cat bar.c
int array[2] = { 1, 2 };

$ ld -r -o temp.o foo.c bar.c
ld: warning: symbol `array' has differing sizes:
        (file foo.o value=0x4; file bar.o value=0x8);
        bar.o definition taken

A similar diagnostic is produced if the symbol's alignment requirements differ. In both of these cases, the diagnostic can be suppressed by using the link-editor's -t option.

Another form of attribute difference is the symbol's type. In the following example, the symbol bar() has been defined as both a data item and a function.


$ cat foo.c
int bar()
{
        return (0);
}
$ cc -o libfoo.so -G -K pic foo.c
$ cat main.c
int     bar = 1;

int main()
{
        return (bar);
}
$ cc -o main main.c -L. -lfoo
ld: warning: symbol `bar' has differing types:
        (file main.o type=OBJT; file ./libfoo.so type=FUNC);
        main.o definition taken

Note –

Symbol types in this context are classifications that can be expressed in ELF. These symbol types are not related to the data types as employed by the programming language, except in the crudest fashion.


In cases like the previous example, the relocatable object definition is taken when the resolution occurs between a relocatable object and a shared object. Or, the first definition is taken when the resolution occurs between two shared objects. When such resolutions occur between symbols of weak or global binding, a warning is also produced.

Inconsistencies between symbol types are not suppressed by the link-editor's -t option.

Fatal Resolutions

Symbol conflicts that cannot be resolved result in a fatal error condition and an appropriate error message. This message indicates the symbol name together with the names of the files that provided the symbols. No output file is generated. Although the fatal condition is sufficient to terminate the link-edit, all input file processing is first completed. In this manner, all fatal resolution errors can be identified.

The most common fatal error condition exists when two relocatable objects both define non-weak symbols of the same name.


$ cat foo.c
int bar = 1;

$ cat bar.c
int bar()
{ 
        return (0);
}

$ ld -r -o temp.o foo.c bar.c
ld: fatal: symbol `bar' is multiply-defined:
        (file foo.o and file bar.o);
ld: fatal: File processing errors. No output written to int.o

foo.c and bar.c have conflicting definitions for the symbol bar. Because the link-editor cannot determine which should dominate, the link-edit usually terminates with an error message. You can use the link-editor's -z muldefs option to suppress this error condition. This option allows the first symbol definition to be taken.

Undefined Symbols

After all of the input files have been read and all symbol resolution is complete, the link-editor searches the internal symbol table for any symbol references that have not been bound to symbol definitions. These symbol references are referred to as undefined symbols. Undefined symbols can affect the link-edit process according to the type of symbol, together with the type of output file being generated.

Generating an Executable Output File

When generating an executable output file, the link-editor's default behavior is to terminate with an appropriate error message should any symbols remain undefined. A symbol remains undefined when a symbol reference in a relocatable object is never matched to a symbol definition.


$ cat main.c
extern int foo();

int main()
{
        return (foo());
}
$ cc -o prog main.c
Undefined           first referenced
 symbol                 in file
foo                     main.o
ld: fatal: Symbol referencing errors. No output written to prog

Similarly, if a shared object is used to create a dynamic executable and leaves an unresolved symbol definition, an undefined symbol error results.


$ cat foo.c
extern int bar;
int foo()
{
        return (bar);
}

$ cc -o libfoo.so -G -K pic foo.c
$ cc -o prog main.c -L. -lfoo
Undefined           first referenced
 symbol                 in file
bar                     ./libfoo.so
ld: fatal: Symbol referencing errors. No output written to prog

To allow undefined symbols, as in the previous example, use the link-editor's -z nodefs option to suppress the default error condition.


Note –

Take care when using the -z nodefs option. If an unavailable symbol reference is required during the execution of a process, a fatal runtime relocation error occurs. This error might be detected during the initial execution and testing of an application. However, more complex execution paths can result in this error condition taking much longer to detect, which can be time consuming and costly.


Symbols can also remain undefined when a symbol reference in a relocatable object is bound to a symbol definition in an implicitly defined shared object. For example, continuing with the files main.c and foo.c used in the previous example.


$ cat bar.c
int bar = 1;

$ cc -o libbar.so -R. -G -K pic bar.c -L. -lfoo
$ ldd libbar.so
        libfoo.so =>     ./libfoo.so

$ cc -o prog main.c -L. -lbar
Undefined           first referenced
 symbol                 in file
foo                     main.o  (symbol belongs to implicit \
                        dependency ./libfoo.so)
ld: fatal: Symbol referencing errors. No output written to prog

prog is built with an explicit reference to libbar.so. libbar.so has a dependency on libfoo.so. Therefore, an implicit reference to libfoo.so from prog is established.

Because main.c made a specific reference to the interface provided by libfoo.so, prog really has a dependency on libfoo.so. However, only explicit shared object dependencies are recorded in the output file being generated. Thus, prog fails to run if a new version of libbar.so is developed that no longer has a dependency on libfoo.so.

For this reason, bindings of this type are deemed fatal. The implicit reference must be made explicit by referencing the library directly during the link-edit of prog. The required reference is hinted at in the fatal error message that is shown in the preceding example.

Generating a Shared Object Output File

When the link-editor is generating a shared object output file, undefined symbols are allowed to remain at the end of the link-edit. This default behavior allows the shared object to import symbols from a dynamic executable that defines the shared object as a dependency.

The link-editor's -z defs option can be used to force a fatal error if any undefined symbols remain. This option is recommended when creating any shared objects. Shared objects that reference symbols from an application can use the -z defs option, together with defining the symbols by using an extern mapfile directive. See SYMBOL_SCOPE / SYMBOL_VERSION Directives.

A self-contained shared object, in which all references to external symbols are satisfied by named dependencies, provides maximum flexibility. The shared object can be employed by many users without those users having to determine and establish dependencies to satisfy the shared object's requirements.

Weak Symbols

Weak symbol references that remain unresolved, do not result in a fatal error condition, no matter what output file type is being generated.

If a static executable is being generated, the symbol is converted to an absolute symbol with an assigned value of zero.

If a dynamic executable or shared object is being produced, the symbol is left as an undefined weak reference with an assigned value of zero. During process execution, the runtime linker searches for this symbol. If the runtime linker does not find a match, the reference is bound to an address of zero instead of generating a fatal relocation error.

Historically, these undefined weak referenced symbols have been employed as a mechanism to test for the existence of functionality. For example, the following C code fragment might have been used in the shared object libfoo.so.1.


#pragma weak    foo

extern  void    foo(char *);

void bar(char *path)
{
        void (*fptr)(char *);

        if ((fptr = foo) != 0)
                (*fptr)(path);
}

When building an application that references libfoo.so.1, the link-edit completes successfully regardless of whether a definition for the symbol foo is found. If during execution of the application the function address tests nonzero, the function is called. However, if the symbol definition is not found, the function address tests zero and therefore is not called.

Compilation systems view this address comparison technique as having undefined semantics, which can result in the test statement being removed under optimization. In addition, the runtime symbol binding mechanism places other restrictions on the use of this technique. These restrictions prevent a consistent model from being made available for all dynamic objects.


Note –

Undefined weak references in this manner are discouraged. Instead, you should use dlsym(3C) with the RTLD_DEFAULT, or RTLD_PROBE handles as a means of testing for a symbol's existence. See Testing for Functionality.


Tentative Symbol Order Within the Output File

Contributions from input files usually appear in the output file in the order of their contribution. Tentative symbols are an exception to this rule, as these symbols are not fully defined until their resolution is complete. The order of tentative symbols within the output file might not follow the order of their contribution.

If you need to control the ordering of a group of symbols, then any tentative definition should be redefined to a zero-initialized data item. For example, the following tentative definitions result in a reordering of the data items within the output file, as compared to the original order described in the source file foo.c.


$ cat foo.c
char One_array[0x10];
char Two_array[0x20];
char Three_array[0x30];

$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
      [11]  0x00010614 0x00000020  OBJT GLOB  D    0 .bss           Two_array
       [3]  0x00010634 0x00000030  OBJT GLOB  D    0 .bss           Three_array
       [4]  0x00010664 0x00000010  OBJT GLOB  D    0 .bss           One_array

Sorting the symbols based on their address shows that their output order is different than the order they were defined in the source. In contrast, defining these symbols as initialized data items ensures that the relative ordering of these symbols within the input file is carried over to the output file.


$ cat foo.c
char A_array[0x10] = { 0 };
char B_array[0x20] = { 0 };
char C_array[0x30] = { 0 };

$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
       [4]  0x00010614 0x00000010  OBJT GLOB  D    0 .data          One_array
      [11]  0x00010624 0x00000020  OBJT GLOB  D    0 .data          Two_array
       [3]  0x00010644 0x00000030  OBJT GLOB  D    0 .data          Three_array

Defining Additional Symbols

Besides the symbols provided from input files, you can supply additional global symbol references or global symbol definitions to a link-edit. In the simplest form, symbol references can be generated using the link-editor's -u option. Greater flexibility is provided with the link-editor's -M option and an associated mapfile. This mapfile enables you to define global symbol references and a variety of global symbol definitions. Attributes of the symbol such as visibility and type can be specified, See SYMBOL_SCOPE / SYMBOL_VERSION Directives for a complete description of the available options.

Defining Additional Symbols with the -u option

The -u option provides a mechanism for generating a global symbol reference from the link-edit command line. This option can be used to perform a link-edit entirely from archives. This option can also provide additional flexibility in selecting the objects to extract from multiple archives. See section Archive Processing for an overview of archive extraction.

For example, perhaps you want to generate a dynamic executable from the relocatable object main.o, which refers to the symbols foo and bar. You want to obtain the symbol definition foo from the relocatable object foo.o contained in lib1.a, and the symbol definition bar from the relocatable object bar.o, contained in lib2.a.

However, the archive lib1.a also contains a relocatable object that defines the symbol bar. This relocatable object is presumably of differing functionality to the relocatable object that is provided in lib2.a. To specify the required archive extraction, you can use the following link-edit.


$ cc -o prog -L. -u foo -l1 main.o -l2

The -u option generates a reference to the symbol foo. This reference causes extraction of the relocatable object foo.o from the archive lib1.a. The first reference to the symbol bar occurs in main.o, which is encountered after lib1.a has been processed. Therefore, the relocatable object bar.o is obtained from the archive lib2.a.


Note –

This simple example assumes that the relocatable object foo.o from lib1.a does not directly or indirectly reference the symbol bar. If lib1.a does reference bar, then the relocatable object bar.o is also extracted from lib1.a during its processing. See Archive Processing for a discussion of the link-editor's multi-pass processing of an archive.


Defining Symbol References

The following example shows how three symbol references can be defined. These references are then used to extract members of an archive. Although this archive extraction can be achieved by specifying multiple -u options to the link-edit, this example also shows how the eventual scope of a symbol can be reduced to local.


$ cat foo.c
#include <stdio.h>
void foo()
{
        (void) printf("foo: called from lib.a\n");
}
$ cat bar.c
#include <stdio.h>
void bar()
{
        (void) printf("bar: called from lib.a\n");
}
$ cat main.c
extern  void    foo(), bar();

void main()
{
        foo();
        bar();
}
$ cc -c foo.c bar.c main.c
$ ar -rc lib.a foo.o bar.o main.o
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                foo;
                bar;
        global:
                main;
};
$ cc -o prog -M mapfile lib.a
$ prog
foo: called from lib.a
bar: called from lib.a
$ elfdump -sN.symtab prog | egrep 'main$|foo$|bar$'
      [29]  0x00010f30 0x00000024  FUNC LOCL  H    0 .text          bar
      [30]  0x00010ef8 0x00000024  FUNC LOCL  H    0 .text          foo
      [55]  0x00010f68 0x00000024  FUNC GLOB  D    0 .text          main

The significance of reducing symbol scope from global to local is covered in more detail in the section Reducing Symbol Scope.

Defining Absolute Symbols

The following example shows how two absolute symbol definitions can be defined. These definitions are then used to resolve the references from the input file main.c.


$ cat main.c
#include <stdio.h>
extern  int     foo();
extern  int     bar;

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION; VALUE=0x400 };
                bar     { TYPE=DATA;     VALUE=0x800 };
};
$ cc -o prog -M mapfile main.c
$ prog
&foo = 0x400
&bar = 0x800
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
      [45]  0x00000800 0x00000000  OBJT GLOB  D    0 ABS            bar
      [69]  0x00000400 0x00000000  FUNC GLOB  D    0 ABS            foo

When obtained from an input file, symbol definitions for functions or data items are usually associated with elements of data storage. A mapfile definition is insufficient to be able to construct this data storage, so these symbols must remain as absolute values. A simple mapfile definition that is associated with a size, but no value results in the creation of data storage. In this case, the symbol definition is accompanied with a section index. However, a mapfile definition that is accompanied with a value results in the creation of an absolute symbol. If a symbol is defined in a shared object, an absolute definition should be avoided. See Augmenting a Symbol Definition.

Defining Tentative Symbols

A mapfile can also be used to define a COMMON, or tentative, symbol. Unlike other types of symbol definition, tentative symbols do not occupy storage within a file, but define storage that must be allocated at runtime. Therefore, symbol definitions of this kind can contribute to the storage allocation of the output file being generated.

A feature of tentative symbols that differs from other symbol types is that their value attribute indicates their alignment requirement. A mapfile definition can therefore be used to realign tentative definitions that are obtained from the input files of a link-edit.

The following example shows the definition of two tentative symbols. The symbol foo defines a new storage region whereas the symbol bar is actually used to change the alignment of the same tentative definition within the file main.c.


$ cat main.c
#include <stdio.h>
extern  int     foo;
int             bar[0x10];

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=COMMON; VALUE=0x4;   SIZE=0x200 };
                bar     { TYPE=COMMON; VALUE=0x102; SIZE=0x40 };
};
$ cc -o prog -M mapfile main.c
ld: warning: symbol 'bar' has differing alignments:
	(file mapfile value=0x102; file main.o value=0x4);
	largest value applied
$ prog
&foo = 0x21264
&bar = 0x21224
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
      [45]  0x00021224 0x00000040  OBJT GLOB  D    0 .bss           bar
      [69]  0x00021264 0x00000200  OBJT GLOB  D    0 .bss           foo

Note –

This symbol resolution diagnostic can be suppressed by using the link-editor's -t option.


Augmenting a Symbol Definition

The creation of an absolute data symbol within a shared object should be avoided. An external reference from a dynamic executable to a data item within a shared object typically requires the creation of a copy relocation. See Copy Relocations. To provide for this relocation, the data item should be associated with data storage. This association can be produced by defining the symbol within a relocatable object file. This association can also be produced by defining the symbol within a mapfile together with a size declaration and no value declaration. See SYMBOL_SCOPE / SYMBOL_VERSION Directives.

A data symbol can be filtered. See Shared Objects as Filters. To provide this filtering, an object file definition can be augmented with a mapfile definition. The following example creates a filter containing a function and data definition.


$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION;       FILTER=filtee.so.1 };
                bar     { TYPE=DATA; SIZE=0x4; FILTER=filtee.so.1 };
        local:
                *;
};
$ cc -o filter.so.1 -G -Kpic -h filter.so.1 -M mapfile -R.
$ elfdump -sN.dynsym filter.so.1 | egrep 'foo|bar'
       [1]  0x000105f8 0x00000004  OBJT GLOB  D    1 .data          bar
       [7]  0x00000000 0x00000000  FUNC GLOB  D    1 ABS            foo
$ elfdump -y filter.so.1 | egrep 'foo|bar'
       [1]  F        [0] filtee.so.1        bar
       [7]  F        [0] filtee.so.1        foo

At runtime, a reference from an external object to either of these symbols is resolved to the definition within the filtee.

Reducing Symbol Scope

Symbol definitions that are defined to have local scope within a mapfile can be used to reduce the symbol's eventual binding. This mechanism removes the symbol's visibility to future link-edits which use the generated file as part of their input. In fact, this mechanism can provide for the precise definition of a file's interface, and so restrict the functionality made available to others.

For example, say you want to generate a simple shared object from the files foo.c and bar.c. The file foo.c contains the global symbol foo, which provides the service that you want to make available to others. The file bar.c contains the symbols bar and str, which provide the underlying implementation of the shared object. A shared object created with these files, typically results in the creation of three symbols with global scope.


$ cat foo.c
extern  const char *bar();

const char *foo()
{
        return (bar());
}
$ cat bar.c
const char *str = "returned from bar.c";

const char *bar()
{
        return (str);
}
$ cc -o libfoo.so.1 -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [41]  0x00000560 0x00000018  FUNC GLOB  D    0 .text          bar
      [44]  0x00000520 0x0000002c  FUNC GLOB  D    0 .text          foo
      [45]  0x000106b8 0x00000004  OBJT GLOB  D    0 .data          str

You can now use the functionality offered by libfoo.so.1 as part of the link-edit of another application. References to the symbol foo are bound to the implementation provided by the shared object.

Because of their global binding, direct reference to the symbols bar and str is also possible. This visibility can have dangerous consequences, as you might later change the implementation that underlies the function foo. In so doing, you could unintentionally cause an existing application that had bound to bar or str to fail or misbehave.

Another consequence of the global binding of the symbols bar and str is that these symbols can be interposed upon by symbols of the same name. The interposition of symbols within shared objects is covered in section Simple Resolutions. This interposition can be intentional and be used as a means of circumventing the intended functionality offered by the shared object. On the other hand, this interposition can be unintentional, the result of the same common symbol name used for both the application and the shared object.

When developing the shared object, you can protect against these scenarios by reducing the scope of the symbols bar and str to a local binding. In the following example, the symbols bar and str are no longer available as part of the shared object's interface. Thus, these symbols cannot be referenced, or interposed upon, by an external object. You have effectively defined an interface for the shared object. This interface can be managed while hiding the details of the underlying implementation.


$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                bar;
                str;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [24]  0x00000548 0x00000018  FUNC LOCL  H    0 .text          bar
      [25]  0x000106a0 0x00000004  OBJT LOCL  H    0 .data          str
      [45]  0x00000508 0x0000002c  FUNC GLOB  D    0 .text          foo

This symbol scope reduction has an additional performance advantage. The symbolic relocations against the symbols bar and str that would have been necessary at runtime are now reduced to relative relocations. See When Relocations are Performed for details of symbolic relocation overhead.

As the number of symbols that are processed during a link-edit increases, defining local scope reduction within a mapfile becomes harder to maintain. An alternative and more flexible mechanism enables you to define the shared object's interface in terms of the global symbols that should be maintained. Global symbol definitions allow the link-editor to reduce all other symbols to local binding. This mechanism is achieved using the special auto-reduction directive “*”. For example, the previous mapfile definition can be rewritten to define foo as the only global symbol required in the output file generated.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [26]  0x00000570 0x00000018  FUNC LOCL  H    0 .text          bar
      [27]  0x000106d8 0x00000004  OBJT LOCL  H    0 .data          str
      [50]  0x00000530 0x0000002c  FUNC GLOB  D    0 .text          foo

This example also defines a version name, libfoo.so.1.1, as part of the mapfile directive. This version name establishes an internal version definition that defines the file's symbolic interface. The creation of a version definition is recommended. The definition forms the foundation of an internal versioning mechanism that can be used throughout the evolution of the file. See Chapter 5, Application Binary Interfaces and Versioning.


Note –

If a version name is not supplied, the output file name is used to label the version definition. The versioning information that is created within the output file can be suppressed using the link-editor's -z noversion option.


Whenever a version name is specified, all global symbols must be assigned to a version definition. If any global symbols remain unassigned to a version definition, the link-editor generates a fatal error condition.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
Undefined           first referenced
 symbol                 in file
str                     bar.o  (symbol has no version assigned)
bar                     bar.o  (symbol has no version assigned)
ld: fatal: Symbol referencing errors. No output written to libfoo.so.1

The -B local option can be used to assert the auto-reduction directive “*” from the command line. The previous example an be compiled successfully as follows.


$ cc -o libfoo.so.1 -M mapfile -B local -G foo.c bar.c

When generating an executable or shared object, any symbol reduction results in the recording of version definitions within the output image. When generating a relocatable object, the version definitions are created but the symbol reductions are not processed. The result is that the symbol entries for any symbol reductions still remain global. For example, using the previous mapfile with the auto-reduction directive and associated relocatable objects, an intermediate relocatable object is created with no symbol reduction.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ ld -o libfoo.o -M mapfile -r foo.o bar.o
$ elfdump -s libfoo.o | egrep 'foo$|bar$|str$'
      [28]  0x00000050 0x00000018  FUNC GLOB  H    0 .text          bar
      [29]  0x00000010 0x0000002c  FUNC GLOB  D    2 .text          foo
      [30]  0x00000000 0x00000004  OBJT GLOB  H    0 .data          str

The version definitions created within this image show that symbol reductions are required. When the relocatable object is used eventually to generate an executable or shared object, the symbol reductions occur. In other words, the link-editor reads and interprets symbol reduction information that is contained in the relocatable objects in the same manner as versioning data is processed from a mapfile.

Thus, the intermediate relocatable object produced in the previous example can now be used to generate a shared object.


$ ld -o libfoo.so.1 -G libfoo.o
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [24]  0x00000508 0x00000018  FUNC LOCL  H    0 .text          bar
      [25]  0x00010644 0x00000004  OBJT LOCL  H    0 .data          str
      [42]  0x000004c8 0x0000002c  FUNC GLOB  D    0 .text          foo

Symbol reduction at the point at which an executable or shared object is created is typically the most common requirement. However, symbol reductions can be forced to occur when creating a relocatable object by using the link-editor's -B reduce option.


$ ld -o libfoo.o -M mapfile -B reduce -r foo.o bar.o
$ elfdump -sN.symtab libfoo.o | egrep 'foo$|bar$|str$'
      [20]  0x00000050 0x00000018  FUNC LOCL  H    0 .text          bar
      [21]  0x00000000 0x00000004  OBJT LOCL  H    0 .data          str
      [30]  0x00000010 0x0000002c  FUNC GLOB  D    2 .text          foo

Symbol Elimination

An extension to symbol reduction is the elimination of a symbol entry from an object's symbol table. Local symbols are only maintained in an object's .symtab symbol table. This entire table can be removed from the object by using the link-editor's -s option, or strip(1). On occasion, you might want to maintain the .symtab symbol table but remove selected local symbol definitions.

Symbol elimination can be carried out using the mapfile keyword ELIMINATE. As with the local directive, symbols can be individually defined, or the symbol name can be defined as the special auto-elimination directive “*”. The following example shows the elimination of the symbol bar for the previous symbol reduction example.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                str;
        eliminate:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
      [26]  0x00010690 0x00000004  OBJT LOCL  H    0 .data          str
      [44]  0x000004e8 0x0000002c  FUNC GLOB  D    0 .text          foo

The -B eliminate option can be used to assert the auto-elimination directive “*” from the command line.

External Bindings

When a symbol reference from the object being created is satisfied by a definition within a shared object, the symbol remains undefined. The relocation information that is associated with the symbol provides for its lookup at runtime. The shared object that provided the definition typically becomes a dependency.

The runtime linker employs a default search model to locate this definition at runtime. Typically, each object is searched, starting with the dynamic executable, and progressing through each dependency in the same order in which the objects were loaded.

Objects can also be created to use direct bindings. With this technique, the relationship between the symbol reference and the object that provides the symbol definition is maintained within the object being created. The runtime linker uses this information to directly bind the reference to the object that defines the symbol, thus bypassing the default symbol search model. See Appendix D, Direct Bindings.

String Table Compression

String tables are compressed by the link-editor by removing duplicate entries, together with tail substrings. This compression can significantly reduce the size of any string tables. For example, a compressed .dynstr table results in a smaller text segment and hence reduced runtime paging activity. Because of these benefits, string table compression is enabled by default.

Objects that contribute a very large number of symbols can increase the link-edit time due to the string table compression. To avoid this cost during development use the link-editors -z nocompstrtab option. Any string table compression performed during a link-edit can be displayed using the link-editors debugging tokens -D strtab,detail.

Generating the Output File

After input file processing and symbol resolution has completed with no fatal errors, the link-editor generates the output file. The link-editor first generates the additional sections necessary to complete the output file. These sections include the symbol tables, which contain local symbol definitions together with resolved global symbol and weak symbol information, from all the input files.

Also included are any output relocation and dynamic information sections required by the runtime linker. After all the output section information has been established, the total output file size is calculated. The output file image is then created accordingly.

When creating a dynamic executable or shared object, two symbol tables are usually generated. The .dynsym table and its associated string table .dynstr contain register, global, weak, and section symbols. These sections become part of the text segment that is mapped as part of the process image at runtime. See mmapobj(2). This mapping enables the runtime linker to read these sections to perform any necessary relocations.

The .symtab table, and its associated string table .strtab contain all the symbols collected from the input file processing. These sections are not mapped as part of the process image. These sections can even be stripped from the image by using the link-editor's -s option, or after the link-edit by using strip(1).

During the generation of the symbol tables, reserved symbols are created. These symbols have special meaning to the linking process. These symbols should not be defined in your code.

_etext

The first location after all read-only information, typically referred to as the text segment.

_edata

The first location after initialized data.

_end

The first location after all data.

_DYNAMIC

The address of the .dynamic information section.

_END_

The same as _end. The symbol has local scope and, together with the _START_ symbol, provides a simple means of establishing an object's address range.

_GLOBAL_OFFSET_TABLE_

The position-independent reference to a link-editor supplied table of addresses, the .got section. This table is constructed from position-independent data references that occur in objects that have been compiled with the -K pic option. See Position-Independent Code.

_PROCEDURE_LINKAGE_TABLE_

The position-independent reference to a link-editor supplied table of addresses, the .plt section. This table is constructed from position-independent function references that occur in objects that have been compiled with the -K pic option. See Position-Independent Code.

_START_

The first location within the text segment. The symbol has local scope and, together with the _END_ symbol, provides a simple means of establishing an object's address range.

When generating an executable, the link-editor looks for additional symbols to define the executable's entry point. If a symbol was specified using the link-editor's -e option, that symbol is used. Otherwise the link-editor looks for the reserved symbol names _start, and then main.

Identifying Capability Requirements

Capabilities identify the attributes of a system that are required to allow code to execute. The following capabilities, in their order of precedence, are available.

Each of these capabilities can be defined individually, or combined to produce a capabilities group.

Code that can only be executed when certain capabilities are available should identify these requirements by means of a capabilities section within the associated ELF object. Recording capability requirements within an object allows the system to validate the object before attempting to execute the associated code. These requirements can also provide a framework where the system can select the most appropriate object from a family of objects. A family consists of variants of the same object, where each variant requires different capabilities.

Dynamic objects, as well as individual functions or initialized data items within an object, can be associated with capability requirements. Ideally, capability requirements are recorded in the relocatable objects that are produced by the compiler, and reflect the options or optimization that was specified at compile time. The link-editor combines the capabilities of any input relocatable objects to create a final capabilities section for the output file. See Capabilities Section.

In addition, capabilities can be defined when the link-editor creates an output file. These capabilities are identified using a mapfile and the link-editor's -M option. Capabilities that are defined by using a mapfile can augment, or override, the capabilities that are specified within any input relocatable objects. Mapfiles are usually used to augment compilers that do not generate the necessary capability information.

System capabilities are the capabilities that describe a running system. The platform name, and machine hardware name can be displayed with uname(1) using the -i option and -m option respectively. The system hardware capabilities can be displayed with isainfo(1) using the -v option. At runtime, the capability requirements of an object are compared against the system capabilities to determine whether the object can be loaded, or a symbol within the object can be used.

Object capabilities are capabilities that are associated with an object. These capabilities define the requirements of the entire object, and control whether the object can be loaded at runtime. If an object requires capabilities that can not be satisfied by the system, then the object can not be loaded at runtime. Capabilities can be used to provide more than one instance of a given object, each optimized for systems that match the objects requirements. The runtime linker can transparently select the best instance from such a family of object instances by comparing the objects capability requirements to the capabilities provided by the system.

Symbol capabilities are capabilities that are associated with individual functions, or initialized data items, within an object. These capabilities define the requirements of one or more symbols within an object, and control whether the symbol can be used at runtime. Symbol capabilities allow for the presence of multiple instances of a function within a single object. Each instance of the function can be optimized for a system with different capabilities. Symbol capabilities also allow for the presence of multiple instances of an initialized data item within an object. Each instance of the data can define system specific data. If a symbol instance requires capabilities that can not be satisfied by the system, then that symbol instance can not be used at runtime. Instead, an alternative instance of the same symbol name must be used. Symbol capabilities offer the ability to construct a single object that can be used on systems of varying abilities. A family of functions can provide optimized instances for systems that can support the capabilities, and more generic instances for other, less capable systems. A family of initialized data items can provide system specific data. The runtime linker transparently selects the best instance from such a family of symbol instances by comparing the symbols capability requirements to the capabilities provided by the system.

Object and symbol capabilities provide for selecting the best object, and the best symbol within an object, for the currently running system. Object and symbol capabilities are optional features, both independent of each other. However, an object that defines symbol capabilities may also define object capabilities. In this case, any family of capabilities symbols should be accompanied with one instance of the symbol that satisfies the object capabilities. If no object capabilities exist, any family of capability symbols should be accompanied with one instance of the symbol that requires no capabilities. This symbol instance provides the default implementation, should no capability instance be applicable for a given system.

The following x86 example displays the object capabilities of foo.o. These capabilities apply to the entire object. In this example, no symbol capabilities exist.


$ elfdump -H foo.o

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_HW_1     0x840  [ SSE  MMX ]

The following x86 example displays the symbol capabilities of bar.o. These capabilities apply to the individual functions foo and bar. Two instances of each symbol exist, each instance being assigned to a different set of capabilities. In this example, no object capabilities exist.


$ elfdump -H bar.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_HW_1      0x40  [ MMX ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
      [25]  0x00000000 0x00000021  FUNC LOCL  D    0 .text    foo%mmx
      [26]  0x00000024 0x0000001e  FUNC LOCL  D    0 .text    bar%mmx

 Symbol Capabilities:
     index  tag               value
       [3]  CA_SUNW_HW_1      0x800  [ SSE ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
      [33]  0x00000044 0x00000021  FUNC LOCL  D    0 .text    foo%sse
      [34]  0x00000068 0x0000001e  FUNC LOCL  D    0 .text    bar%sse

Note –

In this example, the capability symbols follow a naming convention that appends a capability identifier to the generic symbol name. This convention can be produced by the link-editor when object capabilities are converted to symbol capabilities, and is discussed later in Converting Object Capabilities to Symbol Capabilities.


Capability definitions provide for many combinations that allow you to identify the requirements of an object, or of individual symbols within an object. Hardware capabilities provide the greatest flexibility. Hardware capabilities define hardware requirements without dictating a specific machine hardware name, or platform name. However, sometimes there are attributes of an underlying system that can only be determined from the machine hardware name, or platform name. Identifying a capability name can allow you to code to very specific system capabilities, but the use of the identified object can be restrictive. Should a new machine hardware name or platform name become applicable for the object, the object must be rebuilt to identify the new capability name.

The following sections describe how capabilities can be defined, and used by the link-editor.

Identifying a Platform Capability

A platform capability of an object identifies the platform name of the systems that the object, or specific symbols within the object, can execute upon. Multiple platform capabilities can be defined. This identification is very specific, and takes precedence over any other capability types.

The platform name of a system can be displayed by the utility uname(1) with the -i option.

A platform capability requirement can be defined using the following mapfile syntax.

        $mapfile_version 2
        CAPABILITY {
                PLATFORM  = platform_name...;
                PLATFORM += platform_name...;
                PLATFORM -= platform_name...;
        };

The PLATFORM attribute is qualified with one or more platform names. The “+=” form of assignment augments the platform capabilities specified by the input objects, while the “=” form overrides them. The “-=" form of assignment is used to exclude platform capabilities from the output object. The following SPARC example identifies the object foo.so.1 as being specific to the SUNW,SPARC-Enterprise platform.


$ cat mapfile
$mapfile_version 2
CAPABILITY {
        PLATFORM = 'SUNW,SPARC-Enterprise';
};
$ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc
$ elfdump -H foo.so.1

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_PLAT     SUNW,SPARC-Enterprise

Relocatable objects can define platform capabilities. These capabilities are gathered together to define the final capability requirements of the object being built.

The platform capability of an object can be controlled explicitly from a mapfile by using the “=” form of assignment to override any platform capabilities that might be provided from any input relocatable objects. An empty PLATFORM attribute used with the “=” form of assignment effectively removes any platform capabilities requirement from the object being built.

A platform capability requirement defined in a dynamic object is validated by the runtime linker against the platform name of the system. The object is only used if one of the platform names recorded in the object match the platform name of the system.

Targeting code to a specific platform can be useful in some instances, however the development of a hardware capabilities family can provide greater flexibility, and is recommended. Hardware capabilities families can provide for optimized code to be exercised on a broader range of systems.

Identifying a Machine Capability

A machine capability of an object identifies the machine hardware name of the systems that the object, or specific symbols within the object, can execute upon. Multiple machine capabilities can be defined. This identification carries less precedence than platform capability definitions, but takes precedence over any other capability types.

The machine hardware name of a system can be displayed by the utility uname(1) with the -m option.

A machine capability requirement can be defined using the following mapfile syntax.

        $mapfile_version 2
        CAPABILITY {
                MACHINE  = machine_name...;
                MACHINE += machine_name...;
                MACHINE -= machine_name...;
        };

The MACHINE attribute is qualified with one or more machine hardware names. The “+=” form of assignment augments the machine capabilities specified by the input objects, while the “=” form overrides them. The “-=" form of assignment is used to exclude machine capabilities from the output object. The following SPARC example identifies the object foo.so.1 as being specific to the sun4u machine hardware name.


$ cat mapfile
$mapfile_version 2
CAPABILITY {
        MACHINE = sun4u;
};
$ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc
$ elfdump -H foo.so.1

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_MACH     sun4u

Relocatable objects can define machine capabilities. These capabilities are gathered together to define the final capability requirements of the object being built.

The machine capability of an object can be controlled explicitly from a mapfile by using the “=” form of assignment to override any machine capabilities that might be provided from any input relocatable objects. An empty MACHINE attribute used with the “=” form of assignment effectively removes any machine capabilities requirement from the object being built.

A machine capability requirement defined in a dynamic object is validated by the runtime linker against the machine hardware name of the system. The object is only used if one of the machine names recorded in the object match the machine name of the system.

Targeting code to a specific machine can be useful in some instances, however the development of a hardware capabilities family can provide greater flexibility, and is recommended. Hardware capabilities families can provide for optimized code to be exercised on a broader range of systems.

Identifying Hardware Capabilities

The hardware capabilities of an object identify the hardware requirements of a system necessary for the object, or specific symbol, to execute correctly. An example of this requirement might be the identification of code that requires the MMX or SSE features that are available on some x86 architectures.

Hardware capability requirements can be identified using the following mapfile syntax.

        $mapfile_version 2
        CAPABILITY {
                HW  = hwcap_flag...;
                HW += hwcap_flag...;
                HW -= hwcap_flag...;
        };

The HW attribute to the CAPABILITY directive is qualified with one or more tokens, which are symbolic representations of hardware capabilities. The “+=” form of assignment augments the hardware capabilities specified by the input objects, while the “=” form overrides them. The “-=" form of assignment is used to exclude hardware capabilities from the output object.

For SPARC systems, hardware capabilities are defined as AV_ values in sys/auxv_SPARC.h. For x86 systems, hardware capabilities are defined as AV_ values in sys/auxv_386.h.

The following x86 example shows the declaration of MMX and SSE as hardware capabilities required by the object foo.so.1.


$ egrep "MMX|SSE" /usr/include/sys/auxv_386.h
#define AV_386_MMX    0x0040
#define AV_386_SSE    0x0800
$ cat mapfile
$mapfile_version 2
CAPABILITY {
        HW += SSE MMX;
};
$ cc -o foo.so.1 -G -K pic -Mmapfile foo.c -lc
$ elfdump -H foo.so.1

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
      [0]  CA_SUNW_HW_1     0x840  [ SSE  MMX ]

Relocatable objects can contain hardware capabilities values. The link-editor combines any hardware capabilities values from multiple input relocatable objects. The resulting CA_SUNW_HW_1 value is a bitwise-inclusive OR of the associated input values. By default, these values are combined with the hardware capabilities specified by a mapfile.

The hardware capability requirements of an object can be controlled explicitly from a mapfile by using the “=” form of assignment to override any hardware capabilities that might be provided from any input relocatable objects. An empty HW attribute used with the “=” form of assignment effectively removes any hardware capabilities requirement from the object being built.

The following example suppresses any hardware capabilities data defined by the input relocatable object foo.o from being included in the output file, bar.o.


$ elfdump -H foo.o

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_HW_1     0x840  [ SSE  MMX ]
$ cat mapfile
$mapfile_version 2
CAPABILITY {
        HW = ;
};
$ ld -o bar.o -r -Mmapfile foo.o
$ elfdump -H bar.o
$ 

Any hardware capability requirements defined by a dynamic object are validated by the runtime linker against the hardware capabilities that are provided by the system. If any of the hardware capability requirements can not be satisfied, the object is not loaded at runtime. For example, if the SSE feature is not available to a process, ldd(1) indicates the following error.


$ ldd prog
        foo.so.1 =>      ./foo.so.1  - hardware capability unsupported: \
                         0x800 [ SSE ]
        ....

Multiple variants of a dynamic object that exploit different hardware capabilities can provide a flexible runtime environment using filters. See Capability Specific Shared Objects.

Hardware capabilities can also be used to identify the capabilities of individual functions within a single object. In this case, the runtime linker can select the most appropriate function instance to use based upon the current system capabilities. See Creating a Family of Symbol Capabilities Functions.

Identifying Software Capabilities

The software capabilities of an object identify characteristics of the software that might be important for debugging or monitoring processes. Software capabilities can also influence process execution. Presently, the only software capabilities that are recognized relate to frame pointer usage by the object, and process address space restrictions.

Objects can indicate that their frame pointer use is known. This state is then qualified by declaring the frame pointer as being used or not.

64–bit objects can indicate that at runtime they must be exercised within a 32–bit address space.

Software capabilities flags are defined in sys/elf.h.

#define  SF1_SUNW_FPKNWN    0x001
#define  SF1_SUNW_FPUSED    0x002
#define  SF1_SUNW_ADDR32    0x004

These software capability requirements can be identified using the following mapfile syntax.

        $mapfile_version 2
        CAPABILITY {
                SF  = sfcap_flags...;
                SF += sfcap_flags...;
                SF -= sfcap_flags...;
        };

The SF attribute to the CAPABILITY directive can be assigned any of the tokens FPKNWN, FPUSED and ADDR32.

Relocatable objects can contain software capabilities values. The link-editor combines the software capabilities values from multiple input relocatable objects. Software capabilities can also be supplied with a mapfile. By default, any mapfile values are combined with the values supplied by relocatable objects.

The software capability requirements of an object can be controlled explicitly from a mapfile by using the “=” form of assignment to override any software capabilities that might be provided from any input relocatable objects. An empty SF attribute used with the “=” form of assignment effectively removes any software capabilities requirement from the object being built.

The following example suppresses any software capabilities data defined by the input relocatable object foo.o from being included in the output file, bar.o.


$ elfdump -H foo.o

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_SF_1     0x3  [ SF1_SUNW_FPKNWN  SF1_SUNW_FPUSED ]
$ cat mapfile
$mapfile_version 2
CAPABILITY {
        SF = ;
};
$ ld -o bar.o -r -Mmapfile foo.o
$ elfdump -H bar.o
$ 

Software Capability Frame Pointer Processing

The computation of a CA_SUNW_SF_1 value from two frame pointer input values is as follows.

Table 2–1 CA_SUNW_SF_1 Frame Pointer Flag Combination State Table

Input file 1 

Input file 2 

 

SF1_SUNW_FPKNWN SF1_SUNW_FPUSED

SF1_SUNW_FPKNWN

<unknown>

SF1_SUNW_FPKNWN SF1_SUNW_FPUSED

SF1_SUNW_FPKNWN SF1_SUNW_FPUSED

SF1_SUNW_FPKNWN

SF1_SUNW_FPKNWN SF1_SUNW_FPUSED

SF1_SUNW_FPKNWN

SF1_SUNW_FPKNWN

SF1_SUNW_FPKNWN

SF1_SUNW_FPKNWN

<unknown>

SF1_SUNW_FPKNWN SF1_SUNW_FPUSED

SF1_SUNW_FPKNWN

<unknown>

This computation is applied to each relocatable object value and mapfile value. The frame pointer software capabilities of an object are unknown if no .SUNW_cap section exists, or if the section contains no CA_SUNW_SF_1 value, or if neither the SF1_SUNW_FPKNW or SF1_SUNW_FPUSED flags are set.

Software Capability Address Space Restriction Processing

64–bit objects that are identified with the SF1_SUNW_ADDR32 software capabilities flag can contain optimized code that requires a 32–bit address space. 64–bit objects that are identified in this manner can interoperate with any other 64–bit objects whether they are identified with the SF1_SUNW_ADDR32 flag or not. An occurrence of the SF1_SUNW_ADDR32 flag within a 64–bit input relocatable object is propagated to the CA_SUNW_SF_1 value that is created for the output file being created by the link-editor.

The existence of the SF1_SUNW_ADDR32 flag within a 64–bit executable ensures that the associated process is restricted to the lower 32–bit address space. This restricted address space includes the process stack and all process dependencies. Within such a process, all objects, whether they are identified with the SF1_SUNW_ADDR32 flag or not, are loaded within the restricted 32–bit address space.

64–bit shared objects can contain the SF1_SUNW_ADDR32 flag. However, the restricted address space requirement can only be established by a 64–bit executable containing the SF1_SUNW_ADDR32 flag. Therefore, a 64–bit SF1_SUNW_ADDR32 shared object must be a dependency of a 64–bit SF1_SUNW_ADDR32 executable.

A 64–bit SF1_SUNW_ADDR32 shared object that is encountered by the link-editor when building an unrestricted 64–bit executable results in a warning.


$ cc -m64 -o main main.c -lfoo
ld: warning: file libfoo.so: section .SUNW_cap: software capability ADDR32: \
    requires executable be built with ADDR32 capability

A 64–bit SF1_SUNW_ADDR32 shared object that is encountered at runtime by a process that is created from an unrestricted 64–bit executable, results in a fatal error.


$ ldd main
        libfoo.so =>     ./libfoo.so  - software capability unsupported: \ 
                         0x4  [ ADDR32 ]
        ....
$ main
ld.so.1: main: fatal: ./libfoo.so: software capability unsupported: 0x4  [ ADDR32 ]

An executable can be seeded with the SF1_SUNW_ADDR32 using a mapfile.


$ cat mapfile
$mapfile_version 2
CAPABILITY {
        SF += ADDR32;
};
$ cc -m64 -o main main.c -Mmapfile -lfoo
$ elfdump -H main

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_SF_1     0x4  [ SF1_SUNW_ADDR32 ]

Creating a Family of Symbol Capabilities Functions

Developers often desire to provide multiple instances of functions, each optimized for a particular set of capabilities, within a single object. It is desirable for the selection and use of these instances to be transparent to any consumers. A generic, front-end function can be created to provide an external interface. This generic instance, together with the optimized instances, can be combined into one object. The generic instance might use getisax(2) to determine the systems capabilities and then call the appropriate optimized function instance to handle a task. Although this model works, it suffers from a lack of generality, and incurs a runtime overhead.

Symbol capabilities offer an alternative mechanism to construct such an object. This mechanism is simpler, more efficient, and does not require you to write additional front-end code. Multiple instances of a function can be created and associated with different capabilities. These instances, together with a default instance of the function that is suitable for any system, can be combined into a single dynamic object. The selection of the most appropriate member from this family of symbols is carried out by the runtime linker using the symbol capabilities information.

In the following example, the x86 objects foobar.mmx.o and foobar.sse.o, contain the same function foo() and bar(), that have been compiled to use the MMX and SSE instructions respectively.


$ elfdump -H foobar.mmx.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID        mmx
       [2]  CA_SUNW_HW_1      0x40  [ MMX ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
      [10]  0x00000000 0x00000021  FUNC LOCL  D    0 .text    foo%mmx
      [16]  0x00000024 0x0000001e  FUNC LOCL  D    0 .text    bar%mmx

$ elfdump -H foobar.sse.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID        sse
       [2]  CA_SUNW_HW_1      0x800  [ SSE ]

  Capabilities symbols:
     index    value      size      type bind oth ver shndx    name
      [16]  0x00000000 0x0000002f  FUNC LOCL  D    0 .text    foo%sse
      [18]  0x00000048 0x00000030  FUNC LOCL  D    0 .text    bar%sse

Each of these objects contain a local symbol identifying the capabilities function foo%*() and bar%*(). In addition, each object also defines a global reference to the function foo() and bar(). Any internal references to foo() or bar() are relocated through these global references, as are any external interfaces.

These two objects can now be combined with a default instance of foo() and bar(). These default instances satisfy the global references, and provide an implementation that is compatible with any object capabilities. These default instances are said to lead each capabilities family. If no object capabilities exist, this default instance should also require no capabilities. Effectively, three instances of foo() and bar() exist, the global instance provides the default, and the local instances provide implementations that are used at runtime if the associated capabilities are available.


$ cc -o libfoobar.so.1 -G foobar.o foobar.sse.o foobar.mmx.o
$ elfdump -sN.dynsym libfoobar.so.1 | egrep "foo|bar"
       [2]  0x00000700 0x00000021  FUNC LOCL  D    0 .text    foo%mmx
       [4]  0x00000750 0x0000002f  FUNC LOCL  D    0 .text    foo%sse
       [8]  0x00000784 0x0000001e  FUNC LOCL  D    0 .text    bar%mmx
       [9]  0x000007b0 0x00000030  FUNC LOCL  D    0 .text    bar%sse
      [15]  0x000007a0 0x00000014  FUNC GLOB  D    1 .text    foo
      [17]  0x000007c0 0x00000014  FUNC GLOB  D    1 .text    bar

The capabilities information for a dynamic object displays the capabilities symbols, and reveals the capabilities families that are available.


$ elfdump -H libfoobar.so.1

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID        mmx
       [2]  CA_SUNW_HW_1      0x40  [ MMX ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
       [2]  0x00000700 0x00000021  FUNC LOCL  D    0 .text    foo%mmx
       [8]  0x00000784 0x0000001e  FUNC LOCL  D    0 .text    bar%mmx

 Symbol Capabilities:
     index  tag               value
       [4]  CA_SUNW_ID        sse
       [5]  CA_SUNW_HW_1      0x800  [ SSE ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
       [4]  0x00000750 0x0000002f  FUNC LOCL  D    0 .text    foo%sse
       [9]  0x000007b0 0x00000030  FUNC LOCL  D    0 .text    bar%sse

Capabilities Chain Section:  .SUNW_capchain

 Capabilities family: foo
  chainndx  symndx      name
         1  [15]        foo
         2  [2]         foo%mmx
         3  [4]         foo%sse

 Capabilities family: bar
  chainndx  symndx      name
         5  [17]        bar
         6  [8]         bar%mmx
         7  [9]         bar%sse

At runtime, all references to foo() and bar() are initially bound to the global symbols. However, the runtime linker recognizes that these functions are the lead instance of a capabilities family. The runtime linker inspects each family member to determine if a better capability function is available. There is a one time cost to this operation, which occurs on the first call to the function. Subsequent calls to foo() and bar() are bound directly to the function instance selected by the first call. This function selection can be observed by using the runtime linkers debugging capabilities.

In the following example, the underlying system does not provide MMX or SSE support. The lead instance of foo() requires no special capabilities support, and thus satisfies any relocation reference.


$ LD_DEBUG=symbols main
....
debug: symbol=foo;  lookup in file=./libfoo.so.1  [ ELF ]
debug:   symbol=foo[15]:  capability family default
debug:   symbol=foo%mmx[2]:  capability specific (CA_SUNW_HW_1):  [ 0x40  [ MMX ] ]
debug:   symbol=foo%mmx[2]:  capability rejected
debug:   symbol=foo%sse[4]:  capability specific (CA_SUNW_HW_1):  [ 0x800  [ SSE ] ]
debug:   symbol=foo%sse[4]:  capability rejected
debug:   symbol=foo[15]:  used

In the following example, MMX is available, but SSE is not. The MMX capable instance of foo() satisfies any relocation reference.


$ LD_DEBUG=symbols main
....
debug: symbol=foo;  lookup in file=./libfoo.so.1  [ ELF ]
debug:   symbol=foo[15]:  capability family default
debug:   symbol=foo[2]:  capability specific (CA_SUNW_HW_1):  [ 0x40  [ MMX ] ]
debug:   symbol=foo[2]:  capability candidate
debug:   symbol=foo[4]:  capability specific (CA_SUNW_HW_1):  [ 0x800  [ SSE ] ]
debug:   symbol=foo[4]:  capability rejected
debug:   symbol=foo[2]:  used

When more than one capability instance can be exercised on the same system, a set of precedent rules are used to select one instance.

A family of capabilities function instances must be accessed from a procedure linkage table entry. See Procedure Linkage Table (Processor-Specific). This procedure linkage reference requires the runtime linker to resolve the function. During this process, the runtime linker can process the associated symbol capabilities information, and select the best function from the available family of function instances.

When symbol capabilities are not used, there are cases where the link-editor can resolve references to code without the need of a procedure linkage table entry. For example, within a dynamic executable, a reference to a function that exists within the executable can be bound internally at link-edit time. Hidden and protected functions within shared objects can also be bound internally at link-edit time. In these cases, there is normally no need for the runtime linker to be involved in resolving a reference to these functions.

However, when symbol capabilities are used, the function must be resolved from a procedure linkage table entry. This entry is necessary in order for the runtime linker to be involved in selecting the appropriate function, while maintaining a read-only text segment. This mechanism results in an indirection through a procedure linkage table entry for all calls to a capability function. This indirection might not be necessary if symbol capabilities are not used. Therefore, there is a small trade off between the cost of calling the capability function, and any performance improvement gained from using the capability function over its default counterpart.


Note –

Although a capability function must be accessed through a procedure linkage table entry, the function can still be defined as hidden or protected. The runtime linker honors these visibility states and restricts any binding to these functions. This behavior results in the same bindings as are produced when symbol capabilities are not associated with the function. A hidden function can not be bound to from an external object. A reference to a protected function from within an object will only be bound to within the same object.


Creating a Family of Symbol Capabilities Data Items

Multiple instances of initialized data, where each instance is specific to a system, can be provided within the same object. However, providing such data through functional interfaces if often simpler, and is recommended. See Creating a Family of Symbol Capabilities Functions. Special care is required to provide multiple instances of initialized data within an executable.

The following example initializes a data item foo within foo.c, to point to a machine name string. This file can be compiled for various machines, and each instance is identified with a machine capability. A reference to this data item is made from bar() from the file bar.c. A shared object foobar.so.1 is then created by combining bar() with two capabilities instances of foo.


$ cat foo.c
char *foo = MACHINE;
$ cat bar.c
#include	<stdio.h>

extern char *foo = MACHINE;

void bar()
{
        (void) printf("machine: %s\n", foo);
}

$ elfdump -H foobar.so.1

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID       sun4u
       [2]  CA_SUNW_MACH     sun4u

  Symbols:
     index    value      size      type bind oth ver shndx          name
       [1]  0x000108d4 0x00000004  OBJT LOCL  D    0 .data          foo%sun4u

 Symbol Capabilities:
     index  tag               value
       [4]  CA_SUNW_ID       sun4v
       [5]  CA_SUNW_MACH     sun4v

  Symbols:
     index    value      size      type bind oth ver shndx          name
       [2]  0x000108d8 0x00000004  OBJT LOCL  D    0 .data          foo%sun4v

An application can reference bar(), and the runtime linker binds to the instance of foo that is associated with the underlying system.


$ uname -m
sun4u
$ main
machine: sun4u

The proper operation of this code depends on the code having been compiled to be position-independent, as is normally the case for code in sharable objects. See Position-Independent Code. Position-independent data references are indirect references, which allow the runtime linker to locate the required reference and update elements of the data segment. This relocation update of the data segment preserves the text segment as read-only.

However, the code within an executable is typically position-dependent. In addition, data references within an executable are bound at link-edit time. Within an executable, a symbol capabilities data reference must remain unresolved through a global data item, so that the runtime linker can select from the symbol capabilities family. If the reference from bar() in the previous example bar.c is compiled as position-dependent code, then the text segment of the executable must be relocated at runtime. By default, this condition results in a fatal link-time error.


$ cc -o main main.c bar.c foo.o foo.1.o foo.2.o ...
warning: Text relocation remains                referenced
    against symbol                  offset      in file
foo                                 0x0         bar.o
foo                                 0x8         bar.o

One approach to solve this error condition is to compile bar.c as position-independent. Note however, that all references to any symbol capabilities data items from within the executable must be compiled position-independent for this technique to work.

Although data can be accessed using the symbol capabilities mechanism, making data items a part of the public interface to an object can be problematic. An alternative, and more flexible model, is to encapsulate each data item within a symbol capabilities function. This function provides the sole means of access to the data. Hiding data behind a symbol capabilities function has the important benefit of allowing the data to be defined static and kept private. The previous example can be coded to use symbol capabilities functions.


$ cat foobar.c
cat bar.c
#include	<stdio.h>

static char *foo = MACHINE;

void bar()
{
        (void) printf("machine: %s\n", foo);
}
$ elfdump -H main

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID       sun4u
       [2]  CA_SUNW_MACH     sun4u

  Symbols:
     index    value      size      type bind oth ver shndx          name
       [1]  0x0001111c 0x0000001c  FUNC LOCL  D    0 .text          bar%sun4u

 Symbol Capabilities:
     index  tag               value
       [4]  CA_SUNW_ID       sun4v
       [5]  CA_SUNW_MACH     sun4v

  Symbols:
     index    value      size      type bind oth ver shndx          name
       [2]  0x00011138 0x0000001c  FUNC LOCL  D    0 .text          bar%sun4v

$ uname -m
sun4u
$ main
machine: sun4u

Converting Object Capabilities to Symbol Capabilities

Ideally, the compiler can generate objects that are identified with symbol capabilities. If the compiler can not create symbol capabilities, the link-editor offers a solution.

A relocatable object that defines object capabilities can be transformed into a relocatable object that defines symbol capabilities using the link-editor. Using the link-editor -z symbolcap option, any capability data section is converted to define symbol capabilities. All global functions within the object are converted into local functions, and are associated with symbol capabilities. All global initialized data items are converted to local data items, and are associated with symbol capabilities. These transformed symbols are appended with any capability identifier specified as part of the object capabilities group. If a capability identifier is not defined, a default group name is appended.

For each original global function or initialized data item, a global reference is created. This reference is associated to any relocation requirements, and provides for binding to a default, global symbol when this object is finally combined to create a dynamic executable or shared object.


Note –

The -z symbolcap option applies to objects that contain an object capabilities section. The option has no affect upon relocatable objects that already contain symbol capabilities, or relocatable objects that contain both object and symbol capabilities. This design allows multiple objects to be combined by the link-editor, with only those objects that contain object capabilities being affected by the option.


In the following example, a x86 relocatable object contains two global functions foo() and bar(). This object has been compiled to require the MMX and SSE hardware capabilities. In these examples, the capabilities group has been named with a capabilities identifier entry. This identifier name is appended to the transformed symbol names. Without this explicit identifier, the link-editor appends a default capabilities group name.


$ elfdump -H foo.o

Capabilities Section:  .SUNW_cap

 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_ID        sse,mmx
       [1]  CA_SUNW_HW_1      0x840  [ SSE MMX ]

$ elfdump -s foo.o | egrep "foo|bar"
      [25]  0x00000000 0x00000021  FUNC GLOB  D    0 .text    foo
      [26]  0x00000024 0x0000001e  FUNC GLOB  D    0 .text    bar

$ elfdump -r foo.o | fgrep foo
  R_386_PLT32                0x38       .rel.text             foo

This relocatable object can now be transformed into a symbols capabilities relocatable object.


$ ld -r -o foo.1.o -z symbolcap foo.o
$ elfdump -H foo.1.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
     index  tag               value
       [1]  CA_SUNW_ID        sse,mmx
       [2]  CA_SUNW_HW_1      0x840  [ SSE MMX ]

  Symbols:
     index    value      size      type bind oth ver shndx    name
      [25]  0x00000000 0x00000021  FUNC LOCL  D    0 .text    foo%sse,mmx
      [26]  0x00000024 0x0000001e  FUNC LOCL  D    0 .text    bar%sse,mmx

$ elfdump -s foo.1.o | egrep "foo|bar"
      [25]  0x00000000 0x00000021  FUNC LOCL  D    0 .text    foo%sse,mmx
      [26]  0x00000024 0x0000001e  FUNC LOCL  D    0 .text    bar%sse,mmx
      [37]  0x00000000 0x00000000  FUNC GLOB  D    0 UNDEF    foo
      [38]  0x00000000 0x00000000  FUNC GLOB  D    0 UNDEF    bar

$ elfdump -r foo.1.o | fgrep foo
  R_386_PLT32                0x38       .rel.text             foo

This object can now be combined with other objects containing instances of the same functions, associated with different symbol capabilities, to produce an executable or shared object. In addition, a default instance of each function, one that is not associated with any symbol capabilities, should be provided to lead each capabilities family. This default instance provides for all external references, and ensures that an instance of the function is available on any system.

At runtime, any references to foo() and bar() are directed to the lead instances. However, the runtime linker selects the best symbol capabilities instance if the system accommodates the appropriate capabilities.

Exercising a Capability Family

Objects are normally designed and built so that they can execute on all systems of a given architecture. However, individual systems, with special capabilities, are often targeted for optimization. Optimized code can be identified with the capabilities that the code requires to execute, using the mechanisms described in the previous sections.

To exercise and test optimized instances it is necessary to use a system that provides the required capabilities. For each system, the runtime linker determines the capabilities that are available, and then chooses the most capable instances. To aid testing and experimentation, the runtime linker can be told to use an alternative set of capabilities than those provided by the system. In addition, you can specify that only specific files should be validated against these alternative capabilities.

An alternative set of capabilities is derived from the system capabilities, and can be re-initialized or have capabilities added or removed.

A family of environment variables is available to create and target the use of an alternative set of capabilities.

LD_PLATCAP={name}

Identifies an alternative platform name.

LD_MACHCAP={name}

Identifies an alternative machine hardware name.

LD_HWCAP=[+-]{token | number},....

Identifies an alternative hardware capabilities value.

LD_SFCAP=[+-]{token | number},....

Identifies an alternative software capabilities value.

LD_CAP_FILES=file,....

Identifies the files that should be validated against the alternative capabilities.

The capabilities environment variables LD_PLATCAP and LD_MACHCAP accept a string that defines the platform name and machine hardware names respectively. See Identifying a Platform Capability, and Identifying a Machine Capability.

The capabilities environment variables LD_HWCAP and LD_SFCAP accept a comma separated list of tokens as a symbolic representation of capabilities. See Identifying Hardware Capabilities, and Identifying Software Capabilities. A token can also be a numeric value.

A “+” prefix results in the capabilities that follow being added to the alternative capabilities. A “-” prefix results in the capabilities that follow being removed from the alternative capabilities. The lack of “+-” result in the capabilities that follow replacing the alternative capabilities.

The removal of a capability results in a more restricted capabilities environment being emulated. Normally, when a family of capabilities instances is available, a generic, non-capabilities specific instance is also provided. A more restricted capabilities environment can therefore be used to force the use of less capable, or generic code instances.

The addition of a capability results in a more enhanced capabilities environment being emulated. This environment should be created with caution, but can be used to exercise the framework of a capabilities family. For example, a family of functions can be created that define their expected capabilities using mapfiles. These functions can use printf(3C) to confirm their execution. The creation of the associated objects can then be validated and exercised with various capability combinations. This prototyping of a capabilities family can prove useful before the real capabilities requirements of the functions are coded. However, if the code within a family instance requires a specific capability to execute correctly, and this capability is not provided by the system, but is set as an alternative capability, the code instance will fail to execute correctly.

Establishing a set of alternative capabilities without also using LD_CAP_FILES results in all of the capabilities specific objects of a process being validated against the alternative capabilities. This approach should also be exercised with caution, as many system objects require system capabilities to execute correctly. Any alteration of capabilities can cause system objects to fail to execute correctly.

A best environment for capabilities experimentation is to use a system that provides all the capabilities your objects are targeted to use. LD_CAP_FILES should also be used to isolate the objects you wish to experiment with. Capabilities can then be disabled, using the “-” syntax, so that the various instances of your capabilities family can be exercised. Each instance is fully supported by the true capabilities of the system.

For example, suppose you have two x86 capabilities objects, libfoo.so and libbar.so. These objects contain capability functions optimized to use SSE2 instructions, functions optimized to use MMX instructions, and generic functions that require no capabilities. The underlying system provides both SSE2 and MMX. By default, the fully optimized SSE2 functions are used.

libfoo.so and libbar.so can be restricted to use the functions optimized for MMX instructions by removing the SSE2 capability by using a LD_HWCAP definition. The most flexible means of defining LD_CAP_FILES is to use the base name of the required files.


$ LD_HWCAP=-sse2 LD_CAP_FILES=libfoo.so,libbar.so ./main

libfoo.so and libbar.so can be further restricted to use only generic functions by removing the SSE2 and MMX capabilities.


$ LD_HWCAP=-sse2,mmx LD_CAP_FILES=libfoo.so,libbar.so ./main

Note –

The capabilities available for an application, and any alternative capabilities that have been set, can be observed using the runtime linkers diagnostics.


$ LD_DEBUG=basic LD_HWCAP=-sse2,mmx,cx8 ./main
....
02328: hardware capabilities (CA_SUNW_HW_1) - 0x5c6f  \
    [ SSE3 SSE2 SSE FXSR MMX CMOV SEP CX8 TSC FPU ]
02328: alternative hardware capabilities (CA_SUNW_HW_1) - 0x4c2b  \
    [ SSE3 SSE FXSR CMOV SEP TSC FPU ]
....

Relocation Processing

After you have created the output file, all data sections from the input files are copied to the new image. Any relocations specified by the input files are applied to the output image. Any additional relocation information that must be generated is also written to the new image.

Relocation processing is normally uneventful, although error conditions might arise that are accompanied by specific error messages. Two conditions are worth more discussion. The first condition involves text relocations that result from position-dependent code. This condition is covered in more detail in Position-Independent Code. The second condition can arise from displacement relocations, which is described more fully in the next section.

Displacement Relocations

Error conditions might occur if displacement relocations are applied to a data item, which can be used in a copy relocation. The details of copy relocations are covered in Copy Relocations.

A displacement relocation remains valid when both the relocated offset and the relocation target remain separated by the same displacement. A copy relocation is where a global data item within a shared object is copied to the .bss of an executable. This copy preserves the executable's read-only text segment. If the copied data has a displacement relocation applied to the data, or an external relocation is a displacement into the copied data, the displacement relocation becomes invalidated.

Two areas of validation attempt to catch displacement relocation problems.

To help diagnose these problem areas, the link-editor indicates the displacement relocation use of a dynamic object with one or more dynamic DT_FLAGS_1 flags, as shown in Table 7–34. In addition, the link-editor's -z verbose option can be used to display suspicious relocations.

For example, say you create a shared object with a global data item, bar[], to which a displacement relocation is applied. This item could be copy-relocated if referenced from a dynamic executable. The link-editor warns of this condition.


$ cc -G -o libfoo.so.1 -z verbose -K pic foo.o
ld: warning: relocation warning: R_SPARC_DISP32: file foo.o: symbol foo: \
    displacement relocation to be applied to the symbol bar: at 0x194: \
    displacement relocation will be visible in output image

If you now create an application that references the data item bar[], a copy relocation is created. This copy results in the displacement relocation being invalidated. Because the link-editor can explicitly discover this situation, an error message is generated regardless of the use of the -z verbose option.


$ cc -o prog prog.o -L. -lfoo
ld: warning: relocation error: R_SPARC_DISP32: file foo.so: symbol foo: \
    displacement relocation applied to the symbol bar at: 0x194: \
    the symbol bar is a copy relocated symbol

Note –

ldd(1), when used with either the -d or -r options, uses the displacement dynamic flags to generate similar relocation warnings.


These error conditions can be avoided by ensuring that the symbol definition being relocated (offset) and the symbol target of the relocation are both local. Use static definitions or the link-editor's scoping technology. See Reducing Symbol Scope. Relocation problems of this type can be avoided by accessing data within shared objects by using functional interfaces.

Debugging Aids

The link-editor provides a debugging facility that allows you to trace the link-editing process in detail. This facility can help you understand and debug the link-edit of your applications and libraries. The type of information that is displayed by using this facility is expected to remain constant. However, the exact format of the information might change slightly from release to release.

Some of the debugging output might be unfamiliar if you do not have an intimate knowledge of the ELF format. However, many aspects might be of general interest to you.

Debugging is enabled by using the -D option. This option must be augmented with one or more tokens to indicate the type of debugging that is required.

The tokens that are available with -D can be displayed by typing -D help at the command line.


$ ld -Dhelp

By default, all debug output is sent to stderr, the standard error output file. Debug output can be directed to a file instead, using the output token. For example, the help text can be captured in a file named ld-debug.txt.


$ ld -Dhelp,output=ld-debug.txt

Most compiler drivers assign the -D option a different meaning, often to define preprocessing macros. The LD_OPTIONS environment variable can be used to bypass the compiler driver, and supply the -D option directly to the link-editor.

The following example shows how input files can be traced. This syntax can be useful to determine what libraries are used as part of a link-edit. Objects that are extracted from an archive are also displayed with this syntax.


$ LD_OPTIONS=-Dfiles cc -o prog main.o -L. -lfoo
....
debug: file=main.o  [ ET_REL ]
debug: file=./libfoo.a  [ archive ]
debug: file=./libfoo.a(foo.o)  [ ET_REL ]
debug: file=./libfoo.a  [ archive ] (again)
....

Here, the member foo.o is extracted from the archive library libfoo.a to satisfy the link-edit of prog. Notice that the archive is searched twice to verify that the extraction of foo.o did not warrant the extraction of additional relocatable objects. Multiple “(again)” diagnostics indicates that the archive is a candidate for ordering using lorder(1) and tsort(1).

By using the symbols token, you can determine which symbol caused an archive member to be extracted, and which object made the initial symbol reference.


$ LD_OPTIONS=-Dsymbols cc -o prog main.o -L. -lfoo
....
debug: symbol table processing; input file=main.o  [ ET_REL ]
....
debug: symbol[7]=foo  (global); adding
debug:
debug: symbol table processing; input file=./libfoo.a  [ archive ]
debug: archive[0]=bar
debug: archive[1]=foo  (foo.o) resolves undefined or tentative symbol
debug:
debug: symbol table processing; input file=./libfoo(foo.o)  [ ET_REL ]
....

The symbol foo is referenced by main.o. This symbol is added to the link-editor's internal symbol table. This symbol reference causes the extraction of the relocatable object foo.o from the archive libfoo.a.


Note –

This output has been simplified for this document.


By using the detail token together with the symbols token, the details of symbol resolution during input file processing can be observed.


$ LD_OPTIONS=-Dsymbols,detail cc -o prog main.o -L. -lfoo
....
debug: symbol table processing; input file=main.o  [ ET_REL ]
....
debug: symbol[7]=foo  (global); adding
debug:   entered  0x000000 0x000000 NOTY GLOB  UNDEF REF_REL_NEED
debug:
debug: symbol table processing; input file=./libfoo.a  [ archive ]
debug: archive[0]=bar
debug: archive[1]=foo  (foo.o) resolves undefined or tentative symbol
debug:
debug: symbol table processing; input file=./libfoo.a(foo.o)  [ ET_REL ]
debug: symbol[1]=foo.c
....
debug: symbol[7]=bar  (global); adding
debug:   entered  0x000000 0x000004 OBJT GLOB  3     REF_REL_NEED
debug: symbol[8]=foo  (global); resolving [7][0]
debug:       old  0x000000 0x000000 NOTY GLOB  UNDEF main.o
debug:       new  0x000000 0x000024 FUNC GLOB  2     ./libfoo.a(foo.o)
debug:  resolved  0x000000 0x000024 FUNC GLOB  2     REF_REL_NEED
....

The original undefined symbol foo from main.o has been overridden with the symbol definition from the extracted archive member foo.o. The detailed symbol information reflects the attributes of each symbol.

In the previous example, you can see that using some of the debugging tokens can produce a wealth of output. To monitor the activity around a subset of the input files, place the -D option directly in the link-edit command line. This option can be toggled on and toggled off. In the following example, the display of symbol processing is switched on only during the processing of the library libbar.


$ ld .... -o prog main.o -L. -Dsymbols -lbar -D!symbols ....

Note –

To obtain the link-edit command line, you might have to expand the compilation line from any driver being used. See Using a Compiler Driver.