在可执行文件中嵌入文件,又名 Hello World,版本 5967

作者:Mitch Frazier

我最近需要在一个可执行文件中嵌入一个文件。因为我使用gcc等命令行工具,而不是使用花哨的 RAD 工具来神奇地完成这一切,所以我一时没有想到如何实现这一点。在网上搜索了一下,发现了一个技巧,本质上就是cat它到可执行文件的末尾,然后根据一堆我不想知道的信息来解密它的位置。似乎应该有一个更好的方法...

而且确实有,那就是objcopy来救援。objcopy将对象文件或可执行文件从一种格式转换为另一种格式。它理解的格式之一是“binary”,它基本上是任何不属于它理解的其他格式的文件。所以你可能已经设想了这个想法:将我们想要嵌入的文件转换为对象文件,然后它可以简单地与我们的其余代码链接在一起。

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

  # cat data.txt
  Hello world
要将其转换为我们可以与程序链接的对象文件,我们只需使用objcopy来生成一个“.o”文件
  # 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
当然,我还没有讲完整的故事,也没有向你展示main.c。当objcopy进行上述转换时,它会将一些“链接器”符号添加到转换后的对象文件中
   _binary_data_txt_start
   _binary_data_txt_end
链接后,这些符号指定嵌入文件的开始和结束位置。符号名称通过前缀_binary_并附加_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如果数据是一个整数数组,或者作为struct foo_bar_t如果数据是 foo bars 的任何数组。如果嵌入的数据不统一,那么char可能是最方便的:获取它的地址并将指针强制转换为正确的类型,以便遍历数据。

Mitch Frazier 是 Emerson Electric Co. 的嵌入式系统程序员。自 2000 年代初以来,Mitch 一直是 Linux Journal 的贡献者和朋友。

加载 Disqus 评论