使用GCC的C/C++:静态地将资源文件添加到可执行文件/库

Atm*_*ons 88 c++ linux gcc shared-libraries elf

有没有人知道如何使用GCC将任何资源文件静态编译到可执行文件或共享库文件中?

例如,我想添加永远不会改变的图像文件(如果他们这样做,我不得不替换文件),并且不希望它们在文件系统中存在.

如果这是可能的(我认为这是因为Visual C++ for Windows也可以这样做),我如何加载存储在自己的二进制文件中的文件?可执行文件是否解析自身,找到文件并从中提取数据?

也许GCC有一个选项,我还没有见过.使用搜索引擎并没有真正吐出正确的东西.

我需要这个用于共享库和普通的ELF可执行文件.

任何帮助表示赞赏

ndi*_*dim 86

更新我已经开始喜欢John Ripley .incbin基于程序集的解决方案提供的控件,现在使用一个变体.

我使用objcopy(GNU binutils)将文件foo-data.bin中的二进制数据链接到可执行文件的数据部分:

objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o
Run Code Online (Sandbox Code Playgroud)

这为您提供了一个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");
Run Code Online (Sandbox Code Playgroud)

所以你可以做类似的事情

for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) {
    transmit_single_byte(*byte);
}
Run Code Online (Sandbox Code Playgroud)

要么

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);
Run Code Online (Sandbox Code Playgroud)

如果您的目标体系结构对存储常量和可变数据的位置有特殊约束,或者您希望将该数据存储在.text段中以使其适合与程序代码相同的内存类型,则可以objcopy更多地使用这些参数.

  • 使用`ld`更容易,因为输出格式隐含在那里,请参阅http://stackoverflow.com/a/4158997/201725. (2认同)

Sim*_*mon 48

您可以使用ld链接器在可执行文件中嵌入二进制文件 例如,如果您有文件,foo.bar则可以将其嵌入可执行文件中,并将以下命令添加到ld

--format=binary foo.bar --format=default
Run Code Online (Sandbox Code Playgroud)

如果您正在调用ld直通gcc,那么你将需要添加-Wl

-Wl,--format=binary -Wl,foo.bar -Wl,--format=default
Run Code Online (Sandbox Code Playgroud)

这里--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");
Run Code Online (Sandbox Code Playgroud)

还有符号命名"_binary_foo_bar_size".我认为这是类型,uintptr_t但我没有检查它.

  • @xtofl,如果`data_end` 将是一个指针,那么编译器会认为在文件内容之后存储了一个指针。类似地,如果您将 `data` 的类型更改为指针,那么您将获得由文件的第一个字节组成的指针,而不是指向其开头的指针。我想是这样。 (2认同)
  • @xtofl-如果要使其成为指针,则使其成为“ const指针”。编译器允许您更改非常量指针的值,如果它是数组,则不允许更改。因此,使用数组语法的类型可能更少。 (2认同)

Fle*_*exo 46

使用imagemagick:

convert file.png data.h
Run Code Online (Sandbox Code Playgroud)

给出类似的东西:

/*
  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, 

....
Run Code Online (Sandbox Code Playgroud)

为了与其他代码兼容,您可以使用它fmemopen来获取"常规" FILE *对象,或者std::stringstream使用iostream.std::stringstream虽然不是很好,你当然可以在任何可以使用迭代器的地方使用指针.

如果你在automake中使用它,请不要忘记适当地设置BUILT_SOURCES.

这样做的好处是:

  1. 你得到文本,所以它可以在版本控制和补丁明智
  2. 它在每个平台上都是可移植的并且定义良好

  • 有时,您有一个可执行文件,它运行在没有文件系统的地方,甚至没有操作系统.或者您的算法需要一些预先计算的表来进行查找.而且我确信在程序中存储数据时会有很多例子. (32认同)
  • 这种转换的使用与`xxd -i infile.bin outfile.h`完全相同 (15认同)
  • 这种方法的一个缺点是,如果你的图像特别大,一些编译器无法处理如此巨大的静态数组; 解决这个问题的方法是,[ndim](http://stackoverflow.com/questions/4864866/cc-with-gcc-statically-add-resource-files-to-executable-library/4865249#4865249)建议,使用`objcopy`将二进制数据直接转换为目标文件; 然而,这很少是一个问题. (4认同)
  • 请记住,在这样的标题中定义它意味着包含它的每个文件都将获得自己的副本.最好在标题中将其声明为extern,然后在cpp中定义它.[实施例这里](http://stackoverflow.com/questions/4391467/declare-array-in-c-header-and-define-it-in-cpp-file) (3认同)
  • Bleahg!这也是我想到的解决方案.为什么有人想做到这一点超出了我的想法.在明确定义的命名空间中存储数据是文件系统的用途. (2认同)

Nor*_*ame 39

您可以将所有资源放入ZIP文件并将其附加到可执行文件的末尾:

g++ foo.c -o foo0
zip -r resources.zip resources/
cat foo0 resources.zip >foo
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为a)大多数可执行图像格式不关心图像后面是否有额外的数据,b)zip文件签名存储在zip文件的末尾.这意味着,此后您的可执行文件是一个常规zip文件(除了您的前端可执行文件,zip可以处理),可以使用libzip打开和读取.

  • 如果我想将foo0和resources.zip加入foo,那么我需要>如果我在cat的命令行上给出两个输入.(因为我不想附加到foo中的内容) (7认同)

Joh*_*ley 35

如果要控制确切的符号名称和资源位置,可以使用(或脚本)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
Run Code Online (Sandbox Code Playgroud)

C:

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

无论你使用什么,最好是制作一个脚本来生成所有资源,并为所有内容提供漂亮/统一的符号名称.

  • 正是我正在寻找的东西。也许您可以验证对于大小不能被 4 整除的文件也可以。看起来 thing_size 将包含额外的填充字节。 (2认同)

Haz*_*zok 35

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

我最近需要在可执行文件中嵌入一个文件.因为我在gcc等人的命令行工作,而不是使用花哨的RAD工具,这使得一切都神奇地发生在我身上并不是很明显如何实现这一点.在网上进行一些搜索发现了一个hack,基本上把它放到了可执行文件的末尾,然后根据一堆我不想知道的信息来解密它.似乎应该有更好的方式......

而且,这是救援的对象.objcopy将对象文件或可执行文件从一种格式转换为另一种格式.它理解的格式之一是"二进制",基本上任何文件都不是它理解的其他格式之一.所以你可能已经想到了这个想法:将我们想要嵌入的文件转换为目标文件,然后它可以简单地与我们的其余代码链接.

假设我们想要嵌入可执行文件中的文件名data.txt:

# cat data.txt
Hello world
Run Code Online (Sandbox Code Playgroud)

要将其转换为我们可以与程序链接的目标文件,我们只需使用objcopy生成".o"文件:

# objcopy --input binary \
--output elf32-i386 \
--binary-architecture i386 data.txt data.o
Run Code Online (Sandbox Code Playgroud)

这告诉objcopy我们的输入文件是"二进制"格式,我们的输出文件应该是"elf32-i386"格式(x86上的目标文件).--binary-architecture选项告诉objcopy输出文件意味着在x86上"运行".这是必需的,以便ld将接受该文件以链接x86的其他文件.有人会认为将输出格式指定为"elf32-i386"会暗示这一点,但事实并非如此.

现在我们有了一个目标文件,我们只需要在运行链接器时包含它:

# gcc main.c data.o
Run Code Online (Sandbox Code Playgroud)

当我们运行结果时,我们得到了输出的祈祷:

# ./a.out
Hello world
Run Code Online (Sandbox Code Playgroud)

当然,我还没有讲完整个故事,也没有向你展示main.c. 当objcopy执行上述转换时,它会向转换后的目标文件添加一些"链接器"符号:

_binary_data_txt_start
_binary_data_txt_end
Run Code Online (Sandbox Code Playgroud)

链接后,这些符号指定嵌入文件的开头和结尾.符号名称是通过预先添加二进制文件并将_start或_end附加到文件名来形成的.如果文件名包含在符号名称中无效的任何字符,则它们将转换为下划线(例如data.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++);
}
Run Code Online (Sandbox Code Playgroud)

需要注意的一个重要而微妙的事情是添加到目标文件中的符号不​​是"变量".它们不包含任何数据,而是它们的地址是它们的价值.我将它们声明为char类型,因为它对此示例很方便:嵌入数据是字符数据.但是,您可以将它们声明为任何内容,如果数据是整数数组则为int,如果数据是任何foo条数组,则为struct foo_bar_t.如果嵌入数据不一致,则char可能是最方便的:获取其地址并在遍历数据时将指针强制转换为正确的类型.


小智 5

阅读这里和互联网上的所有帖子我得出的结论是没有资源工具,即:

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})
Run Code Online (Sandbox Code Playgroud)

使用该方法的真实示例可以在这里下载, https://bitbucket.org/orex/periodic_table