由于不可避免地粘贴程序编译、程序运行的结果,在此作一些约定:
图片文件名称为logo.jpg,将其转换成目录文件的名称是logo_386.o,测试文件为logo_test.c。
本文有代码有真相,如果想有图有真相的话,移步到这里:将图片嵌入程序文件的测试。
1、将图片文件转换成目标文件
使用objcopy工具即可将图片文件转换成目标文件,示例如下:
这里是针对386平台,毕竟这个平台比较方便(这是废话,请无视)。其中-I选项指定输入文件的格式,这里是二进制;-O指定输出文件的格式,这里应该是指elf文件类型;-B是指定目标文件的架构,这里是i386平台,关于i386,Linux是比较笼统的说法,具体参考相关资料(真实中,笔者使用的Linux是运行在Intel i3 CPU的虚拟机中)。
用objdump看一下logo_386.o有什么东西:
logo_386.o: file format elf32-i386
Sections:
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: %d\n", sizeof(Elf32_Ehdr));
printf("%p %p %p\n", &_binary_logo_jpg_start, &_binary_logo_jpg_end, &_binary_logo_jpg_size);
return 0;
}
注意:程序中没有给出_binary_logo_jpg_start的类型,因为笔者想不出它们几个到底是什么。如果使用-Wall来编译,得到下面的警告:
这说明,如果代码没有明确指定类型,编译器默认int类型。
在代码中,“符号”一词指的东西比较多,指针是符号、数组名是符号,函数名称是符号、变量名称是符号,似乎一切均符号(u-boot将“符号”用得淋漓尽致,可以参考u-boot启动部分的C代码及汇编代码,这部分的C代码就使用到了汇编中的“符号”)。
程序运行结果如下:
程序特意输出elf文件头部信息的结构体的大小,结果是52字节,而生成的目标文件中,图片内容位于0x34字节偏移,前面有0x34字节,0x34正是十进制的52。关于这个结构体,在此不展开。由于笔者曾经在一段时间中几乎天天看jpg文件的内容(主要是关于jpeg、mjpeg、avi这方面的),知道jpg文件以“FF D8”开始,在“FF D9”结束,目标文件的“FF D8”就在0x34处。
Linux下用hexdump查看开始部分内容(由于网页关系,可能不对齐):
3、调试程序
我们用gdb分别看看那三个符号:
先是_binary_logo_jpg_start
(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个字节:
_binary_logo_jpg_start的值正是图片内容的前4个字节。
再看看_binary_logo_jpg_end,
它的值为0,地址为0x804a898,我们再看看这个地址稍微前面的内容,就以0x804a890地址为例:
我们看到几个关键的值,一是4576,它很接近图片文件的大小,另一个是0xff 0xd9,它是图片结束的标志,而0xd9,位于离_binary_logo_jpg_start地址4584处,图片大小正是4584字节。
再看看_binary_logo_jpg_size:
0x11e8的十进制是4584,它就是图片文件的大小。
从上面打印的结果来,那三个符号似乎是int类型的变量,因为打印它们的地址时,如下显示:
如果是int类型的变量的话,按理说应该能打印它们的值出来的,但下面的语句:
会造成段错误,经试验,是最后一个符号_binary_logo_jpg_size造成的,这从一个侧面说明它们又不全是int类型。这是个人造成的错误(即笔者没有显式指定它们的类型)还是某种笔者未知的原因(如何从目标文件知道某个符号是什么变量?)还是其它原因,暂时没有研究到。也是因为这样,笔者才在前面说“讲述有些不正确”。在使用中,可以这样认为,&_binary_logo_jpg_start得到图片开始地址,&_binary_logo_jpg_size得到图片的大小。在这篇文章中就是这样应用的:
注:
1、关于i386,曾经的某个时候,笔者的一个同学问了笔者,但笔者答不上来。网上有论坛也有人问在Linux输入uname -a得到的那些i386、i586、i686是什么意思,现在忘了。Linux基础的书籍中应该有这方面的知识。
2、binutils的确很有用,但好像又没有什么用,笔者很久就学习了一下,结果现在又忘了。就像数据结构和算法,似乎有用,似乎又没用。这只是说:当使用到的时候,它有就用。不是有句话吗,书到用时方恨少。平时多积累点知识,还是有用的。