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?
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.