A.目标文件的格式
目标文件定义
源码经过编译之后但是没有经过链接的中间文件。
理解:
目标文件和可执行文件的结构相似,所以和可执行文件采用一种格式存储。可执行文件的格式,主要有PE(Portable Executable )和ELF(Executable Linkable Format)。目标文件和可执行文件可以看成一种类型的文件。同理,动态链接库和静态链接库也是按照可执行文件的格式存储的。
以后这里主要说的是Linux下的ELF存储格式。
文件类型 | 备注和解释 | 例子 |
---|---|---|
可重定位文件 | 包含代码和数据,可以链接成可执行文件和共享目标文件 | hello.o |
可执行文件 | 包含可以直接执行的程序,一般没有扩展名 | /bin/bash |
共享目标文件 | 包含代码和数据,有两个用处,一是和目标文件或者其他的共享目标文件进行链接,产生新的目标文件,二是动态链接器可以把几个共享目标文件与可执行文件结合,作为进程映像的一部分来运行 | Linux的.so文件 |
核心转储文件 | 进程意外中指时,系统把该进程的地址空间中内容和一些终止时信息转储到核心转储文件 | Linux 下的core dump |
几个例子:
B.目标文件的内容
段:目标文件的不同信息放在不同的段里。
#include <stdio.h>
#include <stdlib.h>
int global_var1 = 66; //全局变量1放在.data段
int global_var2; //全局变量2放在.bss段
int func(int temp)
{
return temp;
} //func函数放在.text段
int main(int argc, char *argv[])
{
static int s_var1 = 77; //局部静态变量放在.data段
static int s_var2 = 0; //局部静态变量放在.bss段
int a = 1; //局部变量放在.text段
int b; //局部变量放在.text段
printf(" %d \n",func(a));
return 0;
}
额外的解释:
ELF文件的简单结构 |
---|
文件头:描述文件属性,是否可执行,如果可执行,目标硬件和操作系统等,段表 |
.text section:目标文件的代码段 |
.data section:目标文件的数据段,存放非零全局变量和非零静态变量 |
.bss section:这个段特殊,它是为没有初始化的全局变量和局部静态变量寓预留位置,没有内容,在文件中不占有空间,只有到真正可执行的时候才有空间 |
段表:
一个描述文件中各个段的数组,它描述了段的属性,每个段在文件中的偏移等。
为什么数据和指令要分开?
1.代码安全。数据可读写,但是指令一定是只读,,如果把他们映射到两个虚存区域,则这两个虚存区域的权限可以设置成可读写和只读。
2.提高程序的局部性。
3.节省空间。指令只读就可以在内存中只留一份这样的指令(程序共享指令),可以节省空间。
C.再看目标文件的段们
源代码是这样的:
#include <stdio.h>
#include <stdlib.h>
int global_var1 = 66; //全局变量1放在.data段
int global_var2; //全局变量2放在.bss段
int func(int temp)
{
return temp;
} //func函数放在.text段
int main(int argc, char *argv[])
{
static int s_var1 = 77; //局部静态变量放在.data段
static int s_var2 = 0; //局部静态变量放在.bss段
int a = 1; //局部变量放在.text段
int b; //局部变量放在.text段
printf(" %d \n",func(a));
return 0;
}
它的目标文件是这样的:
(命令:objdump -h test.o)
.rodatal : 只读数据段
.comment : 注释信息段
.note.GNU-stack : 堆栈提示段
eh_frame: 异常调试段
查看源文件和目标文件数据的对应:
(命令:size test.o)
代码段:
数据段和只读数据段
bss段
其他段
段名称 | 解释 |
---|---|
.rodatal | 存放的是只读数据,比如字符串常量,const变量 |
.comment | 编译器版本信息 |
.debug | 调试信息 |
.dynamic | 动态链接信息 |
.hash | 符号哈希表 |
.line | 调试的行号表,源代码行号与编译后指令的对应表 |
.note | 额外的编译信息 |
.strtab | 存储ELF文件中用到的字符串们 |
.symtab | Symbol table符号表 |
.shstrtab | section string table 段名表 |
.plt & .got | 动态链接调转表和全局入口表 |
.init & .fini | 初始化代码段和终结代码段 |
D.说ELF文件结构
文件头:
第一个02:64位
第一个01:小端
第二个01:ELF版本号
**文件头结构**
typedef struct{
unsigned char e_ident[16];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ephsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndex;
} Elf32_Ehdr;
成员 readelf输出结果与含义
e_ident Magic:7f 454c 46 01 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
e_type Type: EXEC (executable file)
ELF文件类型
e_machine Machine: Intel 80386
ELF文件的CPU平台属性
e_version Version:0x1
ELF版本号:
一般为常数1
e_entry Entry point address: 0x8048320
入口地址
规定ELF程序的入口虚拟地址,操作系统在加载完成该程序后,从这个地址开始执行进程的指令。可重定位文件一般没有入口地址,则这个值为0
e_phoff Start of program headers: 52(bytes into file)
程序头表的偏移。参考下文的“连接视图和执行视图”
e_shoff Start of section headers: 4472(bytes into file)
段表在文件中的偏移,也就是从文件的4473个字节开始是段表内容
e_word Flags: 0x0
ELF标志位
用来标志一些ELF文件平台相关的属性。
e_ehsize Size of this header: 52(bytes)
ELF文件头本身的大小
e_phentsize Size of program headers: 32(bytes)
程序头大小
e_phnum Number of program headers:9
在执行视图中,Segments的数量
e_shentsize Size of section headers:40(bytes)
段表描述符大小
e_shnum Number of section headers:30
段表描述符的数量
这个值等于ELF文件中拥有的段(section)的数量。
e_shstrndx Section header string table index:
段表字符串表所在的段在段表中的下标。
段表
我的:
符号(链接的接口)
符号:函数和变量
符号名:函数名和变量名
类别区分 | 举例 |
---|---|
定义在本身目标文件中的全局符号 | global_var1 |
本身目标文件引用的,来自别人的全局符号 | printf |
段名符号 | .test |
局部符号 | s_var1 |
行号信息 | … |
等等 | … |
ELF符号表结构体
typedef struct {
Elf32_Word st_name; //该符号名在字符串表中的下标。
Elf32_Addr st_value; //符号相对应的值。
Elf32_Word st_size; //符号大小。对于包含数据的符号,这个值是该数据类型的大小
unsigned char st_info; //符号类型和绑定信息。
unsigned char st_other; //该成员目前为0,没用。
Elf32_Half st_shndx; //符号所在的段。
}Elf32_Sym;
st_name
符号名。这个成员包含了该符号名在字符串
表中的下标(还记得字符串表吧?)
st_value
符号相对应的值。这个值跟符号有关,可能
是一个绝对值,也可能是一个地址等,不同的符号,
它所对应的值含义不同,见下文“符号值”
st_size
符号大小。对于包含数据的符号,这个值是该数据
类型的大小。比如一个double型的符号它占用8个字节。
如果该值为0,则表示该符号大小为0或未知
st_info
符号类型和绑定信息,见下文“符号类型与绑定信息”
st_other
该成员目前为0,没用
st_shndx
符号所在的段,见下文“符号所在段”
几个特殊符号
_executable_start 程序起始地址
_etext 代码段结束地址
edata 数据段结束地址
_end 程序结束地址
E.强弱符号和符号多次定义
出现原因
一个符号可能被多次重复定义。那么编译时候需要归并这些相同的符号。
强弱符号规则
1.不允许强符号多次被定义。
2.一个符号在a文件是强符号,b文件是弱符号,则选择强符号
3.都是弱符号,以占用空间最大的那个为准。
That is all.