从二进制中提取数组数据的正确方法是什么?

3

有一种经典的方法可以将资源文件作为C语言数组嵌入二进制文件中,这样我们就可以将一些外部资源文件(如 .jpeg.txt 文件)存储到一个二进制文件中。

例如,在头文件中我们可以定义一个数组:

const unsigned char xd_data[] = {
    77,90,144,0,3,0,0,0,4,0,0,0,255,255,0,0,184,0,0,0,0,0,0,0,64,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,
    0,14,31,186,14,0,180,9,205,33,184,1,76,205,33,84,104,105,115,32,112,114,
    111,103,114,97,109,32,99,97,110,110,111,116,32,98,101,32,114,117,110,
    32,105,110,32,68,79,83,32,109,111,100,101,46,13,13,10,36,0,0,0,0,0,0,
    0,66,163,223,218,6,194,177,137,6,194,177,137,6,194,177,137,105,221,187,
    137,13,194,177,137,133,222,191,137,3,194,177,137,105,221,181,137,4,194,
    177,137,136,202,238,137,4,194,177,137,6,194,176,137,73,194,177,137,133,
    202,236,137,13,194,177,137,48,228,187,137,11,194,177,137,193,196,183,
    137,7,194,177,137,82,105,99,104,6,194,177,137,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,80,69,0,0,76,1,4,0,65,162,32,86,0,0,0,0,0,0,0,
    0,224,0,47,1,11,1,6,0,0,100,0,0,0,74,0,0,0,0,0,0,228,113,0,0,0,16,0,0,
    0,128,0,0,0,0,64,0,0,16,0,0,0,2,0,0,4,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,
    224,0,0,0,4,0,0,0,0,0,0,2,0,0,0,0,0,16,0,0,16,0,0,0,0,16,0,0,16,0,0,0,
    0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,124,140,0,0,140,0,0,0,0,208,0,0,0,16,0
};

这段代码包含了资源文件的内容,会被编译进最终的二进制文件中。

网络上有很多关于这个老技巧的工具和教程,例如:http://www.rowleydownload.co.uk/arm/documentation/index.htm?http://www.rowleydownload.co.uk/arm/documentation/embed.htmhttps://www.fourmilab.ch/xd/http://gareus.org/wiki/embedding_resources_in_executables#c_include_method

然而,大多数页面似乎都在谈论如何使用C语言风格的数组来嵌入数据到二进制文件中。

我的问题是,如何正确找到已编译二进制文件中资源文件的起始地址以便提取它们?也就是说,我怎样才能找到编译后二进制文件中 xd_data 的起始地址?


5
对于 include 方法,在你的示例中只需访问该变量 xd_data。而对于二进制链接选项,第二个参考文献中有一个段落开头为:“这个数据段可以通过简单地使用以下方法在 C 代码中引用:...”。那对于某些原因,这种方式是否无法正常工作或不适合您呢? - kaylum
你是在询问如何编写一个程序来检查已编译的二进制文件并提取此数据吗?看起来这里其他评论者讨论的是如何让编译生成的二进制代码来访问这些数据。 - user2357112
2
您可以使用Binutils工具。例如readelf来列出节、符号等,以获取符号地址。而objdump或objcopy则可提取您感兴趣的节的二进制转储。最好澄清您的问题,以确定您是想以编程方式(例如使用C语言)进行操作,还是使用现有的命令行工具是可以接受的。 - kaylum
@kaylum 谢谢。正如您所提到的,最好的方法是让编译后的二进制文件在开始执行时生成这些文件,而不是使用任何外部工具。 - stanleyli
@kaylum 当我提出问题时,我忘记了编译后的二进制文件可以自己生成这些资源文件,所以我认为我们必须依靠一些外部工具或脚本来检查二进制文件。但是在我意识到这一点之后,我认为这是获取这些资源文件最直接的方法。无论如何,谢谢你! - stanleyli
显示剩余2条评论
2个回答

1
如果您的意思是以编程方式找到数据块开始的文件中的字节地址,就像objdump一样,那么您可以使用二进制文件描述符库(BFD),请参见这里这里

0

如果您存储了数据,例如图像,并且想要加载它(用于打印或其他任何目的),那么如果您有一个从内存中加载它的函数(库),例如void loadResImage(void * mem);,只需执行loadResImage(xd_data)即可。如果没有这样的函数,但您有一个从文件中加载它的函数,在这种情况下,请将其保存到临时文件中,例如:

int fd=open("tmpfile");
int ret=write(fd,xd_data, sizeof(xd_data));
close(fd);
loadImageFile("tmpfile");

但是如果你想在程序之外访问数据(例如十六进制编辑器或其他程序),那么你必须添加一个起始标记和可选的结束标记或数据大小。例如:

const unsigned char xd_data[]={
  ...
'M','A','G','I','C'};

在上面的例子中,数据的结尾是已知的,你只需要搜索找到它。同样地,试着寻找一种合适的方法来存储数据的大小,但要注意编译器的优化。

我认为OP想要访问与应用程序二进制文件共存的资源,就像在Windows上常见的那样。 - sehe
@sehe,我不这么认为,因为像Windows一样实现资源需要一个知道在编译时如何以及在哪里(通常是为此保留的部分)保存资源的外部程序,就像Windows的rc一样。 - milevyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接