Linker and Libraries Guide

Creating a Version Definition

Version definitions commonly consist of an association of symbol names to a unique version name. These associations are established within a mapfile and supplied to the final link-edit of an object using the link-editor's -M option. This technique is introduced in the section Reducing Symbol Scope.

A version definition is established whenever a version name is specified as part of the mapfile directive. In the following example, two source files are combined, together with mapfile directives, to produce an object with a defined public interface.


$ cat foo.c
#include <stdio.h>
extern  const char *_foo1;

void foo1()
{
        (void) printf(_foo1);
}

$ cat data.c
const char *_foo1 = "string used by foo1()\n";

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION SUNW_1.1 {                  # Release X
        global:
                foo1;
        local:
                *;
};
$ cc -c -Kpic foo.c data.c
$ cc -o libfoo.so.1 -M mapfile -G foo.o data.o
$ elfdump -sN.symtab libfoo.so.1 | grep 'foo.$'
      [32]  0x0001074c 0x00000004  OBJT LOCL  H    0 .data          _foo1
      [53]  0x00000560 0x00000038  FUNC GLOB  D    0 .text          foo1

The symbol foo1 is the only global symbol that is defined to provide the shared object's public interface. The special auto-reduction directive “*” causes the reduction of all other global symbols to have local binding within the object being generated. The auto-reduction directive is described in SYMBOL_SCOPE / SYMBOL_VERSION Directives. The associated version name, SUNW_1.1, causes the generation of a version definition. Thus, the shared object's public interface consists of the global symbol foo1 associated to the internal version definition SUNW_1.1.

Whenever a version definition, or the auto-reduction directive, are used to generate an object, a base version definition is also created. This base version is defined using the name of the object being built. This base version is used to associate any reserved symbols generated by the link-editor. See Generating the Output File for a list of reserved symbols.

The version definitions that are contained within an object can be displayed using pvs(1) with the -d option.


$ pvs -d libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;

The object libfoo.so.1 has an internal version definition named SUNW_1.1, together with a base version definition libfoo.so.1.


Note –

The link-editor's -z noversion option allows symbol reduction to be directed by a mapfile but suppresses the creation of version definitions.


From this initial version definition, the object can evolve by adding new interfaces together with updated functionality. For example, a new function, foo2, together with its supporting data structures, can be added to the object by updating the source files foo.c and data.c.


$ cat foo.c
#include <stdio.h>
extern  const char *_foo1;
extern  const char *_foo2;

void foo1()
{
        (void) printf(_foo1);
}

void foo2()
{
        (void) printf(_foo2);
}

$ cat data.c
const char *_foo1 = "string used by foo1()\n";
const char *_foo2 = "string used by foo2()\n";

A new version definition, SUNW_1.2, can be created to define a new interface representing the symbol foo2. In addition, this new interface can be defined to inherit the original version definition SUNW_1.1.

The creation of this new interface is important, as the interface describes the evolution of the object. These interfaces enable users to verify and select the interfaces to bind with. These concepts are covered in more detail in Binding to a Version Definition and in Specifying a Version Binding.

The following example shows the mapfile directives that create these two interfaces.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION SUNW_1.1 {                   # Release X
        global:
                foo1;
        local:
                *;
};

SYMBOL_VERSION SUNW_1.2 {                   # Release X+1
        global:
                foo2;
} SUNW_1.1;

$ cc -o libfoo.so.1 -M mapfile -G foo.o data.o
$ elfdump -sN.symtab libfoo.so.1 | grep 'foo.$'
      [28]  0x000107a4 0x00000004  OBJT LOCL  H    0 .data          _foo1
      [29]  0x000107a8 0x00000004  OBJT LOCL  H    0 .data          _foo2
      [48]  0x000005e8 0x00000020  FUNC GLOB  D    0 .text          foo1
      [51]  0x00000618 0x00000020  FUNC GLOB  D    0 .text          foo2

The symbols foo1 and foo2 are both defined to be part of the shared object's public interface. However, each of these symbols is assigned to a different version definition. foo1 is assigned to version SUNW_1.1. foo2 is assigned to version SUNW_1.2.

These version definitions, their inheritance, and their symbol association can be displayed using pvs(1) together with the -d, -v and -s options.


$ 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

The version definition SUNW_1.2 has a dependency on the version definition SUNW_1.1.

The inheritance of one version definition by another version definition is a useful technique. This inheritance reduces the version information that is eventually recorded by any object that binds to a version dependency. Version inheritance is covered in more detail in the section Binding to a Version Definition.

A version definition symbol is created and associated with a version definition. As shown in the previous pvs(1) example, these symbols are displayed when using the -v option.

Creating a Weak Version Definition

Internal changes to an object that do not require the introduction of a new interface definition can be defined by creating a weak version definition. Examples of such changes are bug fixes or performance improvements. Such a version definition is empty. The version definition has no global interface symbols associated with the definition.

For example, suppose the data file data.c, used in the previous examples, is updated to provide more detailed string definitions.


$ cat data.c
const char *_foo1 = "string used by function foo1()\n";
const char *_foo2 = "string used by function foo2()\n";

A weak version definition can be introduced to identify this change.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION SUNW_1.1 {                   # Release X
        global:
                foo1;
        local:
                *;
};
  
SYMBOL_VERSION SUNW_1.2 {                   # Release X+1
        global:
                foo2;
} SUNW_1.1;

 
SYMBOL_VERSION SUNW_1.2.1 { } SUNW_1.2;     # Release X+2
 
$ cc -o libfoo.so.1 -M mapfile -G foo.o data.o
$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1};
        SUNW_1.2.1 [WEAK]:       {SUNW_1.2};

The empty version definition is signified by the weak label. These weak version definitions enable applications to verify the existence of a particular implementation detail. An application can bind to the version definition that is associated with an implementation detail that the application requires. The section Binding to a Version Definition illustrates how these definitions can be used in more detail.

Defining Unrelated Interfaces

The previous examples show how new version definitions added to an object inherit any existing version definitions. You can also create version definitions that are unique and independent. In the following example, two new files, bar1.c and bar2.c, are added to the object libfoo.so.1. These files contribute two new symbols, bar1 and bar2, respectively.


$ cat bar1.c
extern  void foo1();

void bar1()
{
        foo1();
}
$ cat bar2.c
extern  void foo2();

void bar2()
{
        foo2();
}

These two symbols are intended to define two new public interfaces. Neither of these new interfaces are related to each other. However, each interface expresses a dependency on the original SUNW_1.2 interface.

The following mapfile definition creates the required association.


$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION SUNW_1.1 {                   # Release X
        global:
                foo1;
        local:
                *;
};
 
SYMBOL_VERSION SUNW_1.2 {                   # Release X+1
        global:
                foo2;
} SUNW_1.1;
 
SYMBOL_VERSION SUNW_1.2.1 { } SUNW_1.2;     # Release X+2
 
SYMBOL_VERSION SUNW_1.3a {                  # Release X+3
        global:
                bar1;
} SUNW_1.2;
 
SYMBOL_VERSION SUNW_1.3b {                  # Release X+3
        global:
                bar2;
} SUNW_1.2;

The version definitions created in libfoo.so.1 when using this mapfile, and their related dependencies, can be inspected using pvs(1).


$ cc -o libfoo.so.1 -M mapfile -G foo.o bar1.o bar2.o data.o
$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1};
        SUNW_1.2.1 [WEAK]:       {SUNW_1.2};
        SUNW_1.3a:               {SUNW_1.2};
        SUNW_1.3b:               {SUNW_1.2};

Version definitions can be used to verify runtime binding requirements. Version definitions can also be used to control the binding of an object during the objects creation. The following sections explore these version definition usages in more detail.