目录
前言:
一、ELF文件的类型
二、ELF文件的组成格式
1. ELF头部(ELF Header)
2. 节头表(Section Header Table)
3. 程序头表(Program Header Table)
4. 节(Sections)与段(Segments)
三、ELF文件从形成到加载轮廓
1、ELF可执行文件形成过程
2、 可执行文件从磁盘加载到内存section的变化
1、Section与Segment的基本关系
2、合并机制与内存优化
四、理解动静态库链接和加载
编辑
1. 动态链接核心原理
2. 程序启动与动态链接流程
动态链接器:
1. 位置无关代码(PIC)基础原理
前言:
ELF(Executable and Linkable Format):
是Unix/Linux系统下的标准可执行文件、目标文件和共享库格式。
是一种文件格式的名称
一、ELF文件的类型
ELF文件根据用途可分为以下几种主要类型:
-
可重定位文件(Relocatable File)
- 文件扩展名通常为
.o
- 由编译器生成,包含代码和数据但未指定绝对地址
- 用于与其他目标文件链接生成可执行文件或共享库
- 在Linux系统中通过
gcc -c
命令生成
- 文件扩展名通常为
-
可执行文件(Executable File)
- 通常没有特定扩展名(如Linux中的
a.out
) - 包含可直接执行的程序代码和数据
- 由链接器处理可重定位文件后生成
- 包含程序入口点,可直接被操作系统加载执行
- 通常没有特定扩展名(如Linux中的
-
共享目标文件(Shared Object File)
- 文件扩展名通常为
.so
- 包含可共享的代码和数据,用于动态链接
- 可在两种情况下使用:
- 链接时与其他文件链接生成新目标文件
- 运行时与可执行文件结合作为进程映像的一部分
- 文件扩展名通常为
-
核心转储文件(Core Dump File)
- 由操作系统在程序崩溃时生成
- 包含程序崩溃时的内存状态和寄存器信息
- 用于调试和故障排除
类型 | 扩展名 | 主要用途 | 生成方式 | 使用场景 |
---|---|---|---|---|
可重定位文件 | .o | 链接 | 编译器生成 | 链接阶段 |
可执行文件 | 无/.out | 执行 | 链接器生成 | 运行阶段 |
共享目标文件 | .so | 动态链接 | 链接器生成 | 链接和运行阶段 |
核心转储文件 | core | 调试 | 系统生成 | 调试阶段 |
二、ELF文件的组成格式
基本信息如图:
1. ELF头部(ELF Header)
- 位于文件起始位置,固定大小(32位系统52字节,64位系统64字节)
- 包含文件的基本信息:
- 魔数(Magic Number):
0x7f 0x45 0x4c 0x46
(ASCII为"ELF") - 文件类型(可执行/可重定位/共享库等)
- 目标体系结构(如x86、ARM)
- 程序入口地址(可执行文件)
- 节头表和程序头表的位置和大小信息
- 魔数(Magic Number):
2. 节头表(Section Header Table)
- 包含多个节头表条目,每个条目描述一个节(section)的信息
- 主要作用:
- 记录各节的名称、类型、文件偏移、大小、读写权限等
- 主要用于链接过程(链接视图)
- 对于可重定位文件是必须的,对于可执行文件是可选的
3. 程序头表(Program Header Table)
- 包含多个程序头表条目,每个条目描述一个段(segment)的信息
- 主要作用:
- 描述如何将文件中的段加载到内存
- 主要用于执行过程(执行视图)
- 对于可执行文件和共享库是必须的,对于可重定位文件可能为空
4. 节(Sections)与段(Segments)
-
节(Section):
- 链接视图的基本单位
- 常见节包括:
.text
:可执行代码.data
:已初始化的全局/静态变量.bss
:未初始化的全局/静态变量.rodata
:只读数据.symtab
:符号表.strtab
:字符串表.rel.text
/.rel.data
:重定位信息
-
段(Segment):
- 执行视图的基本单位
- 由多个具有相同权限的连续节组成
- 常见段类型:
LOAD
:需加载到内存的段(代码段、数据段)DYNAMIC
:动态链接信息INTERP
:指定动态链接器路径10
三、ELF文件从形成到加载轮廓
1、ELF可执行文件形成过程
- step-1:将多份 C/C++ 源代码,翻译成为⽬标 .o ⽂件
- step-2:将多份 .o ⽂件section进⾏合并
2、 可执行文件从磁盘加载到内存section的变化
1、Section与Segment的基本关系
ELF文件具有双重视图特性:
- 链接视图:以Section为基本单位,包含
.text
(代码)、.data
(已初始化数据)、.bss
(未初始化数据)等,主要用于链接阶段- 执行视图:以Segment为基本单位,由多个属性相同的Section合并而成,用于运行时加载
关键区别:
特性 Section Segment 用途 链接阶段 执行阶段 组织结构 独立功能单元 合并后的内存块 表结构 Section Header Table Program Header Table 必要性 可执行文件可选 可执行文件必须 2、合并机制与内存优化
合并过程的核心目的是减少内存碎片,提高页面使用效率:
为什么要合并
- 现代系统采用分页加载机制,典型页大小为4KB(4096字节)
- 示例:未合并时
.text
(4097B)和.data
(1B)占用3页(2+1),合并后仅需2页(4098B)合并规则:
- 相同内存属性(可读/可写/可执行)的Section会被合并8
- 必须具有相同的加载需求(需要运行时申请空间)8
- 典型合并模式:
- 代码段:合并
.text
、.rodata
等只读可执行Section- 数据段:合并
.data
、.bss
等可读写Section程序头表(Program Header Table)作用:
- 每个表项(Elf32_Phdr/Elf64_Phdr)描述一个Segment的:
- 类型(p_type):如PT_LOAD(需加载段)
- 标志位(p_flags):读写执行权限
- 文件偏移(p_offset)和大小(p_filesz)
- 内存地址(p_vaddr)和大小(p_memsz)
- 对齐要求(p_align)8
- 加载器根据这些信息建立进程内存映像
四、理解动静态库链接和加载
1、静态库
下面是样例代码
我们可以通过objdump -d 命令:将代码段(.text)进⾏反汇编查看,其中callq的加载到内存的机械码,而后面跟着的一串0代表访问的函数地址,在链接中才会填充地址,也叫做地址重定位
静态链接就是把库中的.o进⾏合并,和上述过程⼀样
所以链接其实就是将编译之后的所有⽬标⽂件连同⽤到的⼀些静态库运⾏时库组合,拼装成⼀个独⽴的可执⾏⽂件。其中就包括我们之前提到的地址修正,当所有模块组合在⼀起之后,链接器会根据我们的.o⽂件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从⽽修正它们的地址。这其实就是静态链接的过程
研究静态链接其实就是研究不同的.o文件是如何链接到一起的,.o类型文件也叫做可重定位目标文件
1. 动态链接核心原理
动态链接的本质是将链接过程推迟到程序加载时完成,这一机制实现了代码共享和内存优化。当执行一个程序时,操作系统会执行以下关键步骤:
- 程序加载:将程序的可执行代码和依赖的动态库加载到内存
- 地址分配:为每个动态库动态分配内存地址(ASLR技术确保地址随机化)
- 地址空间映射:将动态库映射到进程的地址空间
- 符号解析:解析程序对动态库中符号的引用
动态链接的两个关键阶段:
- 地址空间映射:通过
mmap
系统调用将动态库映射到进程地址空间 - 符号绑定:通过PLT/GOT机制实现函数调用跳转
2. 程序启动与动态链接流程
C/C++程序的执行并非直接从main函数开始,而是经历以下初始化过程
阶段 | 执行内容 | 负责组件 |
---|---|---|
_start | 设置堆栈、初始化数据段 | crt0.o |
动态链接 | 加载共享库、符号解析 | ld-linux.so |
__libc_start_main | 线程初始化、信号处理 | glibc |
main | 用户代码执行 | 用户程序 |
详细流程:
- 入口点_start:由C运行时库提供,建立基本执行环境
- 动态链接器调用:通过.interp段定位ld-linux.so
- 库加载与重定位:
- 解析DT_NEEDED条目加载依赖库
- 处理.rel.plt和.rel.dyn重定位表
- 控制权转移:通过__libc_start_main最终调用main函数
动态链接器:
◦ 动态链接器(如ld-linux.so)负责在程序运⾏时加载动态库。
◦ 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。
环境变量和配置⽂件:
◦ Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置⽂件(如/etc/ld.so.conf及其⼦配置⽂件)来指定动态库的搜索路径。
◦ 这些路径会被动态链接器在加载动态库时搜索。
缓存⽂件:
◦ 为了提⾼动态库的加载效率,Linux系统会维护⼀个名为/etc/ld.so.cache的缓存⽂件。
◦ 该⽂件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会⾸先
搜索这个缓存⽂件
1. 位置无关代码(PIC)基础原理
位置无关代码(Position Independent Code)是动态库能够实现多进程共享的核心技术,其核心特性包括:
- 地址无关性:代码可以在内存任意位置加载执行,无需修改指令
- 相对寻址:所有地址引用都基于当前指令指针或全局偏移表(GOT)
- 重定位延迟:符号解析推迟到加载或运行时完成
PIC的实现主要通过两种机制:
- PC相对寻址:用于函数内部跳转和局部数据访问
- 全局偏移表(GOT):存储外部变量和函数的绝对地址,通过间接访问实现重定位
基本过程
- 通过mm_struct中的变量找到有关共享区的结构体,根据里面的成员指针变量找到路径
- 根据路径找到磁盘中的数据块加载到内存
- 发生映射关系关联起来
- 得到库的起始虚拟地址
- 数据区会有一个名为.GOT的表记录库函数的偏移量,映射过后,表会根据库的起始虚拟地址进行修改得到完整的访问共享区的地址
--------------------------------------------------------------------------------------------------------------------------