Сохранить и получить информацию о версии в файле 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 .