计算系统安全速成之链接:理解程序的构建过程【7】

文章目录

  • 说明
  • [一 链接:理解程序的构建过程](#一 链接:理解程序的构建过程)
    • [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 链接器的优势

  1. 模块化:程序可以拆分为多个小的源文件,支持构建通用函数库(如数学库、标准C库),提高代码组织性和可维护性

  1. 时间效率:支持独立编译,修改一个源文件后只需重新编译该文件并重新链接,不需要重新编译其他未修改的文件,支持多文件并发编译。

  1. 空间效率:库函数集中管理。
    • 静态链接:可执行文件仅包含实际使用的库代码
    • 动态链接:可执行文件不包含库代码,运行时共享同一份库代码,节省内存空间

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,引用 sumarray
  • sum.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 函数起始地址:0x4004d0
    • sum 函数起始地址:0x4004e8
    • 调用指令使用 PC 相对寻址:callq 4004e8 = 0x4004e3 + 0x5
  • 数据地址修正:mov $0x601018,%edi%edi = &arrayarray 被重定位到 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.cmultvec.c 实现
  • 主程序 main2.c 调用 addvec
  • 头文件 vector.h 声明接口

链接流程

  • 编译生成 main2.oaddvec.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 -fpicgcc -shared -o libvector.so addvec.o multvec.o

2.2.3 加载时动态链接流程

三 总结

  • 链接是一种技术,它允许程序由多个目标文件构建而成。
  • 链接可在程序生命周期的不同阶段发生生命周期:
    • 编译时(程序被编译时)
    • 加载时(程序被加载到内存时)
    • 运行时(程序正在执行期间)
相关推荐
3824278279 小时前
汇编:宏汇编、宏库
汇编
38242782710 小时前
汇编:条件汇编、
前端·汇编·数据库
white-persist10 小时前
【攻防世界】reverse | simple-check-100 详细题解 WP
c语言·开发语言·汇编·数据结构·c++·python·算法
时空自由民.11 小时前
stm32FXX系列MCU汇编启动文件分析
汇编·stm32·单片机
切糕师学AI11 小时前
ARM 汇编指令:STR
汇编·arm开发
38242782712 小时前
IA-32汇编:MOV r/m16,sreg指令、LAHF指令、ALIGN指令、LABEL 指令、TYPEDEF指令解析
汇编
38242782715 小时前
汇编:字符串的输入
汇编
38242782716 小时前
8086 CPU汇编伪操作汇总
汇编
渡我白衣18 小时前
C++可变参数队列与压栈顺序:从模板语法到汇编调用约定的深度解析
c语言·汇编·c++·人工智能·windows·深度学习·硬件架构