1.ELF文件
要理解编译链链接的细节,我们不得不了解⼀下ELF⽂件。其实有以下四种⽂件其实都是ELF⽂件:
• 可重定位⽂件(Relocatable File ) :即 xxx.o ⽂件。包含适合于与其他⽬标⽂件链接来创
建可执⾏⽂件或者共享⽬标⽂件的代码和数据。
• 可执⾏⽂件(Executable File ) :即可执⾏程序。
• 共享⽬标⽂件(Shared Object File ) :即 xxx.so⽂件。
• 内核转储(core dumps) ,存放当前进程的执⾏上下⽂,⽤于dump信号触发。
⼀个ELF⽂件由以下四部分组成:
• ELF头 (ELF header) :描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位⽂
件的其他部分。
• 程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表⾥
记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中,
需要段表的描述信息,才能把他们每个段分割开。
• 节头表(Section header table) :包含对节(sections)的描述。
• 节(Section ):ELF⽂件中的基本组成单位,包含了特定类型的数据。ELF⽂件的各种信息和
数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等。
最常⻅的节:
• 代码节(.text):⽤于保存机器指令,是程序的主要执⾏部分。
• 数据节(.data):保存已初始化的全局变量和局部静态变量。
2.ELF从形成到加载轮廓
2-1 ELF形成可执⾏
• step-1:将多份 C/C++ 源代码,翻译成为⽬标 .o ⽂件
• step-2:将多份 .o ⽂件section进⾏合并
2-2 ELF可执⾏⽂件加载
当用户执行一个ELF可执行文件时,操作系统通过加载器(Loader) 和**动态链接器(Dynamic Linker)**协作完成内存映射、权限设置、依赖解析等关键操作
⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进⾏Section合并,形成segment
合并原则:相同属性,⽐如:可读,可写,可执⾏,需要加载时申请空间等.
这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到⼀起
很显然,这个合并⼯作也已经在形成ELF的时候,合并⽅式已经确定了,具体合并原则被记录在了
ELF的 程序头表 (Program header table) 中
注意:为什么要讲section合并成segment
Section合并的主要原因是为了减少⻚⾯碎⽚,提⾼内存使⽤效率。如果不进⾏合并,
假设⻚⾯⼤⼩为4096字节(内存块基本⼤⼩,加载,管理的基本单位),如果.text部分
为4097字节,.init部分为512字节,那么它们将占⽤3个⻚⾯,⽽合并后,它们只需2个
⻚⾯。
此外,操作系统在加载程序时,会将具有相同属性的section合并成⼀个⼤的
segment,这样就可以实现不同的访问权限,从⽽优化内存管理和权限访问控制
3.理解连接与加载
3-1 静态链接
无论是自己的.o文件还是静态库中的.o文件本质都是把.o文件进行链接的过程所以静态链接就是研究静态链接的过程
3-2静态链接的核心流程
输入文件准备
-
目标文件 :由编译器生成的
.o
文件,包含代码、数据及未解析的符号引用。 -
静态库 :通过
ar
工具打包的.a
文件,本质是多个.o
文件的集合(如libmath.a
包含sin.o
、cos.o
等)。
符号解析(Symbol Resolution)
-
符号表合并:链接器遍历所有输入文件,构建全局符号表。
-
处理未定义符号 :若同一符号被多文件定义(如两个
.o
文件均定义global_var
),触发重复定义错误。
重定位(Relocation)
-
地址分配 :为所有节(Section)分配运行时内存地址(如
.text
从0x400000
开始)。 -
修正引用:根据最终地址,修改代码中的相对偏移或绝对地址。
复制
下载
// 目标文件中的未重定位指令(假设函数add的地址未确定) call 0x00000000 // 占位符,链接时替换为add的实际地址
生成可执行文件
-
合并所有目标文件的
.text
、.data
等节到统一Segment。 -
生成程序头表(Program Header)供加载器使用。
3-3ELF加载与进程地址空间
3-2-1 虚拟地址/逻辑地址
思考一下一个进程在没有加载到内存的时候它有没有地址呢?
⼀个ELF程序,在没有被加载到内存的时候,本来就有地址,当代计算机⼯作的时候,都采⽤"平坦
模式"进⾏⼯作。所以也要求ELF对⾃⼰的代码和数据进⾏统⼀编址,下⾯是 objdump -S 反汇编
之后的代码
3-4动态链接与动态库加载
进程是如何链接到库的呢
进程间是如何共享库的呢
我们可以思考一下在库加载到内存的时候我们在创建每一个进程的时候是否要给每个进程的重新加载一个库大家可以思考一下
我们是如何跟库具体映射起来的呢看一张图就明白了
总结
静态链接的出现,提⾼了程序的模块化⽔平。对于⼀个⼤的项⽬,不同的⼈可以独⽴地测试和开发
⾃⼰的模块。通过静态链接,⽣成最终的可执⾏⽂件。
我们知道静态链接会将编译产⽣的所有⽬标⽂件,和⽤到的各种库合并成⼀个独⽴的可执⾏⽂件,
其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(也叫做静态重定位)。
⽽动态链接实际上将链接的整个过程推迟到了程序加载的时候。⽐如我们去运⾏⼀个程序,操作系
统会⾸先将程序的数据代码连同它⽤到的⼀系列动态库先加载到内存,其中每个动态库的加载地址
都是不固定的,但是⽆论加载到什么地⽅,都要映射到进程对应的地址空间,然后通过.GOT⽅式进 ⾏调⽤(运⾏重定位,也叫做动态地址重定位)。