The linker supports symbol versions when using ELF. Symbol versions are only useful when using shared libraries. The dynamic linker can use symbol versions to select a specific version of a function when it runs a program that may have been linked against an earlier version of the shared library.
You can include a version script directly in the main linker script, or you can supply the version script as an implicit linker script. You can also use the --version-script linker option.
The syntax of the VERSION
command is simply
VERSION { version-script-commands }
The format of the version script commands is identical to that used by Sun's linker in Solaris 2.5. The version script defines a tree of version nodes. You specify the node names and interdependencies in the version script. You can specify which symbols are bound to which version nodes, and you can reduce a specified set of symbols to local scope so that they are not globally visible outside of the shared library.
The easiest way to demonstrate the version script language is with a few examples.
VERS_1.1 { global: foo1; local: old*; original*; new*; }; VERS_1.2 { foo2; } VERS_1.1; VERS_2.0 { bar1; bar2; extern "C++" { ns::*; "int f(int, double)"; } } VERS_1.2;
This example version script defines three version nodes. The first version node defined is VERS_1.1; it has no other dependencies. The script binds the symbol foo1 to VERS_1.1. It reduces a number of symbols to local scope so that they are not visible outside of the shared library; this is done using wildcard patterns, so that any symbol whose name begins with old, original, or new is matched. The wildcard patterns available are the same as those used in the shell when matching filenames (also known as “globbing”). However, if you specify the symbol name inside double quotes, then the name is treated as literal, rather than as a glob pattern.
Next, the version script defines node VERS_1.2. This node depends upon VERS_1.1. The script binds the symbol foo2 to the version node VERS_1.2.
Finally, the version script defines node VERS_2.0. This node depends upon VERS_1.2. The scripts binds the symbols bar1 and bar2 are bound to the version node VERS_2.0.
When the linker finds a symbol defined in a library which is not specifically bound to a version node, it will effectively bind it to an unspecified base version of the library. You can bind all otherwise unspecified symbols to a given version node by using global: *; somewhere in the version script.
The names of the version nodes have no specific meaning other than what they might suggest to the person reading them. The 2.0 version could just as well have appeared in between 1.1 and 1.2. However, this would be a confusing way to write a version script.
Node name can be omitted, provided it is the only version node in the version script. Such version script doesn't assign any versions to symbols, only selects which symbols will be globally visible out and which won't.
{ global: foo; bar; local: *; };
When you link an application against a shared library that has versioned symbols, the application itself knows which version of each symbol it requires, and it also knows which version nodes it needs from each shared library it is linked against. Thus at runtime, the dynamic loader can make a quick check to make sure that the libraries you have linked against do in fact supply all of the version nodes that the application will need to resolve all of the dynamic symbols. In this way it is possible for the dynamic linker to know with certainty that all external symbols that it needs will be resolvable without having to search for each symbol reference.
The symbol versioning is in effect a much more sophisticated way of doing minor version checking that SunOS does. The fundamental problem that is being addressed here is that typically references to external functions are bound on an as-needed basis, and are not all bound when the application starts up. If a shared library is out of date, a required interface may be missing; when the application tries to use that interface, it may suddenly and unexpectedly fail. With symbol versioning, the user will get a warning when they start their program if the libraries being used with the application are too old.
There are several GNU extensions to Sun's versioning approach. The first of these is the ability to bind a symbol to a version node in the source file where the symbol is defined instead of in the versioning script. This was done mainly to reduce the burden on the library maintainer. You can do this by putting something like:
__asm__(".symver original_foo,foo@VERS_1.1");
in the C source file. This renames the function original_foo to be an alias for foo bound to the version node VERS_1.1. The local: directive can be used to prevent the symbol original_foo from being exported. A .symver directive takes precedence over a version script.
The second GNU extension is to allow multiple versions of the same function to appear in a given shared library. In this way you can make an incompatible change to an interface without increasing the major version number of the shared library, while still allowing applications linked against the old interface to continue to function.
To do this, you must use multiple .symver directives in the source file. Here is an example:
__asm__(".symver original_foo,foo@"); __asm__(".symver old_foo,foo@VERS_1.1"); __asm__(".symver old_foo1,foo@VERS_1.2"); __asm__(".symver new_foo,foo@@VERS_2.0");
In this example, foo@ represents the symbol foo bound to the unspecified base version of the symbol. The source file that contains this example would define 4 C functions: original_foo, old_foo, old_foo1, and new_foo.
When you have multiple definitions of a given symbol, there needs to be some way to specify a default version to which external references to this symbol will be bound. You can do this with the foo@@VERS_2.0 type of .symver directive. You can only declare one version of a symbol as the default in this manner; otherwise you would effectively have multiple definitions of the same symbol.
If you wish to bind a reference to a specific version of the symbol within the shared library, you can use the aliases of convenience (i.e., old_foo), or you can use the .symver directive to specifically bind to an external version of the function in question.
You can also specify the language in the version script:
VERSION extern "lang" { version-script-commands }
The supported langs are C, C++, and Java. The linker will iterate over the list of symbols at the link time and demangle them according to lang before matching them to the patterns specified in version-script-commands.
Demangled names may contains spaces and other special characters. As described above, you can use a glob pattern to match demangled names, or you can use a double-quoted string to match the string exactly. In the latter case, be aware that minor differences (such as differing whitespace) between the version script and the demangler output will cause a mismatch. As the exact string generated by the demangler might change in the future, even if the mangled name does not, you should check that all of your version directives are behaving as you expect when you upgrade.