其实是想搞嵌入式的,但是总是要补补这里的知识补点那里的知识
现在PC平台流行的可执行文件格式(Executable)主要是Windows下的PE和Linux的ELF,他们都是COFF格式的变种
ELF文件标准里面把系统中采用ELF格式的文件归为以下4类
ELF文件类型 | 说明 | 实例 |
---|---|---|
可重定位文件(Relocatable) | 这类文件包含了代码和数据,可以用来链接成可执行文件或共享目标文件,静态链接库也属于这一类 | Linux的.o Windows的.obj |
可执行文件(Executable) | 这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,一般没有扩展名 | 比如/bin/bash文件 Windows的.exe |
共享目标文件(Shared Object File) | 这种文件包含了代码和数据 ,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态连接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分 | Linux的.so windows的DLL |
核心转储文件(Core dump file) | 当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件 | linux下的Core dump |
在linux下可以使用file命令来查看
ELF 文件头 位于最前端,它包含了整个文件的基本属性,如文件版本,目标机器型号,程序入口等等。
.text 为代码段,也是反汇编处理的部分,他们是以机器码的形式存储,没有反汇编的过程基本不会有人读懂这些二进制代码的。
.data 数据段,保存的那些已经初始化了的全局静态变量和局部静态变量。
.bss 段, 存放的是未初始化的全局变量和局部静态变量,这个很容易理解,因为在未初始化的情况下,我们单独用一个段来保存,可以不在一开始就分配空间,而是在最终连接成可执行文件的时候,再在.bss 段分配空间。
其他段, 还有一些可选的段,比如.rodata 表示这里存储只读数据, .debug 表示调试信息等等,具体遇到可以查看相关文档。
自定义段,这一块是为了实现用户特殊功能而存在的段,方便扩展,比如我们使用全局变量或者函数之前加上 attribute(section(‘name’)) 就可以吧变量或者函数放到以name 作为段名的段中。
段表,Section Header Table ,是一个重要的部分,它描述了ELF 文件包含的所有段的信息,比如每个段的段名,段长度,在文件中的偏移,读写权限和一些段的其他属性。
ELF目标文件格式的最前端是ELF文件头,包含了描述整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址。紧接着的就是ELF文件各个段。其中ELF文件中与段有关的重要结构就是段表。段表描述的是ELF文件包含的所有段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限以及段的其他属性。
分段的好处??
1.其实数据和指令分段的好处有很多,一方面是当程序被装载后,数据和指令被分别映射到两个虚存。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样可以防止程序的指令被有意或无意的改写。
2.对于现代cpu来说,它们有着极为强大的缓存体系,现代CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率的提高有好处。
3.当系统中运行多个该程序的副本的时候,由于指令都是一样的,所以内存中只要保存一份该程序的指令。比如说很多程序的带的图标、文本、资源都可以共享。在现代操作系统中,尤其是动态链接的系统中,节省了大量的内存。
以64位版本的文件头结构Elf64_Ehdr为例
85 typedef struct
86 {
87 unsigned char e_ident[EI_NIDENT]; /* 有6个值,分别代表不同的含义*/
88 Elf64_Half e_type; /* ELF文件类型,REL */
89 Elf64_Half e_machine; /* cpu平台*/
90 Elf64_Word e_version; /* ELF版本号,一般为1 */
91 Elf64_Addr e_entry; /*入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位文件一般没有入口地址,则这个值为0*/
92 Elf64_Off e_phoff; /* Program header table file offset */
93 Elf64_Off e_shoff; /* 段表在文件中的偏移,从下一个字节开始 */
94 Elf64_Word e_flags; /*ELF标志位,用来标识一些ELF文件平台相关属性,相关常量的格式一般为EF_machine_flag*/
95 Elf64_Half e_ehsize; /* ELF文件头大小 */
96 Elf64_Half e_phentsize; /* Program header table entry size */
97 Elf64_Half e_phnum; /* Program header table entry count */
98 Elf64_Half e_shentsize; /*段表描述符大小,等于sizeof(Elf64_Shdr) */
99 Elf64_Half e_shnum; /* 段表描述符数量,这个值等于ELF文件中拥有的段的数量 */
100 Elf64_Half e_shstrndx; /* 段表字符串所在的段在段表中的下标 */
101 } Elf64_Ehdr;
要注意的是e_ident数组包含了6个成员,Magic、类别、数据、版本、OS/ABI、ABI版本
Magic中的16个字节被ELF标准规定用来标识ELF文件的平台属性,比如ELF字长(32位/64位)、字节序、ELF文件版本
最开始的4个字节 是所有ELF文件都必须相同的标识码,分别为0x7f 0x45 0x4c 0x46,第一个字节对应ASCII字符里面的DEL控制符、后面3个字节 是ELF这三个字母的ASCII码,这4个字节被称为魔数。第5个字节 标识ELF的文件类,0x01表示32位,0x02表示64位,第6位, 规定ELF文件是大端的还是小端的。第7个字节 规定了ELF文件的主版本号一般是1
段表就是保存段的一些基本属性,描述了段的段名、段的长度、在文件中的偏移、读写权限以及其他属性。ELF文件的段结构是由段表决定的,编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在文件中的位置由ELF文件头的e_shoff成员决定。
下面是段描述符结构
287 typedef struct
288 {
289 Elf64_Word sh_name; /* 段名*/
290 Elf64_Word sh_type; /* 段类型 */
291 Elf64_Xword sh_flags; /* 段标志位*/
292 Elf64_Addr sh_addr; /* 段虚拟地址 */
293 Elf64_Off sh_offset; /* 段偏移 */
294 Elf64_Xword sh_size; /* 段的长度 */
295 Elf64_Word sh_link; /* 段的链接信息 */
296 Elf64_Word sh_info; /* 段的链接信息*/
297 Elf64_Xword sh_addralign; /* 段地址对齐 */
298 Elf64_Xword sh_entsize; /* 项长度 */
299 } Elf64_Shdr;
后记:段的名字对于编译器,链接器来说是有意义的,但是对于操作系统来说并没有实质的意义,对于操作系统来说,一个段如何处理取决于它的属性和权限,也就是段的类型和段的标志位这两个成员决定。
test.o段表结构
将test的所有段的位置和长度信息分析如下:空白处代表字节对齐,因为Section Table的长度为0x340,也就是832个字节,它包含了13个段描述符,每个段描述符为64个字节。
整个文件的结尾是Section Table,总长度为0x730,也就是1840个字节,也刚好是test.o的文件长度。
通过验证也确实是1840字节。
在段的结构体中:
段的名字只是在链接和编译过程中有意义,但是不能真正地表示段的类型。对于编译器和链接器来说,主要决定段的属性的是段的类型(sh_type)和段的标志位(sh_flags),列举如下表
段的标志位表示该段在进程虚拟地址空间中的属性,比如是否可写、是否可执行
对于系统中的保留段,下表也列举了段的类型和段的标志位属性,可以通过readelf -S test.o命令得到的属性进行对照
如果段的类型是与链接相关的(不论是动态链接还是静态链接),比如重定位表、符号表等,那么sh_link和sh_info这两个成员所包含的意义如下表所示,对于其他类型的段,这两个成员,没有意义
这个也就是test.o中有一个叫做.rela.text的段和.rela.eh_frame的段,他们的类型都是SHT_RELA(其他的段类型含义可以到 /usr/include/elf.h 中查看),意思是带有添加项的重定位项,也就是说它是一个重定位表。也就是链接器在处理目标文件的时候,要对一些目标文件中某些部位进行重定位。关于重定位可以看hello.程序的编译过程
关于这里我想说的是关于eh_frame段和rela.eh_frame段查了很多资料目前还不知道存了什么,是什么意思。
一个重定位表同时也是一个ELF段,那么这个段的类型就是(sh_type)就是SHT_RELA类型,它的sh_link表示符号表的下表,它的sh_info表示它作用于哪个段。比如.rela.text作用于.text段,而.text段的下标为1,那么.rela.text的sh_info为1。
Gcc 编译出来的是ELF文件。通常gcc –o test test.c,生成的test文件就是ELF格式的,在linuxshell下输入 ./test就可以执行。
Bin 文件是经过压缩的可执行文件,去掉ELF格式的东西。是直接的内存映像的表示。在系统没有加载操作系统的时候可以执行。
elf(executable and link format)文件里面包含了符号表,汇编等。
BIN文件是将elf文件中的代码段,数据段,还有一些自定义的段抽取出来做成的一个内存的镜像。
在Embedded中,如果上电开始运行,没有OS系统,如果将ELF格式的文件烧写进去,包含一些ELF格式的东西,arm运行碰到这些指令,就会导致失败,如果用arm-softfloat-linux-gnu-objcopy生成纯粹的汇编 bin文件,程序就可以一步一步运行。