在 ELF 文件中存储和检索版本信息

Store and retrieve version information in ELF file

我正在尝试找出一种在 Linux 上的 C/C++ 可执行文件和库中存储和检索版本信息的好方法。我正在为我的 C 和 C++ 程序使用 GCC 编译器。

存储部分非常简单;像这样声明一个变量,将其存储在输出文件的 .rodata 部分:

const char MY_VERSION[] = "some_version_information";

但是,我很难从外部程序检索信息。对于共享库,使用 dlopendlsym 来加载库和查找符号相当容易,但这可能不是最好的方法,它不会工作全部用于可执行文件。另外,如果可能的话,我希望它能与为不同架构构建的可执行文件和库一起使用。

我认为由于共享库和可执行文件都使用 ELF 格式,因此使用知道如何读取 ELF 文件的库是有意义的。我能找到的两个是 libelf 和 BFD;我正在努力为每个人找到像样的文档。是否有更好的库可供使用?

这是我目前使用 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;
}

我意识到由于 ELF 格式,打印字符串可能不是很简单。

是否有直接的方法来打印存储在 ELF 文件中的字符串符号?

我发现我可以使用自定义部分来存储版本信息,然后将该部分转储到 'extract' 字符串。

声明版本信息的方式如下:

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

然后,在使用BFD的程序中,我们可以通过几种不同的方式获取该部分。我们可以使用 bfd_get_section_by_name:

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

现在我们有了该部分的句柄,我们可以将它加载到内存中。我选择使用 bfd_malloc_and_get_section(你应该首先确保 section 不是 NULL):

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

现在我们已将部分加载到缓冲区中,我们可以打印其内容:

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

不要忘记 free 缓冲区。

在您的可执行文件中,只需声明

 extern const char MY_VERSION[];

顺便说一句,对于 C++,最好声明 extern "C" 该符号(即使在定义它的文件中)。

然后你的问题是如何在一些外部 ELF 可执行文件中找到符号 MY_VERSION(简单的方法可能是 popen 一些 nm 进程,参见 nm(1)). BTW, it is the same as for a function symbol (or for a data one). You could use a library such as libelf or libelfin (or the venerable libbfd) or parse the ELF format yourself (be sure to read first that维基页面)

你应该学习并理解ELF格式。您需要仔细阅读有关 ELF 和 x86-64 ABI. Explore existing ELF executables with objdump(1) & readelf(1). Read also elf(5). Read how symbol tables are represented, and how their hash code is computed. Of course read in details all the possible relocations. You could read Levine's book on Linkers and Loaders and Drepper's paper on How to Write Shared Libraries (both explain ELF), and also Assembler Language HowTo, and Ian Taylor's paper on gold, and ELF: better symbol lookup via DT_GNU_HASH. See also Solaris documentation e.g. on Hash Table Section and OSDEV ELF tutorial & ELF

的文档

您不需要任何特定部分(或段)。

(我在大约 20 年前为 Sparc 做过;它不是特别难)

您还可以查看 emacs 源代码,其 unexec.c 正在编写一些 ELF 文件

顺便说一句,ELF 有一些带符号的版本控制信息,例如参见dlvsym(3)

您可能还想了解如何 execve(2) or ld-linux(8) works, what is the virtual address space of a process (see proc(5),试试 cat /proc/$$/maps)