文章目录
- 说明
- [一 链接:理解程序的构建过程](#一 链接:理解程序的构建过程)
-
- [1.1 编译与链接流程](#1.1 编译与链接流程)
-
- [1.1.1 链接器的优势](#1.1.1 链接器的优势)
- [1.1.2 链接器的功能](#1.1.2 链接器的功能)
- [1.2 ELF(可执行与可链接格式)【重点】](#1.2 ELF(可执行与可链接格式)【重点】)
-
- [1.2.1 ELF 文件结构](#1.2.1 ELF 文件结构)
- [1.2.2 链接器符号分类](#1.2.2 链接器符号分类)
- [1.2.3 符号解析详解](#1.2.3 符号解析详解)
- [1.2.4 符号识别练习](#1.2.4 符号识别练习)
- [1.3 局部静态变量](#1.3 局部静态变量)
-
- [1.3.1 符号重复定义的处理规则](#1.3.1 符号重复定义的处理规则)
- [1.3.2 链接器规则](#1.3.2 链接器规则)
- [1.4 重定位过程](#1.4 重定位过程)
- [二 函数库的封装方式](#二 函数库的封装方式)
-
- [2.1 静态库(传统解决方案)](#2.1 静态库(传统解决方案))
-
- [2.1.1 创建静态库](#2.1.1 创建静态库)
- [2.1.2 静态库的链接过程](#2.1.2 静态库的链接过程)
- [2.2 共享库:现代解决方案](#2.2 共享库:现代解决方案)
-
- [2.2.1 动态库依赖查询](#2.2.1 动态库依赖查询)
- [2.2.2 共享库构建示例](#2.2.2 共享库构建示例)
- [2.2.3 加载时动态链接流程](#2.2.3 加载时动态链接流程)
- [三 总结](#三 总结)
说明
- 庄老师的课堂,如春风拂面,启迪心智。然学生愚钝,于课上未能尽领其妙,心中常怀惭愧。
- 幸有课件为引,得以于课后静心求索。勤能补拙,笨鸟先飞,一番沉浸钻研,方窥见知识殿堂之幽深与壮美,竟觉趣味盎然。
- 今将此间心得与笔记整理成篇,公之于众,权作抛砖引玉。诚盼诸位学友不吝赐教,一同切磋琢磨,于学海中结伴同行。
- 资料地址:computing-system-security
一 链接:理解程序的构建过程

main.c文件包含主函数和对sum函数的调用sum.c文件实现sum函数- 全局数组
array[2] = {1, 2} main函数调用sum(array, 2)并返回结果
1.1 编译与链接流程
- 使用命令:
gcc -Og -o prog main.c sum.c - 流程:
- 源文件 → 预处理器(cpp)→ 编译器(cc1)→ 汇编器(as)→ 可重定位目标文件(.o)
- 链接器(ld)将多个
.o文件合并为可执行文件prog
- 输入:两个可重定位目标文件(
main.o,sum.o) - 输出:完全链接的可执行文件(包含所有函数代码和数据)

1.1.1 链接器的优势
- 模块化:程序可以拆分为多个小的源文件,支持构建通用函数库(如数学库、标准C库),提高代码组织性和可维护性
- 时间效率:支持独立编译,修改一个源文件后只需重新编译该文件并重新链接,不需要重新编译其他未修改的文件,支持多文件并发编译。
- 空间效率:库函数集中管理。
- 静态链接:可执行文件仅包含实际使用的库代码
- 动态链接:可执行文件不包含库代码,运行时共享同一份库代码,节省内存空间
1.1.2 链接器的功能
一:符号解析(Symbol Resolution)
- 程序中定义和引用符号(全局变量、函数)
- 符号表由汇编器生成,存储在目标文件中
- 每个符号条目包括名称、大小、位置
- 链接器将每个符号引用关联到唯一的符号定义
二:重定位(Relocation)
- 合并各个
.o文件的代码段和数据段 - 将符号从相对地址重定位到最终的绝对内存地址
- 更新所有对这些符号的引用以反映新地址
1.2 ELF(可执行与可链接格式)【重点】
- 支持三种文件类型:可重定位文件(.o)、可执行文件(a.out)、共享对象文件(.so)统称为 ELF 二进制文件
- 可重定位目标文件(.o):包含可与其他
.o文件合并形成可执行文件的代码和数据。
- 每个
.c文件生成一个.o文件
- 可执行目标文件(a.out):可直接加载到内存并执行的格式
- 共享对象文件(.so):特殊类型的可重定位文件,可在加载或运行时动态加载和链接。Windows 中称为 DLL(动态链接库)
1.2.1 ELF 文件结构

- ELF 头部:描述字长、字节序、文件类型、机器类型等。
- 段头部表(Program Header Table):用于可执行文件,描述虚拟内存中的段(section)及其属性。
主要节区(Sections)
.text:程序代码。.rodata:只读数据(如字符串常量、跳转表)。.data:已初始化的全局变量。.bss:未初始化的全局变量,符号起始块,占用符号表条目但不占实际空间。
其他节区
.symtab:符号表.rel.text:.text节的重定位信息.rel.data:.data节的重定位信息.debug:调试信息(使用gcc -g时生成)
1.2.2 链接器符号分类
- 全局符号(Global Symbols):当前模块定义、可被其他模块引用,如非静态的 C 函数和全局变量。
- 外部符号(External Symbols):当前模块引用但由其他模块定义的全局符号。
- 局部符号(Local Symbols):仅在当前模块内定义和使用,如使用
static关键字定义的函数和变量
- 注意:局部链接器符号 ≠ 局部程序变量(如栈上变量)
1.2.3 符号解析详解

main.c定义main,引用sum和arraysum.c定义sum,使用局部变量i,s- 链接器处理的是全局符号,不关心局部变量
1.2.4 符号识别练习
c
int incr = 1;
static int foo(int a) {
int b = a + incr;
return b;
}
int main(int argc,
char* argv[]) {
printf("%d\n", foo(5));
return 0;
}
- 文件
symbols.c中哪些名称会进入符号表?incr(已初始化全局变量)✅foo(静态函数)✅main(主函数)✅printf(外部函数)✅(作为外部符号)"%d\n"(字符串常量)❌(属于.rodata,不单独列为符号)argc,argv,a,b❌(局部变量,不在符号表中)
1.3 局部静态变量
- 非静态局部变量:栈上分配。
- 静态局部变量:
.data或.bss段中分配。 - 编译器处理方式:为每个静态局部变量分配独立空间,在符号表中创建唯一命名的局部符号(如
x,x.1721,x.1724)。
1.3.1 符号重复定义的处理规则
- 强符号:函数、已初始化的全局变量
- 弱符号 :未初始化的全局变量、用
extern声明的变量

1.3.2 链接器规则
规则一:不允许多个强符号。同名强符号只能有一个定义,否则导致链接错误。
规则二:强弱共存时选择强符号。若存在一个强符号和多个弱符号,采用强符号,所有引用指向强符号定义。
规则三:多个弱符号任选其一。若全是弱符号,链接器任意选择一个,可通过 gcc -fno-common 禁用此行为。

- 尽量避免使用全局变量,如果必须使用,优先使用
static限制作用域,定义时尽量初始化(避免成为弱符号),引用外部全局变量时使用extern。声明为弱符号,若最终未定义,链接时报错。

1.4 重定位过程

- 可重定位文件到可执行文件:合并
.text和.data段,分配最终内存地址,更新所有符号引用。 - 重定位入口(Relocation Entries),来自
main.o的反汇编输出,包含待修正的地址和修正方式。

R_X86_64_32 array:修正array地址R_X86_64_PC32 sum-0x4:PC 相对寻址修正sum调用


- 重定位后的文本段,地址映射
main函数起始地址:0x4004d0sum函数起始地址:0x4004e8- 调用指令使用 PC 相对寻址:
callq 4004e8=0x4004e3 + 0x5
- 数据地址修正:
mov $0x601018,%edi:%edi = &array,array被重定位到0x601018
- 可执行文件加载

内存布局
- 用户空间起始于
0x400000 - 只读代码段 :
.init,.text,.rodata - 读写数据段 :
.data,.bss - 运行时堆 :由
malloc创建,向上增长 - 共享库映射区
- 用户栈 :由内核创建,向下增长(
%rsp指向栈顶) - 未使用区域
内核虚拟内存
- 对用户代码不可见
- 包含页表、内核代码等
加载来源
- 从可执行文件加载
.text,.data等节。 .bss节清零初始化。brk指针标记堆的当前边界
二 函数库的封装方式
2.1 静态库(传统解决方案)
- 静态库为
.a归档文件,将相关的可重定位目标文件合并为一个带索引的单一文件(归档)。 - 增强链接器功能:自动在归档中查找并解析未定义的外部符号
- 若某归档成员能解析引用,则将其链接进可执行文件
2.1.1 创建静态库
- 使用归档工具
ar增量更新库。 - 修改函数后重新编译对应
.o文件并替换进归档。 - 示例命令:
ar rs libc.a atoi.o printf.o ... random.o。

2.1.2 静态库的链接过程

- 自定义向量库
libvector.a - 包含
addvec.c和multvec.c实现 - 主程序
main2.c调用addvec - 头文件
vector.h声明接口
链接流程
- 编译生成
main2.o和addvec.o,multvec.o - 使用
ar打包成libvector.a - 链接时使用
-L.指定路径,-lvector引入库 - 静态链接命令:
gcc -static -o prog2c main2.o -L. -lvector - 生成完全链接的可执行文件(大小 861,232 字节)

2.2 共享库:现代解决方案
静态库的缺点
- 存储冗余:每个可执行文件都复制 libc 等库代码
- 运行时内存冗余
- 系统库的小幅修复需重新链接所有应用
- 举例:glibc 的 CVE-2015-7547 安全漏洞需大规模重编译
- 动态链接库(Shared Libraries)/动态链接库(DLL)、
.so文件:代码和数据在加载或运行时动态加载并链接 - 加载时链接(Load-time Linking):可执行文件首次加载运行时完成,Linux 中由动态链接器
ld-linux.so自动处理,标准 C 库libc.so通常采用此方式。 - 运行时链接(Run-time Linking):程序启动后进行动态链接,Linux 中通过
dlopen()接口实现。 - 应用场景:软件分发,高性能 Web 服务器,运行时库拦截(interpositioning),多进程共享库例程(虚拟内存支持)。
2.2.1 动态库依赖查询
.interp段:指定使用的动态链接器(如ld-linux.so)。.dynamic段:列出所需动态库名称等信息。- 使用
ldd命令查看可执行文件依赖的共享库。 - 示例输出显示依赖
linux-vdso.so.1,libc.so.6,ld-linux-x86-64.so.2。
2.2.2 共享库构建示例

- 编译选项
-fpic生成位置无关代码,使用gcc -shared构建共享库。 - 示例命令:
gcc -Og -c addvec.c multvec.c -fpic、gcc -shared -o libvector.so addvec.o multvec.o。
2.2.3 加载时动态链接流程

三 总结
- 链接是一种技术,它允许程序由多个目标文件构建而成。
- 链接可在程序生命周期的不同阶段发生生命周期:
- 编译时(程序被编译时)
- 加载时(程序被加载到内存时)
- 运行时(程序正在执行期间)