Embedding Version Info into ELF Shared Libraries

How to embed version information into an ELF shared library?

Here are the methods I have used for embedding version information.

These methods are for embedding a version into libraries distributed outside of a package. Packages have their own version information, so we don't need to embed one in the library.

What about the ABI version?

ABI version can be separate from your product/release version. If you haven't changed the ABI interface, you can keep the same ABI version while updating the product/release version on new builds. There are some great resources, policy, and tools for how to handle ABI versioning.

Symbol versioning is another way to handle ABI changes without changing the ABI version, this blog post is all about symbol versioning.

Ways of embedding version information

Okay, so you want to embed a version in your ELF shared libraries. How should you do it?

SCCS String

Probably the easiest to add, this method uses the Source Code Control System @(#) convention to embed the version.

#define VERSION "1.2.3"

static char sccsid[] __attribute__((used)) = "@(#)Version " VERSION;

Then the version can be retrieved via what(1) or strings libfoo.so | grep "@(#)"

$ strings libfoo.so | grep "@(#)"
@(#)Version 1.2.3

This is human-readable but slow since it has to look at all the strings in the binary. If you want to get the version programmatically, we have another method.

ELF Note Header

This method creates an ELF note header by defining a note struct with the descriptor field. Then uses __attribute__((used, section(...), aligned(4))) to tell the compiler to add the ELF header.

#include <elf.h>

#define VERSION "1.2.3"
#define NOTE_SECTION ".note.foo.version"

//Elf64_Nhdr + desc
struct version_note {
    Elf64_Word	namesz;
    Elf64_Word	descsz;
    Elf64_Word	type;
    char    desc[sizeof(VERSION)];
};

__attribute__((used, section(NOTE_SECTION), aligned(4)))
static const struct version_note version = {
    .namesz = 0,
    .descsz = sizeof(VERSION),
    .type = NT_VERSION,
    .desc = VERSION
};

Note sections contain a series of notes. Each note is followed by the name field (whose length is defined in namesz) then by the descriptor field (whose length is defined in descsz) and whose starting address has a 4 byte alignment. Neither field is defined in the note struct due to their arbitrary lengths.

More details in the elf manpage under the Notes (Nhdr) section.

Then readelf -Wn or an ELF parsing library can read the version header.

$ readelf -Wn libfoo.so
Displaying notes found in: .note.foo.version
  Owner                 Data size       Description
  (NONE)               0x00000006       NT_VERSION (version)       description data: 31 2e 32 2e 33 00

The downside is the output is in hex when using readelf, unlike the human-readable format of SCCS above. But this method is faster since it's only reading the ELF headers rather than the whole binary.

Can executables use these methods?

Yup, if you need to provide a version without executing. But the more intuitive solution is to have a --version argument.