C / C ++与GCC:静态添加资源文件到可执行文件/库
有没有人有一个想法如何使用GCC静态编译任何资源文件到可执行文件或共享库文件?
例如,我想添加永不改变的图像文件(如果他们这样做,我将不得不更换文件),并不希望他们躺在文件系统中。
如果这是可能的(我想这是因为Windows的Visual C ++也可以这样做),我怎样才能加载存储在自己的二进制文件? 可执行文件是否parsing自己,find文件并从中提取数据?
也许有一个GCC的选项,我还没有看到。 使用search引擎并没有真正吐出正确的东西。
我需要这个工作共享库和普通的ELF可执行文件。
任何帮助表示赞赏
随着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
来创build一个iostream
。 std::stringstream
不是很好,但你可以在任何地方使用一个指针来使用迭代器。
如果你在automake中使用这个,不要忘了适当地设置BUILT_SOURCES 。
这样做的好处是:
- 你得到的文本,所以它可以在版本控制和补丁明智
- 它是便携式的,并在每个平台上明确定义
更新我已经越来越喜欢控制.incbin
基于汇编.incbin
的解决scheme提供,现在使用一个变种。
我使用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
段中以使其适合与程序代码相同的内存types,则可以使用objcopy
参数多一点。
您可以使用ld
链接器将二进制文件embedded到可执行文件中。 例如,如果您有文件foo.bar
那么您可以将其embedded到可执行文件中,将以下命令添加到ld
--format=binary foo.bar --format=default
如果你通过gcc
调用ld
那么你将需要添加-Wl
-Wl,--format=binary -Wl,foo.bar -Wl,--format=default
这里--format=binary
告诉链接器下面的文件是二进制文件,而--format=default
切换回默认的input格式(如果你在foo.bar
之后指定其他的input文件,这是有用的)。
然后你可以从代码中访问你的文件的内容:
extern uint8_t data[] asm("_binary_foo_bar_start"); extern uint8_t data_end[] asm("_binary_foo_bar_end");
还有一个名为"_binary_foo_bar_size"
符号。 我认为这是typesuintptr_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 :
我最近有需要在可执行文件中embedded一个文件。 由于我在gcc等人的命令行工作,而不是一个奇特的RAD工具,使所有这一切都发生奇迹,我不是很明显如何做到这一点。 在网上search的一点发现一个黑客基本上把它放到可执行文件的末尾,然后根据我不想知道的一堆信息解密它的位置。 看起来应该是一个更好的方法…
而且,这是救援的目标。 objcopy将目标文件或可执行文件从一种格式转换为另一种格式。 它理解的格式之一是“二进制”,这基本上是任何文件,不是它所了解的其他格式之一。 所以你可能已经想到了这个想法:把我们想要embedded的文件转换成一个目标文件,然后它可以简单地与我们其他的代码链接起来。
假设我们有一个文件名data.txt,我们想要embedded到我们的可执行文件中:
# cat data.txt Hello world
为了将它转换成我们可以链接到我们的程序的目标文件,我们只需使用objcopy生成一个“.o”文件:
# objcopy --input binary \ --output elf32-i386 \ --binary-architecture i386 data.txt data.o
这告诉objcopy我们的input文件是“二进制”格式,我们的输出文件应该是“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
链接后,这些符号指定embedded文件的开始和结束。 符号名称由二进制前缀形成,并将_start或_end附加到文件名中。 如果文件名包含符号名称中无效的任何字符,则它们将转换为下划线(例如,data.txt变为data_txt)。 如果在使用这些符号进行链接时遇到未parsing的名称,请在目标文件上执行hexdump -C并查看转储的结尾,以查找objcopyselect的名称。
实际使用embedded文件的代码现在应该是相当明显的:
#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++); }
要注意的一个重要和微妙的事情是,添加到目标文件的符号不是“variables”。 他们不包含任何数据,而是他们的地址是他们的价值。 我把它们声明为chartypes,因为这个例子很方便:embedded的数据是字符数据。 但是,可以将它们声明为任何内容,例如,如果数据是整数数组,则为int;如果数据是任何foo数组的数组,则为struct foo_bar_t。 如果embedded的数据不统一,那么char可能是最方便的:在遍历数据时,获取其地址并将指针转换为正确的types。
如果你想控制确切的符号名称和资源的位置,你可以使用(或脚本)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; }
无论你使用什么,最好的做法是创build一个脚本来生成所有的资源,并且为每件事物devise好的/统一的符号名称。
我可以补充说,约翰·里普利的方法可能是一个巨大的原因 – alignment。 如果你做一个标准的objcopy或者“ld -r -b binary -o foo.o foo.txt”,然后用objdump -x来查看结果对象,那么看起来块的alignment方式设置为0.如果你想除了字符以外的二进制数据alignment是正确的,我无法想象这是一件好事。
阅读这里和互联网上的所有post,我得出的结论是没有资源的工具,即:
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