3

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

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

const char MY_VERSION[] = "some_version_information";

但是,我在从外部程序中检索信息时遇到了难以置信的困难。dlopen使用共享库,使用和加载库和查找符号相当容易dlsym,但这可能不是最好的方法,而且它根本不适用于可执行文件。另外,如果可能的话,我希望它可以与为不同架构构建的可执行文件和库一起使用。

我认为由于共享库和可执行文件都使用 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 文件中的字符串符号?

4

2 回答 2

2

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

 extern const char MY_VERSION[];

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

那么你的问题是如何MY_VERSION在一些外部 ELF 可执行文件中找到一个符号(简单的方法可能是popen一些nm进程,见nm(1))。顺便说一句,它与功能符号(或数据符号)相同。您可以使用诸如libelflibelfin(或 venerable libbfd)之类的库或自己解析ELF格式(请务必先阅读维基页面)

您应该学习和理解ELF 格式。您需要仔细阅读有关 ELF 和x86-64 ABI的文档。使用objdump(1)readelf(1)探索现有的 ELF 可执行文件。另请阅读elf(5)。阅读符号表是如何表示的,以及它们的哈希码是如何计算的。当然详细阅读所有可能的重定位。您可以阅读 Levine 的关于Linkers and Loaders的书和 Drepper 的关于如何编写共享库(都解释 ELF)的论文,以及Assembler Language HowTo和 Ian Taylor 的关于gold, 和ELF:通过 DT_GNU_HASH 更好地查找符号。另请参阅 Solaris 文档,例如哈希表部分和 OSDEV ELF 教程ELF页面

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

(大约 20 年前,我为 Sparc 做过这件事;这并不是特别难)

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

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

您可能还想了解execve(2)ld-linux(8)的工作原理,进程的虚拟地址空间是什么(参见proc(5),try cat /proc/$$/maps

于 2017-09-18T19:21:11.357 回答
2

我发现我可以使用自定义部分来存储版本信息,然后转储该部分以“提取”字符串。

以下是版本信息的声明方式:

__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缓冲区。

于 2017-09-18T19:13:40.320 回答