UncLe D. wants YOU! ... to use versioned symbols
I would like to encourage more library developers to adopt the use of versioned symbols.
Without versioned symbols, ELF binaries only effectively express a “major version” depenency (in Semantic Version terms.) That limitation extends to the package manager, which will express only a major version dependency.
Because there is no minor version in RPM dependencies, every time a
user executes dnf install <package>, dnf update <package>, or
dnf update --security, there is a risk that the newly installed
packages will require features not present on the system, and they
will fail to run:
$ ./Bar 5 10 area
./Bar: symbol lookup error: ./Bar: undefined symbol: rectangle_area
This condition can affect anything that uses libraries without versioned symbols. It can cause gdm to fail, preventing users from logging in. It could break rpm or dnf, making it very difficult to fix the problem.
The binutils manual states that “symbol versioning is in effect a much more sophisticated way of doing minor version checking.” Versioned symbols are a simple addition that is backward compatible, which rpm can use to express more specific dependency information, and prevent runtime failures.
libfoo-versioned provides as a simple demonstration of versioned libraries. In that project, you’ll find foo.c, which provides two functions, and libFoo.map, which describes which version of the library each symbol first appeared in:
LIBFOO_1.0 {
global:
rectangle_perimeter;
local:
*;
};
LIBFOO_1.1 {
global:
rectangle_area;
};
That map file and just one extra argument,
-Wl,--version-script=libFoo.map are all that is needed to add symbol
versions to a library.
Upkeep is also straightforward. Whenever a new release includes new symbols that you want to export, add a new set of symbols to the map. Sets within a symbol version map should never be changed after a release.
If you make this project, you’ll get an app named Bar, which is
linked to the unversioned build of the shared library, and an app
named Bar-versioned which is linked to the versioned build of the
shared library.
ldd ./Bar will demonstrate that Bar uses the shared library in the
lib-unversioned directory by default (the location is embedded using
rpath). You can run env LD_LIBRARY_PATH=lib-versioned ./Bar to use
the library build that includes versioned symbols, demonstrating that
the library remains backward compatible when versioned symbols are
added.
If you are developing a library that does not currently include versioned symbols, please don’t let rebuilding the version history be a reason to put off adoption. The simple path forward is simply to add all of your symbols to the version you consider current, and then add new versions for any symbols you introduce in the future.
See Also
- https://people.redhat.com/drepper/symbol-versioning
- https://people.redhat.com/drepper/goodpractice.pdf
- https://people.redhat.com/drepper/dsohowto.pdf
- https://www.berrange.com/posts/2011/01/13/versioning-in-the-libvirt-library/
- https://sourceware.org/binutils/docs-2.44/ld.pdf
- https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic.html
- https://maskray.me/blog/2020-11-26-all-about-symbol-versioning