Встраивание ресурсов в исполняемый файл с помощью GCC

Я ищу способ легко встроить любые внешние двоичные данные в приложение C / C ++, скомпилированное GCC.

Хороший пример того, что я хотел бы сделать, - это обработка кода шейдера - я могу просто сохранить его в исходных файлах, таких как const char* shader = "source here";, но это крайне непрактично.

Я бы хотел, чтобы компилятор сделал это за меня: после компиляции (этап связывания) прочтите файл «foo.bar» и свяжите его содержимое с моей программой, чтобы я мог получить доступ к содержимому как двоичные данные из код.

Может быть полезно для небольших приложений, которые я хотел бы распространять как один файл .exe.

Поддерживает ли GCC что-то подобное?


person Kos    schedule 11.11.2010    source источник
comment
Возможный дубликат C / C ++ с GCC: статическое добавление ресурса файлы в исполняемый файл / библиотеку   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 19.11.2018


Ответы (4)


Есть несколько возможностей:


Обновление: вот более полный пример того, как использовать данные, привязанные к исполняемому файлу с помощью ld -r -b binary:

#include <stdio.h>

// a file named foo.bar with some example text is 'imported' into 
// an object file using the following command:
//
//      ld -r -b binary -o foo.bar.o foo.bar
//
// That creates an bject file named "foo.bar.o" with the following 
// symbols:
//
//      _binary_foo_bar_start
//      _binary_foo_bar_end
//      _binary_foo_bar_size
//
// Note that the symbols are addresses (so for example, to get the 
// size value, you have to get the address of the _binary_foo_bar_size
// symbol).
//
// In my example, foo.bar is a simple text file, and this program will
// dump the contents of that file which has been linked in by specifying
// foo.bar.o as an object file input to the linker when the progrma is built

extern char _binary_foo_bar_start[];
extern char _binary_foo_bar_end[];

int main(void)
{
    printf( "address of start: %p\n", &_binary_foo_bar_start);
    printf( "address of end: %p\n", &_binary_foo_bar_end);

    for (char* p = _binary_foo_bar_start; p != _binary_foo_bar_end; ++p) {
        putchar( *p);
    }

    return 0;
}

Обновление 2 - Получение размера ресурса: я не мог правильно прочитать _binary_foo_bar_size. Во время выполнения gdb показывает мне правильный размер текстового ресурса, используя display (unsigned int)&_binary_foo_bar_size. Но присвоение этого переменной всегда давало неверное значение. Я мог бы решить эту проблему следующим образом:

unsigned int iSize =  (unsigned int)(&_binary_foo_bar_end - &_binary_foo_bar_start)

Это обходной путь, но он работает хорошо и не слишком уродлив.

person Michael Burr    schedule 11.11.2010
comment
Шейдеры не являются BLOB. Это обычный текст. - person BЈовић; 11.11.2010
comment
@VJo: тогда относитесь к blob как к тексту. Возможно, вам придется немного поработать, чтобы убедиться, что в конце текста есть '\0', если вам нужно, чтобы он так заканчивался. Возможно, стоит поэкспериментировать. - person Michael Burr; 11.11.2010
comment
Спасибо, Майкл; похоже на то, что мне нужно, но я получаю objdump: foo.o: File format not recognized ошибку и похожую при попытке связать этот объект с моим источником. Какие-нибудь намеки? Я нахожусь в Windows, использую tdm-mingw 4.5.1, и мой ld -v дает GNU ld (GNU Binutils) 2.20.51.20100319. Я могу вернуться к вашему второму предложению, так что с этого момента это просто мое любопытство. :) - person Kos; 12.11.2010
comment
@Kos: Я опубликовал пример кода, который компилируется и запускается в моей системе. Я использую дистрибутив MinGW из nuwen.net/mingw.html, в котором есть gcc (GCC) 4.5.1, GNU ld (GNU Binutils) 2.20.1.20100303 и GNU objdump (GNU Binutils) 2.20.1.20100303. В вашей системе objdump -i что-нибудь говорит о формате binary? - person Michael Burr; 12.11.2010
comment
К сожалению, но даже после редактирования ваше решение все еще не подходит, потому что шейдер - это не блок двоичных данных, а текст. - person BЈовић; 12.11.2010
comment
@VJo: текст является двоичным. Все на компьютере двоичное. - person MSalters; 12.11.2010
comment
@Michael, вы можете взглянуть на мою среду здесь: nopaste.voric.com/paste .php? f = me4dr3. Если я использую версию ld от nuwen для создания файла foo.bar.o (только это - я могу использовать сборку tdm для остальных), тогда все работает нормально. Я нахожу несколько удивительным, что на самом деле мы получаем здесь разные результаты. См .: nopaste.voric.com/paste.php?f=95zizg. - person Kos; 12.11.2010
comment
@MSalters re: текст является двоичным. Да, но ... в тексте EOL может обрабатываться по-разному в разных системах. Явный вызов этого двоичного кода предотвращает такие недостатки. - person Jesse Chisholm; 20.05.2014
comment
Ваш ответ и этот здесь дополняют друг друга, поэтому я возвращаюсь к нему, чтобы помочь людям получить больше примеров. - person DrBeco; 11.07.2015
comment
@MSalters Кроме того, двоичный файл даже не является двоичным. Обычно вы выделяете часть памяти либо под код, либо под данные (см .: VirtualAlloc и mmap); ограничивая доступ, вы можете защитить приложения. Также из-за этого я сомневаюсь, что это решение будет работать во всех случаях; в основном он компилирует blob как блок кода, а затем код использует его как блок данных ... iirc, который должен работать только в том случае, если блок исполняемого файла / DLL помечен как .text, который в основном (всегда) отмечает его как блок данных. - person atlaste; 11.09.2015
comment
@atlaste: Вы описываете различие между записываемым (данные) и исполняемым (код). Данные только для чтения не требуют ни одного метода. - person MSalters; 11.09.2015
comment
Исполняемые файлы @MSalters сопоставляются и затем выполняются. Если вы присмотритесь, вы увидите, что EXECUTE, EXECUTE_READ и READONLY - разные флаги. Если раздел в exe / dll помечен как «код» (EXECUTE), нет причин отмечать его как «только для чтения» - что и используется здесь (и наоборот). Причина, по которой это работает, заключается в том, что он помечен как данные «.text», что соответствует правильным флагам защиты. Помещение его в '.code' должно привести к ошибкам. Ссылка для флагов: msdn.microsoft .com / ru-ru / library / windows / desktop /. Linux mmap может делать аналогичные вещи с PROT_READ и PROT_EXEC. - person atlaste; 11.09.2015
comment
Можете ли вы сказать ld, какое имя символа нужно сгенерировать для данных? - person Calmarius; 20.07.2016
comment
Разве это не должно быть &_binary_foo_bar_end - &_binary_foo_bar_start + 1? Количество элементов в диапазоне [a, b] равно b - a + 1. - person jww; 30.08.2018
comment
@Calmarius задали вопрос: stackoverflow.com/questions/19169039/ кажется, что вы не можете, что делает этот подход непригодным для использования во многих случаях. - person Ciro Santilli 新疆再教育营六四事件ۍ 16.11.2018
comment
Выполнение этого с x86_64-w64-mingw32 на хосте Linux, & _binary_foo_bar_size было правильным при запуске моего исполняемого файла через вино, но неверным, когда тот же самый исполняемый файл был запущен в Windows 7. end - start всегда работает. - person repkap11; 13.04.2019
comment
@jww нет, конец - один за концом, вычитание работает как есть - person K. Brafford; 26.09.2020

Помимо уже упомянутых предложений, в Linux вы можете использовать инструмент шестнадцатеричного дампа xxd, который имеет функцию создания файла заголовка C:

xxd -i mybinary > myheader.h
person Riot    schedule 06.02.2014
comment
Я считаю это решение лучшим. Это также кроссплатформенная и кросс-компиляторная поддержка. - person Behrouz.M; 27.07.2015
comment
Это верно, но у него есть один недостаток - итоговые файлы заголовков намного больше, чем исходный двоичный файл. Это не влияет на окончательный результат компиляции, но может быть нежелательным в процессе сборки. - person Riot; 28.07.2015
comment
эту проблему можно решить с помощью предварительно скомпилированного заголовка. - person Behrouz.M; 28.07.2015

Для этой задачи можно использовать .incbin GAS директиву. Вот полностью бесплатная лицензированная библиотека, которая обтекает его:

https://github.com/graphitemaster/incbin

Подведем итоги. Метод incbin такой. У вас есть файл сборки thing.s, который вы компилируете с помощью gcc -c thing.s

      .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 или cpp вы можете ссылаться на него с помощью:

extern const char thing[];
extern const char* thing_end;
extern int thing_size;

Затем вы связываете полученный .o с остальными модулями компиляции. Благодарим @John Ripley с его ответом здесь: C / C ++ с GCC: статическое добавление файлов ресурсов в исполняемый файл / библиотеку

Но вышеперечисленное не так удобно, как то, что вам может дать incbin. Чтобы выполнить вышеуказанное с помощью incbin, вам не нужно писать ассемблер. Достаточно всего следующего:

#include "incbin.h"

INCBIN(thing, "meh.bin");

int main(int argc, char* argv[])
{
    // Now use thing
    printf("thing=%p\n", gThingData);
    printf("thing len=%d\n", gThingSize);   
}
person hookenz    schedule 29.03.2016
comment
Мне нравится этот метод, потому что он позволяет контролировать имя символа. - person Ciro Santilli 新疆再教育营六四事件ۍ 16.11.2018

Вы можете сделать это в файле заголовка:

#ifndef SHADER_SRC_HPP
#define SHADER_SRC_HPP
const char* shader= "

//source

";
#endif

и просто включите это.

Другой способ - прочитать файл шейдера.

person BЈовић    schedule 11.11.2010
comment
Я думаю, Кос хочет иметь возможность поддерживать исходный код шейдера, не беспокоясь об экранировании специальных символов (среди других возможных проблем). - person Michael Burr; 11.11.2010
comment
@Michael Очевидно, ты никогда не использовал ни одного шейдера. - person BЈовић; 12.11.2010
comment
@VJo: нет - никогда не использовал шейдер. Я подходил к вопросу как встраивание в программу произвольных данных, находящихся во внешних файлах. Я определенно могу согласиться с тем, что это могло бы быть намного лучшим решением, в частности, для шейдеров. - person Michael Burr; 12.11.2010
comment
Файл, который определяет (а не объявляет) глобальную переменную, должен быть не файлом заголовка, а исходным модулем. А ваш тип крайне неэффективен. Вместо этого сделайте const char shader[] = "source";. - person R.. GitHub STOP HELPING ICE; 12.11.2010
comment
@R лучший способ - объявить внешнюю переменную в заголовке и определить ее в исходном файле. Это также легко поддерживать - person BЈовић; 12.11.2010
comment
Кроме того, я считаю, что C ++ не позволяет использовать многострочные строковые литералы иначе, чем открывать и закрывать "" кавычки в каждой строке отдельно или иметь обратную косую черту в конце каждой строки. Не говоря уже о других преимуществах наличия шейдера в виде отдельного файла во время разработки (по крайней мере, раскраска синтаксиса?). - person Kos; 12.11.2010
comment
@Kos Вы можете поместить кавычки в начало и конец каждой строки, но это вряд ли проще, но, возможно, более понятно. По крайней мере, это более понятно людям, знакомым с конкатенацией строковых литералов времени компиляции. - person Sqeaky; 02.09.2015
comment
Начиная с C ++ 11 вы можете использовать необработанный строковый литерал, он выглядит как R"*( ... multiline text ... )*". Вы можете использовать другой разделитель вместо *. - person Zeno Rogue; 17.03.2021