目标文件
目标文件: 源代码经过编译后,但未进行链接的 “中间文件”(例如,windows下的.obj、linux下的.o)
他 和 exe可执行文件,无论内容和格式,都非常接近! 广义上,两者可以看成是一个东西。
目标文件(Executable),基本可以分为:
- Windows下的: PE(Portable executable)
- Linux下的: ELF(Executable linkable format)
他们都遵循 “COFF(Common file format)”格式,COFF是标准的exe可执行文件格式。
COFF不仅包含 exe,例如DLL(Dynamic link lab) SLL(Static link lab) 都遵循COFF格式。
COFF分类:
- 可重定位文件,例如
windows的obj,linux的o
该文件包含 代码和数据,以后用来被链接成exe或dll (静态sll就属于这一类) - 共享目标文件,例如
windows的dll,linux的so
该文件包含 代码和数据,有两种使用方式:(一种是:和其他的dll和sll,进行链接产生exe)
(另一种是: 动态链接器 将dll 和exe进行结合,作为进程映像的一部分) - 可执行文件,例如
windows的exe,linux的bash
该文件包含 可以直接运行的程序 - 核心转储文件,例如
linux下的core dump
当进程意外终止,系统可以将该进程的地址空间内容 和 终止时的一些信息,转储到该文件里。
在linux下,可以通过file命令,得到文件的格式。
历史:
Linux最早是使用.out文件,但共享库出现后 这种文件就捉襟见肘了,于是 产生了COFF格式。
COFF是由Linux提出的,作为可执行文件的格式。
后来,windows在COFF的基础上,引入了PE格式
Linux也在COFF的基础上,引入了ELF格式。
现在linux,都是以ELF作为可执行文件的基础格式。
所以,PE和ELF 非常相似,因为都是基于COFF格式。
目标文件—格式
因为一个程序,分为: 代码 和 数据。 所以,目标文件 也分为: 编译后的机器指令代码 和 数据。
具体的,目标文件里的内容,是划分为了 若干个“段”(其实就是 内存!):
- “文件头 File Header”
描述:该文件的一些属性,是静态/动态链接、入口地址、、 - “代码段 .text”
他里面存的:即你程序的代码文本。 反汇编后,就可以得到汇编指令。
这个段的大小,取决于你程序代码文本的大小。 - “数据段 .data”
已经初始化了的 全局变量 + 已经初始化的 局部静态变量
不考虑全局静态变量,你可以思考下,全局静态变量 其实就是全局变量(静态:用处是在,局部域/类 里面)
比如:int g_data = 1
static local_data = 2
那么,bss段的大小是8字节。分别是:00,00,00,01 和 00,00,00,02
共8个字节,存储的是 这个变量的值
(顺序,取决于cpu的字节序Byte Order
:大端、小端)
这个段的大小,就是所有这些变量的sizeof
之和!!! - “预存段 .bss”
未初始化的 全局变量 + 未初始化的 局部静态变量。
bss段只是记录了 这些变量 的空间之和!!! “预留 这些大的 空间!”,但并没有将内存空间设置初始值!
bss: block started by symbol,用于为符号 预留 一块内存空间 - 上面的主要的一些分段,细分下去 其实还有很多
比如,还有.rodata(rom data):存 只读const
数据、字符串常量
你的printf("%d\n", 123)
,其中的%d\n
这就是个字符串常量,他就在.rodata段,占用4[%][d][\][n]
字节
还有.rel段,表示“重定位表”: 即extern的变量和函数
将程序 分为: 代码段.text 和 数据段.data + .bss 的好处:
- 代码段 和 数据段,被放到 不同的 虚拟内存空间。 代码段是不可以写的!! 这样防止修改代码段的内容
- cpu的缓存 分为: 数据缓存 和 代码缓存,这样可以提高cpu缓存的 命中率
- 当运行同一程序的多个副本时,因为他们的代码段 都是完全一样 而且是只读的,这样会极大的节省内存。
自定义段:
__attribute__((section("name"))) int a = 123;
指定 该变量/函数 所在的段。
符号
一个目标文件里的:全局变量和函数(可以是自定义的,也可以是extern的),就称为:符号。
符号存放在:符号表Symbol Table
,每个目标文件 都有一个符号表
每个符号都有一个符号值,即该变量/函数 的地址。
细分符号有: 内部符号(不带extern的全局量)、外部符号(带extern的)
符号修饰
源代码经过编译 在得到目标文件时,会对“变量、函数”的名称 进行 修饰Decorate
最终得到的:修饰后的名称decorated name
,就是“符号名”
所有的符号名,肯定是不同的。
这个修饰,即根据他的:命名空间、原生名、返回值、参数名、、、 得到一个最终的名称
比如: f1函数里有个static的a,f2函数里也有个static的a。那么, 最终他俩最终的修饰名,肯定是不同的。