目标代码(Object Code)指编译器和汇编器处理源代码后所生成的机器语言目标代码
目标文件(Object File)指包含目标代码的文件
最早的目标文件格式是自有格式,非标准的。标准的几种目标文件格式:
– DOS操作系统(最简单) :COM格式,文件中仅包含代码和数据,且被加载到固定位置
– System V UNIX早期版本:COFF格式,文件中不仅包含代码和数据,还包含重定位信息、调试信息、符号表等其他信息,由一组严格定义的数据结构序列组成
– Windows:PE格式(COFF的变种),称为可移植可执行(Portable Executable,简称PE)
– Linux等类UNIX:ELF格式(COFF的变种),称为可执行可链接(Executable and Linkable Format,简称ELF)
ELF文件格式提供了两种视图,分别是:
链接视图(被链接):可重定位目标文件 (Relocatable object files)
执行视图(被执行):可执行目标文件(Executable object files)
(1)链接视图——可重定位目标文件
扩展名为.o(相当于Windows中的 .obj文件),包含代码、数据、定位信息(指出哪些符号引用处需要重定位)等
可被链接合并,生成可执行文件或共享目标文件(.so文件);若干个可重定位目标文件组成静态链接库文件(.a文件)。
节(section)是ELF 文件中具有相同特征的最小可处理单位:
- ELF 头:
分32位系统对应结构和64位系统对应结构(32位版本、64位版本)。32位系统中:
ELF头位于ELF文件开始,共52字节,包括16字节标识信息、文件类型 (.o, exec, .so)、机器类型(如 IA-32)、节头表的偏移、节头表的表项大小以及表项个数等
#define EI_NIDENT 16 typedef struct{ unsigned char e_ident[EI_NIDENT]; //16个字节的数组,定义了一些标识信息。最开头4B称为魔数,标识目标文件的类型或者格式。加载或读取文件时,可用魔数确认文件类型是否正确。再后面12字节,标识32位还是64位、大端还是小端、ELF头的版本号。 Elf32_Half e_type; //目标文件类型,是可重定位文件、可执行文件、共享库文件还是其它 Elf32_Half e_machine; //机器结构类型,是IA32、AMD64、SPARC V9还是其它 Elf32_Word e_version; //目标文件版本 Elf32_Addr e_entry; //程序执行的入口地址,可重定位目标文件应该为0,且没有程序头表(段头表)等执行视图 Elf32_Off e_phoff; //程序头表(段头表)的起始位置 Elf32_Off e_shoff; //节头表的起始位置 Elf32_Word e_flags; //处理器特定标志 Elf32_Half e_ehsize; //ELF头结构大小 Elf32_Half e_phentsize; //程序头表(段头表)的表项长度 Elf32_Half e_phnum; //程序头表(段头表)的表项数目 Elf32_Half e_shentsize; //节头表的表项长度 Elf32_Half e_shnum; //节头表的表项数目 Elf32_Half e_shstrndx; //字符串表在节头表中的索引,即节头表中第多少项是字符串表(String Table)。 }Elf32_Ehdr;
ELF头是二进制存储的,需要使用命令查看内容
$ readelf -h main.o Magic: 7f 45 4c 4601 01 01 00 00 00 00 00 00 00 00 00 //ELF文件的魔数是7f 45 4c 46。 Class: ELF32 Data: 2's complement, little endian //负数用2进制补码形式表示、小端方式存放 Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 //可重定位文件程序执行入口地址为0 Start of program headers: 0 (bytes into file) //没有程序头表 Start of section headers: 516 (bytes into file) //节头表起始位置 Flags: 0x0 Size of this header: 52 (bytes) Size of section headers: 40 (bytes) //节头表每项40B Number of section headers: 15 //节头表共15项 Section header string table index: 12 //.strtab在节头表中的索引
- .text 节
编译后的代码部分
- .rodata节
只读数据,如 printf 格式串、switch跳转表等
- .data 节
已初始化全局变量和静态成员变量,存放具体的初始值,需要占磁盘空间。
区分初始化和非初始化是为了空间效率
- .bss 节
未初始化全局变量和局部静态变量,默认初始值为0,.bss节中无需存放初始值,只要说明.bss中的每个变量将来在执行时占用几个字节即可,因此,.bss节实际上不占用磁盘空间,
通过专门的节头表(Section header table)来说明应该为.bss节预留多大的空间
- .symtab 节
存放函数和全局变量的 (符号表)信息 ,它不包括局部变量
- .rel.text 节
.text节的重定位信息,用于重新修改代码段的指令中的地址信息
- .rel.data 节
.data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息
- .debug 节
调试用符号表
- strtab 节
字符串表,包括.symtab节和.debug节中的符号以及节头表中的节名。
字符串表就是以null结尾的字符串序列。
- Section header table(节头表)
节头表的起始位置、表项数目、长度在ELF头中给出。
以下是32位系统对应的节头表数据结构(每个表项占40B),说明了每个节的节名、在文件中的偏移、大小、访问属性、对齐方式等
typedef struct { Elf32_Word sh_name; //节名字符串在.strtab节(字符串表)中的偏移 Elf32_Word sh_type; //节类型:无效/代码或数据/符号/字符串/... Elf32_Word sh_flags; //节标志:该节在虚拟空间中的访问属性 Elf32_Addr sh_addr; //虚拟地址:若可被加载,则对应虚拟地址 Elf32_Off sh_offset; //在文件中的偏移地址,对.bss节而言则无意义 Elf32_Word sh_size; //节在文件中所占的长度 Elf32_Word sh_link; //sh_link和sh_info用于与链接相关的节(如 .rel.text节、.rel.data节、.symtab节等) Elf32_Word sh_info; Elf32_Word sh_addralign; //节的对齐要求 Elf32_Word sh_entsize; //节中每个表项的长度,0表示无固定长度表项 } Elf32_Shdr;
使用readelf命令命令查看节头表内容
$ readelf -S test.o
![](https://images2018.cnblogs.com/blog/1424868/201806/1424868-20180621110307843-1768895818.jpg)
可重定位目标文件中,每个可装入节的起始地址总是0
.bss节应占00000c大小,但只有装入内存时才会分配。
这11个节在装入内存时,只有.text节、.data节、.bss节和.rodata节会分配存储空间
(2)执行视图——可执行目标文件
与可重定位文件的不同
- ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段为0
- 多一个程序头表,也称段头表(segment header table) ,是一个结构数组
- 多一个.init节,用于定义_init函数,该函数用来进行可执行目标文件开始执行时的初始化工作
- 少两个.rel节(无需重定位)
![](https://images2018.cnblogs.com/blog/1424868/201806/1424868-20180621110412669-304233500.jpg)
使用readelf命令查看ELF头的内容:
$ readelf -h main.o Magic: 7f 45 4c 4601 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: x8048580 Start of program headers: 52 (bytes into file) //程序头表在ELF头后 Start of section headers: 3232 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) //程序头表每项32B Number of program headers: 8 //程序头表共8项 Size of section headers: 40 (bytes) Number of section headers: 29 Section header string table index: 26 //.strtab在节头表中的索引
装入内存时,ELF头、程序头表、.init节、.rodata节会被装入只读代码段
.data节和.bss节会被装入读写数据段
描述可执行文件中的节与虚拟空间中的存储段之间的映射关系。
一个表项32B,说明虚拟地址空间中一个连续的段或一个特殊的节。
以下是32位系统对应的程序头表数据结构:
typedef struct { Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。 Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移 Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址 Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。 Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。 Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。 Elf32_Word p_flags; //此成员给出与段相关的标志。 Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。 } Elf32_phdr;
使用readelf命令某可执行目标文件的程序头表
$ readelf –l main
![](https://images2018.cnblogs.com/blog/1424868/201806/1424868-20180621110429497-46318751.jpg)
程序头表信息有8个表项,其中两个为可装入段(即Type=LOAD):
第一可装入段:第0x00000~0x004d3的长度为0x4d4字节的ELF头、程序头表、.init、.text和.rodata节,映射到虚拟地址0x8048000开始长度为0x4d4字节的区域 ,按0x1000=2^12=4KB对齐,具有只读/执行权限(Flg=RE),是只读代码段。
第二可装入段:第0x00f0c~0x01014的长度为0x108字节的.data节和磁盘中不占存储空间的.bss节,映射到虚拟地址0x8049f0c开始长度为0x110字节的存储区域,在0x110=272B存储区中,前0x108=264B用.data节内容初始化,后面272-264=8B对应.bss节,初始化为0 ,按0x1000=4KB对齐,具有可读可写权限(Flg=RW),是可读写数据段。
由此看出.bss节在文件中不占用磁盘空间,但在存储器中需要给它分配相应大小的空间。
![](https://images2018.cnblogs.com/blog/1424868/201806/1424868-20180621110446648-2096616144.jpg)