Сохранить и получить информацию о версии в файле ELF

Я пытаюсь найти хороший способ хранения и получения информации о версии в исполняемых файлах и библиотеках C / C++ в Linux. Я использую компилятор GCC для моих программ на C и C++.

Часть хранения довольно проста; объявление такой переменной сохраняет ее в разделе.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?

3 ответа

Решение

Я понял, что могу использовать пользовательский раздел для хранения информации о версии, а затем просто создать дамп для "извлечения" строки.

Вот как информация о версии должна быть объявлена:

__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" этот символ (даже в файле, определяющем его).

Тогда ваша проблема в том, как найти символ MY_VERSION в некотором внешнем исполняемом файле ELF (простой способ может быть popen немного nm процесс, см. nm (1)). Кстати, это то же самое, что и для символа функции (или для символа данных). Вы можете использовать библиотеку, такую ​​как libelf или libelfin (или почтенный libbfd) или самостоятельно проанализируйте формат ELF (обязательно прочитайте сначала этот википейдж)

Вы должны изучить и понять формат ELF. Вам необходимо внимательно прочитать документацию по ELF и по x86-64 ABI. Исследуйте существующие исполняемые файлы ELF с помощью objdump(1) и readelf (1). Читайте также эльф (5). Прочитайте, как представлены таблицы символов и как вычисляется их хэш-код. Конечно, подробно прочитайте все возможные переезды. Вы можете прочитать книгу Левина о компоновщиках и загрузчиках и статью Дреппера о том, как писать общие библиотеки (обе объясняют ELF), а также HowTo на языке ассемблера и статью Яна Тейлора о 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), попробуйте cat /proc/$$/maps)

Традиционный способ сделать это — использовать строки SCCS what(1). См. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/what.html .

Другие вопросы по тегам