lucy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Nick Wellnhofer <wellnho...@aevum.de>
Subject [lucy-dev] Shared library versioning
Date Fri, 19 Apr 2013 13:13:20 GMT
Hello lucy-dev,

I spent a bit of time researching how shared library versioning works on 
different operating systems and how the stand-alone C library could use 
it. First, let's recap some basics about versioning. A version usually 
consists of

     * Major version. This is increased whenever the API or ABI changes
       in a way that is not backward-compatible. The dynamic linker
       should never load a library with a different major version than
       the one specified in the executable, no matter if it's a newer
       or older version.

     * Minor version. This is increased whenever the API changes in a
       backward-compatible way. Executables linked against an older
       version of a library should continue to work with newer versions
       of the library as long as the major version doesn't change.
       On the other side, executables linked against a newer library
       version shouldn't run with older library versions as they might
       use some of the newer features.

     * Patch level. This is increased whenever a new version of the
       library is released that doesn't change the API at all,
       typically for bug fixes. An executable should work with every
       library that has the same major or minor version, regardless
       whether the patch level is higher or lower.

So if we have version strings of the form "major.minor.patchlevel" and a 
library with version "1.3.4", the following should happen with 
executables linked against different versions:

     * Executable version "0.9.2": FAIL
     * Executable version "1.0.3": OK
     * Executable version "1.3.0": OK
     * Executable version "1.3.4": OK
     * Executable version "1.3.9": OK
     * Executable version "1.4.0": FAIL
     * Executable version "2.2.2": FAIL

Or, if the executable version is linked against "1.3.4":

     * Library version "0.9.2": FAIL
     * Library version "1.2.5": FAIL
     * Library version "1.3.0": OK
     * Library version "1.3.4": OK
     * Library version "1.3.9": OK
     * Library version "1.4.0": OK
     * Library version "2.2.2": FAIL

Now, let's see how different operating systems or, more specifically, 
different object file formats load libraries and support library versioning.

Portable Executable (PE) on Windows
-----------------------------------

Windows searches for DLLs by filename in a couple of directories at run 
time:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586(v=vs.85).aspx

PE has no support for versioning (to my knowledge). It's only possible 
to encode the major version in the filename of the DLL.

Mach-O on OS X
--------------

At run time, Mach-O libraries are loaded using the "install name" 
encoded in the executable. The install name is a full filesystem path. 
This path can start with a number of placeholders like @executable_path, 
@loader_path, or @rpath.

The major version can be encoded in the install name. For minor versions 
and patch levels, every library has a so called "compatibility version". 
When an executable is linked against a library the compatibility version 
of the library is encoded in the executable at compile time. If the 
dynamic linker later finds a library with an older version, it refuses 
to load it. With the "major.minor.patchlevel" scheme, a compatibility 
version of "minor.0" could be used.

For more information, see the OS X man pages for dyld, libtool, ld, 
otool, and install_name_tool.

ELF on many UNIX variants
-------------------------

The ELF dynamic linker searches libraries at run time by "soname" in a 
couple of directories. The soname is basically an arbitrary string with 
no relation to the library filename, but typically it's 
"name.so.major-version". The soname is encoded in libraries and 
executables at compile time.

For minor versions, some ELF implementations support "version scripts" 
in which versions can be specified for every symbol. This allows for 
very sophisticated version checks. On Linux, you can even specify 
multiple versions of a symbol in a single library. The details are 
explained in-depth in chapter 3 of Ulrich Drepper's DSO Howto:

     http://www.akkadia.org/drepper/dsohowto.pdf

Regarding Lucy, I think it's too tedious to maintain version scripts 
with all the symbols. An easier approach might be to have a version 
script that covers only a single symbol like lucy_bootstrap_parcel. This 
would in effect be similar to Mach-O's compatibility version.

Another solution would be to implement minor version checks explicitly 
in lucy_bootstrap_parcel:

     void lucy_bootstrap_parcel(int minor_version) {
         if (minor_version > x) {
             // throw
         }
         ...
     }

Or we could ignore minor version checks completely and leave it all to 
the user.

Nick


Mime
View raw message