Combining versions
Sun introduced a symbol versioning scheme to use for the linker. Their implementation is relatively simple: symbol versions are defined in a version script provided when a shared library was created. The dynamic linker can verify that all required versions are present. This is useful for ensuring that an application can run with a specific version of the library.
In the Sun versioning scheme, when a symbol is changed to have an incompatible
interface, the library file name must change. This then produces a new
DT_SONAME
entry, which leads to new DT_NEEDED
entries, and thus manages
incompatibility at that level.
Ulrich Drepper and Eric Youngdale introduced a much more sophisticated symbol
versioning scheme, which is used by the glibc, the GNU linker, and gold. The
key differences are that versions may be specified in object files and that
shared libraries may contain multiple independent versions of the same symbol.
Versions are specified in object files by naming the symbol NAME@VERSION
or
NAME@@VERSION
. In the former case the symbol is a hidden version, available
only by specific request. In the latter case the symbol is a default version,
and references to NAME
will be linked to NAME@@VERSION
. Versions may also
be specified in version scripts.
This facility means that in principle it is never necessary to change the library file name. The versioning scheme lets the dynamic linker direct each symbol reference to the appropriate version. This in turn means that in a complicated program with many shared libraries compiled against different versions of the base library, only one instance of the base library needs to be loaded.
However, this additional complexity leads to additional ambiguity. There are
now two possible sources of a symbol version: the name in the object file and
an entry in the version script. There is the possibility that two instances of
the same name will disagree on whether the name should be globally visible or
not–in fact, this is normal, as undefined references will always use
NAME@VERSION
, not NAME@@VERSION
. Symbol overriding can be confusing: if the
main executable defines NAME
without a version, which versions should it
override in the shared library? Which version should be used in the program?
Symbol visibility adds an additional wrinkle to this.
The most important issue for the linker arises when it sees both NAME and
NAME@VERSION
, and then sees NAME@@VERSION
. At that time the linker has seen
two separate symbols and has to decide whether to merge them. The rules that
gold currently follows are these:
- If
NAME
is hidden, andNAME@@VERSION
is in a shared object, they are two independent symbols, and we do not changeNAME
or its version. - If
NAME
already has a version, because we earlier sawNAME@@VERSION2
, then we produce two separate symbols, and leaveNAME@@VERSION2
as the default symbol. - Otherwise, we change the version of
NAME
toVERSION
, and do normal symbol resolution.
I recently fixed a bug in this code in gold, which was breaking symbol overriding in a specific case. I wouldn’t be surprised if there are more bugs. As far as I know nobody has worked through all the symbol combining issues and defined what should happen.