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


94

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

例如,我想要添加永不更改的图像文件(如果更改,则无论如何我都必须替换该文件),并且不希望它们位于文件系统中。

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

也许我还没有看到GCC的选择。使用搜索引擎并没有真正吐出正确的东西。

我需要此功能才能用于共享库和普通的ELF可执行文件。

任何帮助表示赞赏



问题blueberryfields指向的objcopy链接也是一个很好的通用解决方案
Flexo

@blueberryfields:很抱歉重复。你是对的。通常,我会为重复投票而关闭。但是因为他们都发布了如此好的答案,所以我只接受一个。
Atmocreations '02

我可以补充一下,约翰·里普利(John Ripley)的方法可能是最好的方法,原因之一是对齐。如果执行标准的objcopy或“ ld -r -b二进制-o foo.o foo.txt”,然后使用objdump -x查看生成的对象,则看起来该块的对齐方式设置为0。对齐以正确处理char以外的二进制数据,我无法想象这是一件好事。
carveone 2012年

Answers:


49

使用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“常规” FILE *对象,也std::stringstream可以使用iostreamstd::stringstream但这并不是很好,您当然可以在可以使用迭代器的任何地方使用指针。

如果您将此参数与automake一起使用,请不要忘记适当地设置BUILT_SOURCES

这样做的好处是:

  1. 您会收到文本,因此可以在版本控制中进行合理的修补
  2. 它是便携式的,并且在每个平台上都定义明确

2
le!这也是我想到的解决方案。为什么有人要这样做,这超出了我。在定义良好的名称空间中存储数据就是文件系统的目的。
2011年

35
有时,您有一个可执行文件,该文件在没有文件系统甚至没有操作系统的情况下运行。或者您的算法需要一些预先计算的表才能进行查找。我相信有很多更多的情况下,当在程序存储数据使得很多的意义。
ndim 2011年

15
转换的使用与xxd -i infile.bin outfile.h
greyfade 2011年

5
这种方法的一个缺点是,如果您的图像特别大,某些编译器将无法处理如此庞大的静态数组。正如ndim所建议的,解决该问题的方法是objcopy将二进制数据直接转换为目标文件。但是,这很少引起关注。
亚当·罗森菲尔德

3
请记住,像这样在标头中定义它意味着包含它的每个文件都会得到自己的副本。最好在标头中将其声明为extern,然后在cpp中对其进行定义。 这里的例子
Nicholas Smith

90

更新我逐渐喜欢John Ripley的.incbin基于程序集的解决方案所提供的控件,现在在其上使用一个变体。

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

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更多参数。


好主意!就我而言,它不是很有用。但这确实是我要放入摘要集的内容。感谢您分享!
Atmocreations

2
使用它有点容易,ld因为其中隐含了输出格式,请参阅stackoverflow.com/a/4158997/201725
Jan Hudec 2014年

52

您可以使用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但我没有检查。


非常有趣的评论。感谢您分享!
Atmocreations

1
好一个!只是一个问题:为什么是data_end数组而不是指针?(还是这个惯用的C?)
xtofl 2012年

2
@xtofl,如果data_end将是一个指针,则编译器将认为在文件内容之后存储了一个指针。同样,如果将类型更改data为指针,则将获得由文件的第一个字节组成的指针,而不是指向其开头的指针。我认同。
西蒙(Simon)

1
+1:您的答案允许我将Java类加载器和Jar嵌入到exe文件中,以构建自定义Java启动器
Aubin

2
@xtofl-如果要使其成为指针,则使其成为const pointer。编译器允许您更改非常量指针的值,如果它是数组,则不允许更改。因此,使用数组语法的类型可能更少。
杰西·奇斯霍尔姆

40

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

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

之所以可行,是因为a)大多数可执行映像格式都不关心映像后面是否有多余数据,并且b)zip将文件签名存储在zip文件的末尾。这意味着,可执行文件是此后的常规zip文件(前期可执行文件除外,该zip可以处理),可以使用libzip打开和读取该可执行文件。


7
如果我想将foo0和resources.zip连接到foo中,那么如果我在cat的命令行上都输入了这两个输入,则需要>。(因为我不想附加到foo中已经存在的内容)
Nordic Mainframe

1
啊,是我的错。第一次阅读时,我没有在名称中正确找到0
Flexo

这很聪明。+1。
Linuxios

1
+1 很棒
mvp

这将产生一个无效的二进制文件(至少在Mac和Linux上是这样),无法通过这类工具进行处理install_name_tool。除此之外,该二进制文件仍可作为可执行文件使用。
安迪·李

36

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

最近,我需要将文件嵌入可执行文件中。由于我是在gcc等人的命令行下工作,而不是使用一个花哨的RAD工具来使这一切神奇地发生,因此对我而言,如何立即实现这一点并不立刻显而易见。在网上进行了一些搜索,发现黑客实际上将其捕获到可执行文件的末尾,然后根据一堆我不想知道的信息来解密它的位置。似乎应该有更好的方法...

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

假设我们有一个文件名data.txt想要嵌入到可执行文件中:

# cat data.txt
Hello world

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

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

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

现在我们有了目标文件,只需要在运行链接器时将其包括在内:

# gcc main.c data.o

当我们运行结果时,我们祈求输出:

# ./a.out
Hello world

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

_binary_data_txt_start
_binary_data_txt_end

链接后,这些符号指定嵌入式文件的开始和结束。符号名称由前缀组成二进制文件名加上_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++);
}

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


36

如果要控制确切的符号名称和资源位置,则可以使用(或编写脚本)GNU汇编器(不是gcc的一部分)来导入整个二进制文件。试试这个:

组装(x86 /臂):

    .section .rodata

    .global thing
    .type   thing, @object
    .balign 4
thing:
    .incbin "meh.bin"
thing_end:

    .global thing_size
    .type   thing_size, @object
    .balign 4
thing_size:
    .int    thing_end - thing

C:

#include <stdio.h>

extern const char thing[];
extern const unsigned thing_size;

int main() {
  printf("%p %u\n", thing, thing_size);
  return 0;
}

无论使用什么,最好编写一个脚本来生成所有资源,并对所有内容使用漂亮的/统一的符号名称。

根据您的数据和系统特性,您可能需要使用不同的对齐方式值(最好是带有.balign可移植性),或者将thing_size,或使用不同的元素类型作为thing[]数组使用不同大小的整数类型。


感谢分享!确实看起来很有趣,但是这次不是我想要的=)问候
Atmocreations

1
正是我想要的。也许您可以验证大小不为4的文件也可以使用。看起来something_size将包含额外的填充字节。
Pavel P

如果我希望事物成为本地符号怎么办?我可能可以将编译器输出与自己的程序集一起使用,但是有更好的方法吗?
user877329 2014年

作为记录:我的编辑解决了@Pavel指出的额外填充字节的问题。
ndim '17

4

在这里和在互联网上阅读所有文章后,我得出了一个结论,即没有资源工具:

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下载


我认为您的答案需要更好的解释才能对更多人有用。
kyb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.