Doing Stuff

Embedding Version Info into ELF Shared Libraries

How to embed version information into an ELF shared library?

That is the question I ended up answering for myself, and here are the methods I discovered for embedding version information.

This will cover embedding a product/release version in libraries distributed outside a package. Packages have their own version that can be referenced, so we don't need to embed a version.

What about 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 | grep "@(#)"

$ strings | 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 ""

//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
Displaying notes found in:
  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.