Picking up where I left with my post about ABI I’d like to provide some insights about the “soversion”, or shared object version, that is part of the “soname” (the canonical name of a shared object/library), and its relationship with the
-version-info option in libtool.
First of all, the “soname” is the string listed in the
DT_SONAME tag of the .dynamic section of an ELF shared object. It represents the canonical name the library should be called with, and it’s used to create the
DT_NEEDED entries for the shared objects and dynamic executables depending on it, as well as the canonical name used when opening the library through
dlopen() (without the full path).
Usually, the soname is composed of the library’s basename (
libfoo.so) followed by a reduced shared object version, but the extent to which is reduced (or not) depends on the standard rules for the operating system and a few other notes. What I’m going to talk about today is that last part, the shared object version, which is probably the most important part of the soname.
First of all, the “soversion” does not correspond to either the package version nor the
-version-info parameter (although it is calculated starting from that one); using either directly would be a big mistake, unless you expect to be able to keep a perfect ABI based on your package versioning, in which case you might want to try using the package version, but that’s quite a difficult thing to do.
The part of this version that is embedded in the soname is the version of the ABI, and has to change when the ABI is changed following the rules I shown previously. If this was kept the same between versions and the ABI was broken, software would be going to subtly crash because of the changes in the ABI. By changing the ABI version, and thus the soname, you make the loader refuse to start the program with a different library than it was developed for; of course it does not make the software magically work, but it will at least stop it from crashing further on along the road.
By default on Linux and Solaris, there is a single component used for the soname, as ABI version, at least with
libtool, projects following, manually, this rule, and setting their soversion the same as their package version would be providing a single ABI for each major version of their software; I rarely have seen anything like that working out good. Ruby uses a mix of this, by defining two components as the soversion, so that eventually you could have
libruby.so.1.9 (on the other hand, we rename them to
libruby19 so that they don’t collide for other reasons, but that’s beside the point). This works as long as they don’t have to change, for any reason, the ABI of a minor release of Ruby; when that happens, something will certainly break.
-version-info of libtool is explicitly distinct from the package version, as well as the actual soversion, and is used to provide a consistent library versioning among releases, by providing three components: current, age and revision; they represent the information in form of API/ABI supported and dropped; understanding the separation is quite a time waste but it can be summarised in three simple steps:
- if you don’t change the interface at all just increase the “interface revision” value;
- if you make backward-compatible changes (like adding interfaces), increase the “current interface” value and the “older interface age” value, reset “interface revision” to zero;
- if you make backward-incompatible changes, breaking ABI (removing interfaces for instance), increase the “current interface” value and reset both “older interface age” and ”interface revision” to zero.
Depending on the operating system, this will create a soname change either on backward-incompatible changes (Linux, Solaris and Gentoo/FreeBSD), or with any type of change to the interface (vanilla FreeBSD).
Again, the idea is that each time you might have a backward-incompatible change you get a different soname so that the loader can’t mix and match different interfaces. When you don’t guarantee any ABI stability between versions, usually for internal libraries, like GNU binutils do for libbfd, you just put the package name in the library’s basename rather than soversion, and set the soversion to all zeros, so you get stuff like
libbfd-2.20.so.0.0.0. This way you’re sure that, whether you change interfaces or not, an upgrade of your package won’t break others’ software. Of course it should also be enough for people to understand that it should not be used at all since it’s not guaranteed to be stable.
Next step is going to describe the symbol versioning technique to reduce the amount of backward-incompatible changes, to keep the same ABI available until it really has to go.