Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
5.  Application Binary Interfaces and Versioning Internal Versioning Creating a Version Definition Defining Unrelated Interfaces  Previous   Contents   Next 
   
 

Binding to a Version Definition

When a dynamic executable or shared object is built against other shared objects, these dependencies are recorded in the resulting object. See "Shared Object Processing" and "Recording a Shared Object Name" for more details. If these shared object dependencies also contain version definitions, then an associated version dependency will be recorded in the object being built.

The following example takes the data files from the previous section and generates a shared object suitable for a compile time environment. This shared object, libfoo.so.1, will be used in the succeeding binding examples.

$ cc -o libfoo.so.1 -h libfoo.so.1 -M mapfile -G foo.o bar.o \
data.o
$ ln -s libfoo.so.1 libfoo.so
$ pvs -dsv libfoo.so.1
        libfoo.so.1:
                _end;
                _GLOBAL_OFFSET_TABLE_;
                _DYNAMIC;
                _edata;
                _PROCEDURE_LINKAGE_TABLE_;
                _etext;
        SUNW_1.1:
                foo1;
                SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1}:
                foo2;
                SUNW_1.2;
        SUNW_1.2.1 [WEAK]:       {SUNW_1.2}:
                SUNW_1.2.1;
        SUNW_1.3a:               {SUNW_1.2}:
                bar1;
                SUNW_1.3a;
        SUNW_1.3b:               {SUNW_1.2}:
                bar2;
                SUNW_1.3b

In effect, there are six public interfaces being offered by this shared object. Four of these interfaces (SUNW_1.1, SUNW_1.2, SUNW_1.3a, and SUNW_1.3b) define exported symbol names. One interface (SUNW_1.2.1) describes an internal implementation change to the shared object, and one interface (libfoo.so.1) defines several reserved labels. Dynamic objects that are created with this object as a dependency will record the version names of these interfaces they bind to.

The following example creates an application that references symbols foo1 and foo2. The versioning dependency information recorded in the application can be examined using pvs(1) with the -r option.

$ cat prog.c
extern void foo1();
extern void foo2();

main()
{
        foo1();
        foo2();
}
$ cc -o prog prog.c -L. -R. -lfoo
$ pvs -r prog
        libfoo.so.1 (SUNW_1.2, SUNW_1.2.1);

In this example, the application prog has bound to the two interfaces SUNW_1.1 and SUNW_1.2. These interfaces provided the global symbols foo1 and foo2 respectively.

Because version definition SUNW_1.1 is defined within libfoo.so.1 as being inherited by the version definition SUNW_1.2, you also need to record the latter version dependency. This normalization of version definition dependencies reduces the amount of version information maintained within an object, and reduces the processing required at runtime.

Because the application prog was built against the shared object's implementation containing the weak version definition SUNW_1.2.1, this dependency is also recorded. Even though this version definition is defined to inherit the version definition SUNW_1.2, the version's weak nature precludes its normalization with SUNW_1.1, and results in a separate dependency recording.

Had there been multiple weak version definitions that inherited from each other, then these definitions will be normalized in the same manner as non-weak version definitions are.


Note - The recording of a version dependency can be suppressed by the link-editor's -z noversion option.


Having recorded these version definition dependencies, the runtime linker validates the existence of the required version definitions in the objects that are bound to when the application is executed. This validation can be displayed using ldd(1) with the -v option. For example, by running ldd(1) on the application prog, the version definition dependencies are shown to be found correctly in the shared object libfoo.so.1:

$ ldd -v prog

   find object=libfoo.so.1; required by prog
        libfoo.so.1 =>   ./libfoo.so.1
   find version=libfoo.so.1;
        libfoo.so.1 (SUNW_1.2) =>            ./libfoo.so.1
        libfoo.so.1 (SUNW_1.2.1) =>          ./libfoo.so.1
   ....

Note - ldd(1) with the -v option implies verbose output. A recursive list of all dependencies, together with all versioning requirements, is generated.


If a non-weak version definition dependency cannot be found, a fatal error occurs during application initialization. Any weak version definition dependency that cannot be found is silently ignored. For example, if the application prog is run in an environment in which libfoo.so.1 only contains the version definition SUNW_1.1, then the following fatal error occurs:

$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
$ prog
ld.so.1: prog: fatal: libfoo.so.1: version `SUNW_1.2' not \
found (required by file prog)

Had the application prog not recorded any version definition dependencies, the nonexistence of the required interface symbol foo2 would have manifested itself some time during the execution of the application as a fatal relocation error. This relocation error might occur at process initialization, during process execution, or might not occur at all if the execution path of the application did not call the function foo2. See "Relocation Errors".

Recording version definition dependencies provides an alternative and immediate indication of the availability of the interfaces required by the application.

If the application prog is run in an environment in which libfoo.so.1 only contains the version definitions SUNW_1.1 and SUNW_1.2, then all non-weak version definition requirements will be satisfied. The absence of the weak version definition SUNW_1.2.1 is deemed nonfatal, and so no runtime error condition will be generated. However, ldd(1) can be used to display all version definitions that cannot be found:

$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1};
$ prog
string used by foo1()
string used by foo2()
$ ldd prog
        libfoo.so.1 =>   ./libfoo.so.1
        libfoo.so.1 (SUNW_1.2.1) =>          (version not found)
        ...........

Note - If an object requires a version definition from a given dependency, and at runtime an implementation of that dependency is found that contains no version definition information, the version verification of the dependency will be silently ignored. This policy provides a level of backward compatibility as a transition from non-versioned to versioned shared objects occurs. ldd(1), however, can still be used to display any version requirement discrepancies.


Verifying Versions in Additional Objects

Version definition symbols also provide a mechanism for verifying the version requirements of an object obtained by dlopen(3DL). Any object added to the process's address space using this function will have no automatic version dependency verification carried out by the runtime linker. Thus, the caller of this function is responsible for verifying that any versioning requirements are met.

The presence of a required version definition can be verified by looking up the associated version definition symbol using dlsym(3DL). The following example shows the shared object libfoo.so.1 being added to a process by dlopen(3DL) and verified to ensure that the interface SUNW_1.2 is available.

#include        <stdio.h>
#include        <dlfcn.h>
 
main()
{
        void *       handle;
        const char * file = "libfoo.so.1";
        const char * vers = "SUNW_1.2";
        ....
 
        if ((handle = dlopen(file, RTLD_LAZY)) == NULL) {
                (void) printf("dlopen: %s\n", dlerror());
                exit (1);
        }
 
        if (dlsym(handle, vers) == NULL) {
                (void) printf("fatal: %s: version `%s' not found\n",
                    file, vers);
                exit (1);
        }
        ....
 
 
 
  Previous   Contents   Next