As described before Vala have quite interesting mix of high and low-level features. One of elements one need to be aware in Vala is that there is an ABI, when the “ABI compatibility” is needed and how to keep it.
What’s an ABI and why should I care
Most programmers know about API, which is a contract about how the components should interact. ABI is also a contract however while API is on the source code level, ABI is on binary level.
The problem is that there is no automated check if library is ABI compatible. While there are some automated tools, they are only useful when one knows how to read the output.
Further problem is that the tools are not aware about Vala or GObject specific issues and can report a false positive or false negative if one’s not careful.
As a rule of thumb you need to care every time you distribute the binary of library/plugin separately from program. That mean that if you bundle library with program (common practice on Windows but not on, for example Linux) the ABI does not concern you. Similarly if you are writing an application parts which are not concerning plugins does not need to be ABI-compatible.
On the other hand if you have a library which you intend to publish and you do break the ABI the programs using it might break in subtle ways depending on the change:
- It might result in unresolved symbols – a symbol existed in a previous version (or next) but it does not in current one. The linker will print the error on the output but unless user starts the program from commandline or check the logs the program will simply not work.
- It might result in calling or overriding different function than intended. For example every time someone wants to add something to collection (s)he clears it.
- It might result in garbage being read as the memory layout of struct have changes.
- As parameter might have been added/removed to the function it might have received a garbage data.
In any case such conflict cannot be detected automatically, which requires an additional effort from package maintainers (and on Gentoo it prevents the preserved libraries feature from working).
Note about naming
Speaking about ABI we are mostly concerned with two things:
- Symbols – a simplest way of thinking about symbol is that it is some pointer to where something (some static resource) is located resolved at runtime. For example
strtokis a symbol which needs to be used when code calls this function.
- Memory layouts – if there is a chunk of memory (object, vtable, etc.) how can we get the things we are interested in.
What to do it the ABI have changed
The Linux partially avoids the problem of the DLL Hell by versioning the libraries (technique currently used by Windows as well). However the version of a library is not the same as version of package (a simpler explanation of what to do is in Maintainers Corner).
The version information contains 3 parts:
- It changes every time the ABI have changed.
- It changes with every release, being either incremented (if ABI stayed the same) or reset to 0 (if ABI changed).
- Age denotes with how many releases backward the current library is compatible. Therefore it should be incremented every time the current is incremented but the ABI compatibility is preserved and it should be reset to 0 every time the ABI is broken.
In such way the change can be detected automatically (for example by revdep-rebuild on Gentoo or by similar tools on binary distributions).
Parallel installation and symbol versioning
From time to time the parallel installation and/or symbol versioning is proposed as a way of dealing with ABI breakage. The parallel installation is when the library name changes slightly (for example the current version is added) to facilitate two versions being installed, while symbol versioning adds a ‘namespaces’ so a single library can contain 2 ABI versions.
While they work in some circumstances they tend to not be a silver bullet. In general both of the propositions allows for the 2 versions of the code (older and newer) to live at single system at single time. While it is achievable or sometimes even required this can cause problems if not used carefully. It is always possible to have a traingle dependencies – program is using library A and B, where A depends on B. Now consider what happens if B has broken the ABI and there two versions used in single program (by symbol versioning or parallel installation).
Both program and library can communicate with correct copy. However we need to consider what if there is exchange of data. If the memory layout have changed, either user visible or opaque, this would cause a problems (incorrect virtual method call, incorrect data read or written etc.). While it is possible to still maintain the compatibility – for example by adding a Magic number – it do requires additional effort (and is possibly non-trivial in Vala/GObject).
In the next episode
In the following post(s) I will try to describe the common ways one can break the ABI compatibility and how to avoid it.