Quantcast
Channel: Fmehphisto
Viewing all articles
Browse latest Browse all 16

How not to package a library

$
0
0

I need to vent a little steam about the way that GNU binutils is packaged in Ubuntu.

Now binutils is a very important package, and contains many tools which are the basis of the development toolchain on Linux and other free platforms, and widely used on non-free platforms also.  Important parts of the build pipeline such as gasldar, and strip live here.  So do a bunch of commandline utilities which are less well known but just as useful in their way like strings, objdump, readelf, and addr2line.

Binutils also contains some more obscure stuff that practically nobody uses…like the BFD library.

BFD stands for Binary File Descriptor (not Big F***ing Deal as you might guess).  It’s basically the binutils internal abstraction layer for object files, executables, and core files.  These days on almost any Unix-like platform that means ELF objects, but the BFD library supports all kinds of file formats and platforms.  Dozens of them.

So BFD is a very important library.  And it’s packaging is a mess.

BFD lives in a kind of confused halfway state between being a supported externally visible library and an internal abstraction layer. The library is documented, and the documentation is shipped with binutils and includes this statement:

BFD is a package which allows applications to use the same routines to
operate on object files whatever the object file format. A new object
file format can be supported simply by creating a new BFD back end and
adding it to the library.

Now that really sounds like it’s meant to be external. But, and this a big but, there’s no meaningful ABI for the library. It’s internal structures are entirely visible in the header file, and the API requires you to reach into those structures, but the names, types, and order of the fields are subject to arbitrary change from one bintuils version to the next. The library has no API or ABI versioning scheme either, so there’s no easy way to write code which handles multiple versions.

Sensibly written libraries go to great lengths to hide their internal structures, or to keep the ABI view of those structures stable, and also keep the signatures of functions stable. Gnome libraries typically do this.

Another set of libraries will change their APIs and ABIs from time to time but use the ELF symbol versioning mechanism to ensure that older applications will continue to run. The best example is glibc. On my system there are two (presumably incompatible) versions of the pthread_cond_timedwait function.


% objdump --dynamic-syms /lib/x86_64-linux-gnu/libc.so.6
...
0000000000101ed0 g DF .text 0000000000000026 GLIBC_2.3.2 pthread_cond_timedwait
0000000000131c70 g DF .text 0000000000000026 (GLIBC_2.2.5) pthread_cond_timedwait
...

Yet another class of libraries do change their API but provide a way to detect this at compile time. For example, here’s some typical sample code which uses Berkeley DB.

#if (DB_VERSION_MAJOR > 3) || ((DB_VERSION_MAJOR == 3) && (DB_VERSION_MINOR > 0))
r = (dbenv->open)(dbenv, dbdir, flags, 0644);
#else
r = (dbenv->open)(dbenv, dbdir, NULL, flags, 0644);
#endif

But BFD doesn’t do any of that (well, that’s not entirely fair, some more recent changes have had some support for conditional compilation, like BFD_SUPPORTS_PLUGINS). The ABI changes and there’s no way to know at link time.

This creates one giant headache for distro packager folks.

RedHat solved it by not building or shipping a dynamic library for BFD. Instead they ship a static library only, in the binutils-devel package. This is pretty sensible; applications will link against -lbfd and get the static library. The executables might be a bit bigger, but they work and that counts for a lot.

Ubuntu solved it by shipping a dynamic library, and appending the version number of binutils to the name of the library. Not just a library version number which indicates the ABI revision and changes rarely, but the full version of the package, which changes on every single release. Like this

/usr/lib/libbfd-2.22.90-system.20120924.so

and all the binutils programs that link against it get the dynamic library and depend on that library at runtime.

% ldd /usr/bin/objdump
...
libbfd-2.22.90-system.20120924.so => /usr/lib/libbfd-2.22.90-system.20120924.so

which keeps those executable sizes down. When a new binutils package is released, there’s a new library name but all the executables that link against it at runtime are also updated, so it Just Works right?

Wrong! When somebody like me comes along and actually uses the BFD library in another package, like ggcov, everything falls apart. But not immediately, oh no. What happens is:

  1. User downloads ggcov source.
  2. User builds ggcov, linking against -lbfd, gets today’s libbfd dynamic library.
  3. Ubuntu’s packaging system detects and records in the ggcov package a dependency on binutils but not the specific version.
  4. User happily uses my package for a while.
  5. A few weeks later, Ubuntu releases a new binutils package with a new dynamic library
  6. Of course there’s no warning from the packaging system when the user upgrades binutils
  7. But the BFD library ggcov was using is now gone
  8. User runs ggcov again, it fails to start due to a missing library
  9. User files a bug report against ggcov. Like Ubuntu #713811.
  10. User decides to try the latest version of ggcov; rinse and repeat

And I get to pick up all the pieces. Great.

Anyway, the latest release of ggcov has a workaround for this mess. It tries quite hard to link against a static BFD library first, and only falls back to a dynamic library if no static library could be found. This is about a dozen lines of convoluted shell code in the configure.in that would not be necessary if the Ubuntu guys would be a little bit more thoughtful when packaging binutils.

Ok, that’s enough venting for now.


Viewing all articles
Browse latest Browse all 16

Latest Images

Trending Articles





Latest Images