C / C++ с GCC: статическое добавление файлов ресурсов в исполняемый файл / библиотеку

есть ли у кого-нибудь идея, как статически скомпилировать любой файл ресурсов прямо в исполняемый файл или файл общей библиотеки с помощью GCC?

например, я хотел бы добавить файлы изображений, которые никогда не меняются (и если они это сделают, мне все равно придется заменить файл) и не хотел бы, чтобы они лежали в файловой системе.

Если это возможно (и я думаю, что это потому, что Visual C++ для Windows может сделать это тоже), как я могу загрузить файлы, которые хранятся в собственном двоичном файле? Выполняет ли исполняемый файл синтаксический анализ, находит файл и извлекает из него данные?

возможно, есть вариант для GCC, который я еще не видел. Использование поисковых систем на самом деле не выплюнуло правильный материал.

Мне нужно, чтобы это работало для общих библиотек и обычных исполняемых файлов ELF.

любая помощь приветствуется

8 ответов


С imagemagick:

convert file.png data.h

дает что-то типа:

/*
  data.h (PNM).
*/
static unsigned char
  MagickImage[] =
  {
    0x50, 0x36, 0x0A, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 
    0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x0A, 0x32, 0x37, 
    0x37, 0x20, 0x31, 0x36, 0x32, 0x0A, 0x32, 0x35, 0x35, 0x0A, 0xFF, 0xFF, 
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 

....

для совместимости с другим кодом, то вы можете использовать либо fmemopen чтобы получить "обычный"


обновление Я вырос, чтобы предпочесть контроль собрание Джона Рипли .incbin на основании решения предлагает и теперь использует вариант на этом.

я использовал objcopy (GNU binutils) для связи двоичных данных из файла foo-data.bin в раздел данных исполняемого файла:

objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o

это дает вам foo-data.o объектный файл, который вы можете связать с исполняемым файлом. Интерфейс C выглядит примерно как

/** created from binary via objcopy */
extern uint8_t foo_data[]      asm("_binary_foo_data_bin_start");
extern uint8_t foo_data_size[] asm("_binary_foo_data_bin_size");
extern uint8_t foo_data_end[]  asm("_binary_foo_data_bin_end");

так что вы можете делать такие вещи, как

for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) {
    transmit_single_byte(*byte);
}

или

size_t foo_size = (size_t)((void *)foo_data_size);
void  *foo_copy = malloc(foo_size);
assert(foo_copy);
memcpy(foo_copy, foo_data, foo_size);

если ваша целевая архитектура имеет специальные ограничения относительно того, где хранятся постоянные и переменные данные, или вы хотите сохранить эти данные в .text сегмент, чтобы он вписывался в тот же тип памяти, что и ваш программный код, вы можете играть с objcopy параметры немного больше.


вы можете вставить двоичные файлы в исполняемый файл с помощью ld линкер. Например, если у вас есть файл foo.bar затем вы можете встроить его в исполняемый файл, добавив следующие команды в ld

--format=binary foo.bar --format=default

если вы призываете ld до gcc тогда вам нужно будет добавить -Wl

-Wl,--format=binary -Wl,foo.bar -Wl,--format=default

здесь --format=binary указывает компоновщику, что следующий файл является двоичным и --format=default переключается на формат ввода по умолчанию (это полезно, если вы укажете другой вход файлы после foo.bar).

затем вы можете получить доступ к содержимому файла из кода:

extern uint8_t data[]     asm("_binary_foo_bar_start");
extern uint8_t data_end[] asm("_binary_foo_bar_end");

есть также символ с именем "_binary_foo_bar_size". Я думаю, что это типа uintptr_t но я не проверял его.


вы можете поместить все свои ресурсы в ZIP-файл и добавьте это в конец исполняемого файла:

g++ foo.c -o foo0
zip -r resources.zip resources/
cat foo0 resources.zip >foo

это работает, потому что a) большинству исполняемых форматов изображений все равно, есть ли дополнительные данные за изображением, и b) zip хранит подпись файла в конец файла zip. Это означает, что ваш исполняемый файл является обычным zip-файлом после этого (за исключением вашего предварительного исполняемого файла, который может обрабатывать zip), который можно открыть и прочитать с помощью libzip.


от http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967:

недавно у меня возникла необходимость встроить файл в исполняемый файл. Поскольку я работаю в командной строке с gcc, et al, а не с причудливым инструментом RAD, который делает все это волшебным, мне не сразу стало ясно, как это сделать. Немного поиска в сети нашел хак, чтобы по существу cat его на конец исполняемого файла, а затем расшифровать, где это было основано на куче информации, о которой я не хотел знать. Казалось, должен быть лучший способ...

и есть, это objcopy для спасения. objcopy преобразует объектные файлы и исполняемые файлы из одного формата в другой. Один из форматов, который он понимает, - "двоичный", который в основном является любым файлом, который не находится в одном из других форматов, которые он понимает. Таким образом, вы, вероятно, предусмотрели идею: преобразуйте файл, который мы хотим встроить в объектный файл, тогда он может просто быть связанным с остальной частью нашего кода.

предположим, у нас есть данные имени файла.txt, который мы хотим внедрить в наш исполняемый файл:

# cat data.txt
Hello world

чтобы преобразовать это в объектный файл, который мы можем связать с нашей программой, мы просто используем objcopy для создания ".o " file:

# objcopy --input binary \
--output elf32-i386 \
--binary-architecture i386 data.txt data.o

это говорит objcopy, что наш входной файл находится в формате" binary", что наш выходной файл должен быть в формате" elf32-i386 " (объектные файлы на x86). Опция --binary-architecture сообщает objcopy, что выходной файл предназначен для" запуска " на x86. Это необходимо, чтобы ld принял файл для связывания с другими файлами для x86. Можно было бы подумать, что указание выходного формата как "elf32-i386" подразумевает это, но это не так.

теперь, когда у нас есть объектный файл, нам нужно только включить его при запуске компоновщика:

# gcc main.c data.o

когда мы запускаем результат, мы получаем молиться за выход:

# ./a.out
Hello world

конечно, я не рассказал всю историю однако, не показал тебе главного.c. Когда objcopy выполняет вышеуказанное преобразование, он добавляет некоторые символы "компоновщика" в преобразованный объектный файл:

_binary_data_txt_start
_binary_data_txt_end

после связывания эти символы указывают начало и конец внедренного файла. Имена символов формируются путем добавления бинарные и добавить _start и _end к имени файла. Если имя файла содержит символы, которые недопустимы в имени символа, они преобразуются в символы подчеркивания (например, сведения.txt становится data_txt). Если вы получить неразрешенные имена при связывании с помощью этих символов, сделать hexdump-C на объектном файле и посмотреть в конце дампа для имен, которые objcopy выбрал.

код для фактического использования встроенного файла теперь должен быть достаточно очевидным:

#include <stdio.h>

extern char _binary_data_txt_start;
extern char _binary_data_txt_end;

main()
{
    char*  p = &_binary_data_txt_start;

    while ( p != &_binary_data_txt_end ) putchar(*p++);
}

важно отметить, что символы, добавленные в объектный файл, не являются "переменными". Они не содержат никаких данных, скорее, их адрес является их значением. Я заявляю их как тип char, потому что это удобно для этого примера: внедренные данные являются символьными данными. Однако вы можете объявить их как угодно, как int, если данные являются массивом целых чисел, или как struct foo_bar_t, если данные были любым массивом баров foo. Если встроенные данные неоднородны, то char, вероятно, наиболее удобен: возьмите его адрес и наведите указатель на правильный тип по мере прохождения данных.


Если вы хотите контролировать точное имя символа и размещение ресурсов, вы можете использовать (или скрипт) ассемблер GNU (на самом деле не часть gcc) для импорта целых двоичных файлов. Попробуйте это:

сборка (x86 / arm):

    .section .rodata
    .global thing
    .type   thing, @object
    .align  4
thing:
    .incbin "meh.bin"
thing_end:
    .global thing_size
    .type   thing_size, @object
    .align  4
thing_size:
    .int    thing_end - thing

C:

#include <stdio.h>
extern char thing[];
extern unsigned thing_size;
int main() {
  printf("%p %u\n", thing, thing_size);
  return 0;
}

что бы вы ни использовали, вероятно, лучше всего создать скрипт для генерации всех ресурсов и иметь хорошие/единообразные имена символов для всего.


могу ли я добавить, что метод Джона Рипли, вероятно, лучший здесь по одной огромной причине-выравнивание. Если вы делаете стандартную objcopy или " LD-r-B binary-o foo.о Фу.txt", а затем посмотрите на результирующий объект с objdump-x, похоже, что выравнивание для блока установлено в 0. Если вы хотите, чтобы выравнивание было правильным для двоичных данных, отличных от char, я не могу представить, что это хорошо.


читая все сообщения здесь и в Интернете, я сделал вывод, что нет инструмента для ресурсов, который :

1) простота использования в коде.

2) автоматизировано (быть легко включенным в cmake/make).

3) крест-платформы.

Я решил написать инструмент самостоятельно. Код доступен здесь. https://github.com/orex/cpp_rsc

использовать его с cmake очень легко.

вы должны добавить к вашей CMakeLists.txt-файл с таким кодом.

file(DOWNLOAD https://raw.github.com/orex/cpp_rsc/master/cmake/modules/cpp_resource.cmake ${CMAKE_BINARY_DIR}/cmake/modules/cpp_resource.cmake) 

set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}/cmake/modules)

include(cpp_resource)

find_resource_compiler()
add_resource(pt_rsc) #Add target pt_rsc
link_resource_file(pt_rsc FILE <file_name1> VARIABLE <variable_name1> [TEXT]) #Adds resource files
link_resource_file(pt_rsc FILE <file_name2> VARIABLE <variable_name2> [TEXT])

...

#Get file to link and "resource.h" folder
#Unfortunately it is not possible with CMake add custom target in add_executable files list.
get_property(RSC_CPP_FILE TARGET pt_rsc PROPERTY _AR_SRC_FILE)
get_property(RSC_H_DIR TARGET pt_rsc PROPERTY _AR_H_DIR)

add_executable(<your_executable> <your_source_files> ${RSC_CPP_FILE})

реальный пример, используя подход, можно скачать здесь, https://bitbucket.org/orex/periodic_table