There is no day so wasted that you can’t take notes about what didn’t work, so here’s some talk about ABI-compliance-checking. ABI-compliance, or ABI-compatibility, is roughly when a shared library can be changed (to a different version, usually an update and upgrade) and users of that shared library (applications, or other libraries) just work with the new version. This requires some discipline, and there are tools to help out.

“Wasted” is, in the end, relative: there’s this blog entry, and I have some new tooling to build now that I know what works and what does not.

KDE libraries – kdelibs in KDE4 times, and now KDE Frameworks – have pretty stringent ABI requirements. Frameworks update every month, so we need to be sure that consumers don’t need to be recompiled with the new version all the time. There is some fairly extensive documentation with do’s and don’ts.

One way to help maintain binary compatibility is to use tools that check the ABI: figure out the shape of the ABI in one version, the shape in another version, and compare those shapes. KDE Frameworks have checks in place, like this one (that link assumes openSUSE and Qt 5.15 are still in use and that there was a recent successful build).

Generally, an ABI-shape getting bigger is not a problem (from a technical perspective, although you can have all kinds of semantic mix-ups). Things that go away – functions, variables, etc. – those are problematic.

Calamares is a Linux system installer – it can be customized by Linux distro’s to act as the installer for their ISO images. It’s a C++ program offering modules for all kinds of system-installation services. It also offers an ABI: the modules use the ABI of the Calamares libraries to talk to the main program. Calamares supports “third-party” modules, e.g. modules specific to one distro or otherwise customized, and for those third-party modules, ABI compatibility suddenly becomes an issue: it would be nice if they didn’t have to be recompiled when a new Calamares library comes out. That can only happen if the Calamares libraries commit to ABI compatibility.

Traditionally, Calamares has had zero ABI compatibility. That comes down to me not having that discipline, and not having (er .. maybe “not knowing about”) useful tools. But Harald was nagging me about it, and I realise it would be useful for friends at Linux distro’s that build their own modules.

Library updates example

Consider the source code for this library:

// Library version 1
int library_version = 1;

Assume there’s a header that says extern int library_version;, etc. etc. In any case, this library contains at least one exported symbol, namely library_version. We can build an executable against this shared library and run it:

adridg:build$ ./hello
Hello, library_version=1

We can update the library and build the library, then without changing the executable, update the library by replacing the old .so:

adridg:build$ cp libabi2.so libabi.so
adridg:build$ ./hello
Hello, library_version=2

Lovely! Compatible symbols all around. But the code looks Pythonic, rather than Qt, so we’ll release a newer version with a different coding style:

// Library version 3
int libraryVersion = 3;

That is in no way, shape, or form, a drop-in replacement: applications that link to the symbol library_version will fail to run. Just copying over the library leads to a run-time-linker error like this one:

adridg:build$ cp libabi3.so libabi.so
adridg:build$ ./hello
./hello: symbol lookup error: ./hello: undefined symbol: library_version

This is the most egregious form of breaking-ABI, and an ABI-checking tool should spot that from a mile off.

abi-compliance-checker

The first tool I looked at was ABI compliance checker. It’s actually the tool behind the KDE Frameworks check mentioned earlier. There is a description with a lovely monkey as mascot here from 2010 – so the tool has been around in various incarnations for a long time.

This tool is packaged for FreeBSD, but in a very very old version, so I have a TODO item of “Update the port”.

There is a companion tool, abi-dumper, to be used with abi-compliance-checker. The documentation states that using the dumper is faster, more reliable, and simpler.

That documentation chewed up a day and half of my time, and simply isn’t correct (edit: it is correct, but you need up-to-date versions of the tooling, see below).

Using abi-dumper will produce suitable perl-variables that describe the library, including the symbols that are defined by the library. The tool abi-dumper also has a command-line-argument -compare to do just that: compare two dumps. Applied to a dump of versions 1 and 3 of the egregious library example, abi-dumper can tell me that the libraries are different: it says I removed library_version and added libraryVersion which is true!

Unfortunately, using abi-compliance-checker with the resulting dumps says that the libraries are 100% compatible, forward and backward. This is a useless tool.

abi-compliance-checker revisited

Since the KDE Frameworks use the tool I’ve just callously and carelessly labeled “useless”, and those reports don’t look completely useless, there must be something more to it. Having spent half a day looking at different versions of the tool, checking if there is more documentation .. nope. It turns out that the KDE tooling uses the “old” way of invoking the tool, with an “XML” file describing library- and header-locations. This invocation of the tool does some compiling of its own.

Running this version of the tool shows me 0% compatibility. Yay! So the tool can work if you ignore some of the documentation that tells you to use the newer, faster, simpler way (edit: or get a recent version of abi-dumper).

Applying this older invocation to Calamares actually produces some nice reports, useful ones, too for figuring out when I can start committing to binary compatibility (e.g. if there’s even compatibility between libcalamares.so.3.2.35 and libcalamares.so.3.2.36).

abi-compliance-checker re-revisited

Passive-agressive dickery via a blog (like this one) is not going to solve anything, so I spent some time putting together an extensive example, with a test script, so I could write a proper bug report upstream (in GitHub).

While doing that I spotted a fairly recent abi-dumper release, version 1.2, with release notes “Fedora 30 compatibility ; misc fixes”. On a whim I copied the git version of that tool over top of the version installed in my Debian machine.

And that fixed the “useless tool” part as well. I am duly chastened for using a stable Debian version, although I Have Opinions about release notes and release frequencies. Also note that the newer version hasn’t reached Ubuntu, so if I’d been on any of my other likely testing platforms I would have hit exactly the same problem.

I knew I should have checked repology first, before using any tools at all. So I closed my upstream bug report and commented on some open issues that could probably be fixed with an update, too.

abigail

Since I was griping about ABI checking, I got a suggestion from Matthias Klumpp to look at abigail and in particular at abidiff which .. is a diff tool for ABIs. No surprise there.

Feed the tool two .so files and it spits out a report and a useful exit code. The report is plain text, not HTML so it’s a bit unstructured, but certainly readable. It seems to be much faster than abi-compliance-checker as well (probably because it is reading the ELF files directly, rather than doing a round-trip through external tools).

The abigail tools have extensive documentation. That’s a big plus.

Where does this leave Calamares

This leaves Calamares in a bit of an awkward spot, actually. The HTML output from the one tool is nice to read. The other tool has a better getting-started experience.

There may be another qualitative issue: abigail tooling looks only at the symbols, while abi-compliance-checker in its “old style” invocation takes headers into account, which presumably means that it can take what-does-the-header-promise into account as well. For “new style” (with the new abi-dumper) I don’t think there’s a difference.

For my CI pipeline I think I’ll end up with abigail, simply because it has a version available on my CI host that works. It may not pick up subtle changes, but will at least tell me quickly when I’ve removed the constructor for Calamares from the lib.