[Matroska-devel] libmatroska/ebml 0.9.0/0.8.0 is binary incompatible with previous version

Cristian Morales Vega cmorve69 at yahoo.es
Sun May 30 13:03:26 CEST 2010


2010/5/30 Steve Lhomme <slhomme at matroska.org>:
> I often refer to it as libebml2 (currently 0.9.7 or something like that). I
> suppose a linux package would be libebml2-0.9.7
> For the new libs that are incompatible, I suspect any C++ lib in general
> that has API additions is binary incompatible. Isn't there a way to tell a
> package is incompatible with the previous version ? Package dependencies are
> also there to avoid mismatch.

Ulrich Drepper, glibc maintainer, wrote a good article about all this:
"How to Write Shared Libraries". It is available at
http://people.redhat.com/drepper/, under ELF section. He also
developed a symbol versioning scheme, available since glibc 2.1, that
allows what you ask for
(http://people.redhat.com/drepper/symbol-versioning). But "How to
Write Shared Libraries" is the document to read, in there it's
explained not just the Linux way but also other schemes like the
Solaris one: http://people.redhat.com/drepper/dsohowto.pdf (chapter 3:
"Maintianing APIs and ABIs")

Quick explanation for Linux:
For libebml 0.8.1 what it's expected is:
libebml.so
libebml.so.0
libebml.so.0.8
libebml.so.0.8.1

libebml.so.0.8.1 being the real file and all the others symlinks to
it. And the soname being libebml.so.0.

To the linker you give "-lebml", so it can find always the library.
But it reads the soname and adds a DT_NEEDED entry to the binary with
the content libebml.so.0. Now at load time the libebml.so.0 is the
file that is searched for.

Now after 0.8.1: A bugfix release, without API additions, should be
"0.8.2". A release with API additions should be "0.9.0". A release
that breaks binary compatibility should be "1.0.0".

When you break the binary compatibility the distro packager will create:
- A libebml0 package with libebml.so.0, libebml.so.0.8 and libebml.so.0.8.1
- A libebml1 package with libebml.so.1, libebml.so.1.0 and libebml.so.1.0.0
- A libebml-devel package with libebml.so (a symlink to
libebml.so.1.0.0) and the headers of version 1.0.0.

So new packages will be only compiled against the new version (you
don't want new users to continue using the old version), but old
binaries compiled against the old version will continue to work.
The headers should be in /usr/include/ebml for all 0.x.y releases. You
don't need to version the headers dir with minor and micro versions
since only one version (the latest) will be installed at the same
time. But if you are going to provide an all new API for libebml2 then
yes, the headers should be versioned (/usr/include/ebml2).


Every binary compiled with version z.x will run with version z.y if y
> x. Since it's expected that after compilation the user will upgrade
the libraries but never downgrade them this is enough. But you are
correct in that a binary compiled with version 0.9.0 could not work if
at runtime there is version 0.8.1. And the error will not be detected
until the program fails.

What Drepper added with its symbol versioning scheme is support to
NEVER break the binary compatibility. You can change the interface
with a new version, but you can create a library that provides both
the new and the old interfaces, and at runtime the binaries will get
the correct interface. The thing is that the scheme is pretty complex,
not supported in other Unixes and so has not been used a lot (I think
nowhere outside of glibc).
Still, you can use that versioning scheme in a simple way so that
binaries compiled against libebml 0.8.1 will refuse to start if at
runtime there is only libebml 0.8.0 and the binary uses a new function
added in 0.8.1. That's it, a way to know if the library is new enough
(notice that if you compile against 0.8.1 but your program doesn't
uses any new function it will still work with 0.8.0 at runtime).

The best part is that since the RPM build system analyzes binaries to
add automatically dependencies (I don't explicitly say man requires
libc, RPM sees that in the binary) the minor required version of the
libraries will be also added if symbol versioning is used. See
mkvtoolnix:

$ rpm -q --requires mkvtoolnix
libebml >= 0.8.0
libmatroska >= 0.9.0
...
libc.so.6()(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
libebml.so.0()(64bit)
...
libmatroska.so.0()(64bit)
...

It was compiled with libebml 0.8.0 and libmatroska 0.9.0. But since
them don't use symbol versioning rpm just added
"libebml.so.0()(64bit)" and "libmatroska.so.0()(64bit)", I had to
manually add the "libebml >= 0.8.0" and "libmatroska >= 0.9.0"
requirement. And it was compiled against glibc 2.10.1, but it didn't
used any function added after glibc 2.4. Since glibc uses symbol
versioning rpm automatically added the "libc.so.6(GLIBC_2.4)(64bit)"
requirement.


Again, "How to Write Shared Libraries" of Drepper is THE document to read.



More information about the Matroska-devel mailing list