clinuxelfbfd

Store and retrieve version information in ELF file


I'm trying to figure out a good way to store and retrieve version information in C / C++ executables and libraries on Linux. I'm using the GCC compiler for my C and C++ programs.

The storage part is pretty straightforward; declaring a variable like this stores it in the .rodata section of the output file:

const char MY_VERSION[] = "some_version_information";

However, I'm having an incredibly difficult time with retrieving the information from an external program. With shared libraries, it is fairly easy to use dlopen and dlsym to load a library and look up a symbol, but this might not be the best way to do it, and it won't work at all for executables. Also, if possible, I would like this to work with executables and libraries built for a different architecture.

I figure that since shared libraries and executables both use the ELF format, it makes sense to use a library that knows how to read ELF files. The two I was able to find are libelf and BFD; I'm struggling to find decent documentation for each. Is there perhaps a better library to use?

This is what I have so far, using BFD:

#include <stdio.h>                                                                                                                                                                                                               [6/1356]
#include <string.h>
#include <bfd.h>

int main(int argc, char* argv[])
{
    const char *filename;
    int i;
    size_t storage;
    bfd *b = NULL;
    asymbol **symbol_table;
    long num_symbols;

    if(argc != 2) return 1; // todo: print a useful message
    else filename = argv[1];

    b = bfd_openr(filename, NULL);

    if(b == NULL){
        fprintf(stderr, "Error: failed to open %s\n", filename);
        return 1;
    }

    // make sure we're opening a file that BFD understands
    if(!bfd_check_format(b, bfd_object)){
        fprintf(stderr, "Error: unrecognized format\n");
        return 1;
    }

    // how much memory is needed to store the symbol table
    storage = bfd_get_symtab_upper_bound(b);

    if(storage < 0){
        fprintf(stderr, "Error: unable to find storage bound of symbol table\n");
        return 1;
    } else if((symbol_table = malloc(storage)) == NULL){
        fprintf(stderr, "Error: failed to allocate memory for symbol table\n");
        return 1;
    } else {
        num_symbols = bfd_canonicalize_symtab(b, symbol_table);
    }

    for(i = 0; i < num_symbols; i++){
        if(strcmp(symbol_table[i]->name, "MY_VERSION") == 0){
            fprintf(stderr, "found MY_VERSION\n");

            // todo: print the string?
        }
    }

    return 0;
}

I realize that printing the string may not be very simple due to the ELF format.

Is there a straightforward way to print a string symbol that is stored in an ELF file?


Solution

  • I figured out that I could use a custom section to store the version information, and then just dump the section to 'extract' the string.

    Here's how the version information should be declared:

    __attribute__((section("my_custom_version_info"))) const char MY_VERSION[] = "some.version.string";
    

    Then, in the program using BFD, we can get the section a few different ways. We can use bfd_get_section_by_name:

    asection *section = bfd_get_section_by_name(b, "my_custom_version_info");
    

    Now that we have a handle to the section, we can load it into memory. I chose to use bfd_malloc_and_get_section (you should make sure section isn't NULL first):

    bfd_byte *buf;
    if(!bfd_malloc_and_get_section(b, section, &buf)){
        // error: failed to malloc or read the section
    }
    

    Now that we have the section loaded into a buffer, we can print its contents:

    for(int i = 0; i < section->size && buf[i]; i++){
        printf("%c", buf[i]);
    }
    printf("\n");
    

    Don't forget to free the buffer.