当前位置: 首页 > GNU/Linux系统 > 正文

将图片嵌入程序文件的一点研究

背景:

重读《程序员的自我修养——链接、装载与库》,里面第3章主要讲目标文件。同时讲到如何将一些二进制文件作为目标文件的一个段(详细的请参考此书)。

像图片、音乐文件其实也是二进制文件(作为初级程序的我,还没有达到将一切看成二进制的境界)。本文就以此展开了一些研究,顺便复习一下binutils工具以及gdb的使用。

另外,也将这个知识应用到我的ARM开发板上,即是在原来基本上,添加图片的显示,当然,图片已经放到可执行程序中了,无须额外的图片文件。

由于不可避免地粘贴程序编译、程序运行的结果,在此作一些约定:

图片文件名称为logo.jpg,将其转换成目录文件的名称是logo_386.o,测试文件为logo_test.c。

本文有代码有真相,如果想有图有真相的话,移步到这里:xxxx。

1、将图片文件转换成目标文件

使用objcopy工具即可将图片文件转换成目标文件,示例如下:

$ objcopy -I binary -O elf32-i386 -B i386 logo.jpg logo_386.o

这里是针对386平台,毕竟这个平台比较方便(这是废话,请无视)。其中-I选项指定输入文件的格式,这里是二进制;-O指定输出文件的格式,这里应该是指elf文件类型;-B是指定目标文件的架构,这里是i386平台,关于i386,Linux是比较笼统的说法,具体参考相关资料(真实中,笔者使用的Linux是运行在Intel i3 CPU的虚拟机中)。

用objdump看一下logo_386.o有什么东西:

$ objdump -ht logo_386.ologo_386.o:     file format elf32-i386Sections:Idx Name          Size      VMA       LMA       File off  Algn

0 .data         000011e8  00000000  00000000  00000034  2**0

CONTENTS, ALLOC, LOAD, DATA

SYMBOL TABLE:

00000000 l    d  .data  00000000 .data

00000000 g       .data  00000000 _binary_logo_jpg_start

000011e8 g       .data  00000000 _binary_logo_jpg_end

000011e8 g       *ABS*  00000000 _binary_logo_jpg_size

-ht选项表示输入段的头部和目标文件的符号,可以看到,最后三行输出了三个符号,它们分别表示图片文件在内存中的起始地址、结束地址和大小,我们可以在程序中直接声明并使用它们(来自《程序员的自我修养》)。不过,经过笔者的测试,发现这些讲述有些不正确,此是后话,暂且不提。

这三个符号是会变化的,它的命名格式是:_binary_*_start/end/size,其中,*是图片的文件及后缀名。如果图片是foo.bmp,那么,这三个符号就是_binary_foo_bmp_start/end/size了。如果使用十六进制查看logo_386.o,便会发现它比logo.jpg多了一些信息,这里将它们称为头部和尾部信息(头部和尾部是这里的描述,不能登大雅之堂)。头部是一个ELF头部结构体,尾部笔者就不知道了。

2、测试程序

下面是测试程序:

/******************************************************************

将图片资源嵌入到程序文件中

objcopy -I binary -O elf32-i386 -B i386 logo.jpg logo_386.o

gcc -g logo_test.c logo_386.o

$ ./a.out

hello, elf head: 52

0x8049674 0x804a85c 4584

******************************************************************/

#include <stdio.h>

#include <elf.h>

extern _binary_logo_jpg_start;

extern _binary_logo_jpg_end;

extern _binary_logo_jpg_size;

int main(void)

{

printf(“hello, elf head: %dn”, sizeof(Elf32_Ehdr));

printf(“%p %p %pn”, &_binary_logo_jpg_start, &_binary_logo_jpg_end,  &_binary_logo_jpg_size);

return 0;

}

注意:程序中没有给出_binary_logo_jpg_start的类型,因为笔者想不出它们几个到底是什么。如果使用-Wall来编译,得到下面的警告:

logo_test.c:14: warning: type defaults to ‘int’ in declaration of ‘_binary_logo_jpg_start’logo_test.c:15: warning: type defaults to ‘int’ in declaration of ‘_binary_logo_jpg_end’logo_test.c:16: warning: type defaults to ‘int’ in declaration of ‘_binary_logo_jpg_size’

这说明,如果代码没有明确指定类型,编译器默认int类型。

在代码中,“符号”一词指的东西比较多,指针是符号、数组名是符号,函数名称是符号、变量名称是符号,似乎一切均符号(u-boot将“符号”用得淋漓尽致,可以参考u-boot启动部分的C代码及汇编代码,这部分的C代码就使用到了汇编中的“符号”)。

程序运行结果如下:

$ ./a.outhello, elf head: 520×8049674 0x804a85c 4584

程序特意输出elf文件头部信息的结构体的大小,结果是52字节,而生成的目标文件中,图片内容位于0x34字节偏移,前面有0x34字节,0x34正是十进制的52。关于这个结构体,在此不展开。由于笔者曾经在一段时间中几乎天天看jpg文件的内容(主要是关于jpeg、mjpeg、avi这方面的),知道jpg文件以“FF D8”开始,在“FF D9”结束,目标文件的“FF D8”就在0x34处。

Linux下用hexdump查看开始部分内容(由于网页关系,可能不对齐):

$ hexdump -C logo_386.o | head -n 400000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF…………|00000010  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |…………….|

00000020  40 12 00 00 00 00 00 00  34 00 00 00 00 00 28 00  |@…….4…..(.|

00000030  05 00 02 00 ff d8 ff e1  00 e6 45 78 69 66 00 00  |……….Exif..|

3、调试程序

我们用gdb分别看看那三个符号:

先是_binary_logo_jpg_start

(gdb) p/x _binary_logo_jpg_start$1 = 0xe1ffd8ff(gdb) p/x &_binary_logo_jpg_start$5 = 0x8049674

可以看到,&_binary_logo_jpg_start的值为0x8049674,这表明图片内容位于0x8049674地址,按笔者对ELF文件的认识,它应该位于.text段,就是说,图片已经属于可执行文件的一部分了。_binary_logo_jpg_start值为0xe1ffd8ff,我们知道,386是小端模式,反过来一个字节一个字节看,它是ff d8 ff e1,这不是图片内容是什么?不信,我们再看一下那个地址的8个字节:

(gdb) x/8b &_binary_logo_jpg_start0x8049674 <_binary_logo_jpg_start>:     0xff    0xd8    0xff    0xe1    0x00   0xe6     0x45    0x78

_binary_logo_jpg_start的值正是图片内容的前4个字节。

再看看_binary_logo_jpg_end,

(gdb) p _binary_logo_jpg_end$1 = 0(gdb) p &_binary_logo_jpg_end

$2 = (int *) 0x804a898

(gdb) x/x 0x804a898

0x804a898 <completed.5731>:     0x00000000

(gdb) x/8b 0x804a898

0x804a898 <completed.5731>:     0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

它的值为0,地址为0x804a898,我们再看看这个地址稍微前面的内容,就以0x804a890地址为例:

(gdb) x/12b 0x804a8900x804a890 <_binary_logo_jpg_start+4576>:        0x8a    0x00    0xa2    0x8a    0x28    0x0f    0xff    0xd90x804a898 <completed.5731>:     0x00    0x00    0x00    0x00

我们看到几个关键的值,一是4576,它很接近图片文件的大小,另一个是0xff 0xd9,它是图片结束的标志,而0xd9,位于离_binary_logo_jpg_start地址4584处,图片大小正是4584字节。

再看看_binary_logo_jpg_size:

(gdb) p _binary_logo_jpg_sizeCannot access memory at address 0x11e8(gdb) p &_binary_logo_jpg_size

$8 = (int *) 0x11e8

0x11e8的十进制是4584,它就是图片文件的大小。

从上面打印的结果来,那三个符号似乎是int类型的变量,因为打印它们的地址时,如下显示:

(gdb) p &_binary_logo_jpg_start$3 = (int *) 0x80496b0(gdb) p &_binary_logo_jpg_end

$4 = (int *) 0x804a898

(gdb) p &_binary_logo_jpg_size

$5 = (int *) 0x11e8

如果是int类型的变量的话,按理说应该能打印它们的值出来的,但下面的语句:

printf(“%d %d %dn”, _binary_logo_jpg_start, _binary_logo_jpg_end, _binary_logo_jpg_size);

会造成段错误,经试验,是最后一个符号_binary_logo_jpg_size造成的,这从一个侧面说明它们又不全是int类型。这是个人造成的错误(即笔者没有显式指定它们的类型)还是某种笔者未知的原因(如何从目标文件知道某个符号是什么变量?)还是其它原因,暂时没有研究到。也是因为这样,笔者才在前面说“讲述有些不正确”。在使用中,可以这样认为,&_binary_logo_jpg_start得到图片开始地址,&_binary_logo_jpg_size得到图片的大小。在这篇文章中就是这样应用的:

注:

1、关于i386,曾经的某个时候,笔者的一个同学问了笔者,但笔者答不上来。网上有论坛也有人问在Linux输入uname -a得到的那些i386、i586、i686是什么意思,现在忘了。Linux基础的书籍中应该有这方面的知识。

2、binutils的确很有用,但好像又没有什么用,笔者很久就学习了一下,结果现在又忘了。就像数据结构和算法,似乎有用,似乎又没用。这只是说:当使用到的时候,它有就用。不是有句话吗,书到用时方恨少。平时多积累点知识,还是有用的。

本文固定链接: http://www.latelee.org/using-gnu-linux/make-picture-into-executable.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: 将图片嵌入程序文件的一点研究 | 迟思堂工作室

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter