标签 · ELF

23 篇文章,记录每一次探索。

[链接器的世界-实验篇14] 接住 rustc:把链接器变成真正的驱动程序
为累积的 class13 静态 PIE 后端实现命令行驱动,让 rustc 直接调用它,链接一个使用 core::fmt 的 no_std Rust 程序。
阅读全文
[链接器的世界-实验篇13] 地址也有语义:安全 ICF 与代码顺序
在 class12 的 ET_EXEC 与静态 PIE 后端上实现保守 ICF 和代码顺序,重定向引用并修正折叠副本的调试信息。
阅读全文
[链接器的世界-实验篇12] 让调试信息跟着地址走:DWARF、符号表与节头
为 ET_EXEC 与静态 PIE 补全调试节、符号表和节头表,区分图像地址、调试节偏移与 TLS 偏移。
阅读全文
[链接器的世界-实验篇11] 看穿间接层:GOT、指令松弛与静态 TLS
在 class9 的静态 PIE 上构建 GOT,实施课程支持的 mov 松弛,布局 TLS 模板,并用提供的启动代码安装主线程 TLS。
阅读全文
[链接器的世界-实验篇10] 不再原样复制:常量合并与展开表重建
在 class8 的 ET_EXEC 后端中合并重复常量,映射输入偏移,并为存活代码重建 .eh_frame 与搜索索引。
阅读全文
[链接器的世界-实验篇09] 不知道加载地址时怎么链接:自重定位的静态 PIE
在 class8 前端与 class6 布局之上生成静态 PIE,区分静态补丁和加载期指针,并通过提供的启动代码完成自重定位。
阅读全文
[链接器的世界-实验篇08] 只留下被用到的:可达性回收与链接映射
从入口和显式根追踪存活节,建立不依赖输出地址的链接计划,并输出确定、字节安全的 map。
阅读全文
[链接器的世界-实验篇07] 静态库按需取用:成员抽取的不动点
静态库是一组目标文件的容器,而不是一个更大的目标文件。本实验解析 ar 归档格式,按未解析的全局名字逐个抽取成员,并在同一个归档内反复扫描直到不再有新成员被选中。
阅读全文
[链接器的世界-原理篇09] TLS:同一个变量,每个线程各有一份
thread_local 变量在每个线程里是同一个名字、不同的地址。这个地址由编译器、链接器和动态加载器合作算出:.tdata/.tbss 与 PT_TLS 是模板,%fs 和 TPIDR_EL0 指向每个线程自己的那一份,四种访问模型在通用和快之间取舍,链接器在知道更多信息时把慢序列原地改写成快序列。本文用 clang、ld.lld 和 GNU ld 的真实输出走一遍全过程,最后解释"cannot allocate memory in static TLS block"的根因。
阅读全文
[链接器的世界-原理篇08] 栈展开:沿调用栈找到回去的路
函数一层套一层地调用,返回地址散落在栈上。打印调用栈、抛出 C++ 异常时,运行时要一层层退回调用者,靠的是编译器和汇编器写进 .eh_frame 的一张"每条指令处怎么找回调用者"的表。本章用真实目标文件逐字节拆开 CIE/FDE,讲 CFI 状态机、personality 与 LSDA、两阶段展开,以及链接器为 .eh_frame 和 .eh_frame_hdr 要做的事。
阅读全文
[链接器的世界-原理篇07] 动态链接:把最后的地址留给运行时
共享库的加载地址要到运行时才知道,它引用的符号甚至可能被别的库替换。链接器在链接时能做的,是把所有"现在还填不了"的地址集中到少数几处,再给动态链接器 ld.so 写一份说明书。本文从 PIC、GOT、PLT 讲到几类动态重定位、惰性绑定、RELRO、符号插入、符号版本和 ld.so 的启动顺序,全部配真实的目标文件与链接输出。
阅读全文
[链接器的世界-实验篇06] 文件里的字节与内存里的字节:数据、BSS 与段权限
只有代码的程序几乎没有用。本实验加入只读常量、已初始化全局变量、BSS 和 COMMON,用三个权限不同的加载段描述它们,并让内核替我们检查:写只读数据、执行数据段都会收到 SIGSEGV。
阅读全文
[链接器的世界-原理篇06] 从入口到 main:程序启动之前发生了什么
链接器写完程序头表就退场了,剩下的事归加载器。本文先读 xv6 的 kexec,看一个最小的加载器只用哪几个字段、做哪三道检查;再读 Linux 的 load_elf_binary,看 PT_INTERP、PT_GNU_STACK、基址选择、.bss 的页尾清零与匿名页、按需调页和 brk;然后打印初始进程栈和辅助向量,跟着 musl 的源码从 _start 走到 main,手工复现驱动程序加的五个启动文件,构建一个 static-pie 看 rcrt1.o 怎样自己处理 R_X86_64_RELATIVE;最后在真实内核上改坏 ELF,看加载器怎样守住安全边界。
阅读全文
[链接器的世界-实验篇05] 跨越文件的调用:合并代码,再填写位移
上一阶段的可执行文件只能来自一个没有重定位的目标文件。本实验让汇编写的入口调用另一个文件里用 C 编译的函数:合并多个代码节,计算每个字段的 S+A-P,并保证失败时不留下半写的字节。
阅读全文
[链接器的世界-原理篇05] 布局与 GC:给每个节找位置,让无用代码退场
同一个目标文件,用不同的链接器、不同的选项链接,填进机器码的数字都不一样。这些数字取决于布局:输入节怎么并成输出节、输出节怎么分进段、段放在哪个地址、哪些节被垃圾回收删掉。本文用 GNU ld 和 lld 的真实输出,讲清楚程序头的每个字段、文件偏移与虚拟地址为什么要同余、-z separate-code、PIE 与基址、--gc-sections 的标记过程、__start_/__stop_ 符号,以及链接器脚本和映射文件。
阅读全文
[链接器的世界-实验篇04] 第一个能运行的 ELF:两张程序头与一个入口
前四个阶段只在读输入。本实验第一次写输出:规划一个最小的可执行映像,手工序列化 ELF 头和两个程序头,让 Linux 内核直接运行它并返回 42。
阅读全文
[链接器的世界-原理篇04] 重定位:一次调用,四个字节的距离
从一个真正能运行的 Linux 程序出发,追踪 call 指令里的四个字节:输入节偏移如何变成 P,CPU 为什么从下一条指令算起,S+A-P 又如何落成机器码。再逐步进入负位移、绝对指针、不同加数、溢出和链接期指令改写。
阅读全文
[链接器的世界-实验篇03] 同一个名字,谁说了算:全局符号解析
先选择跨文件的全局定义,再绑定实际引用;保持局部身份、弱定义顺序和 COMMON 的大小与对齐要求,不在解析阶段分配地址。
阅读全文
[链接器的世界-原理篇03] 符号解析:同一个名字,究竟指向谁
从一个能独立运行的加法程序开始,亲手制造缺失定义、重复定义和库顺序错误。沿着“谁需要、谁提供、提供者是否进入链接”理解符号解析,再讨论弱定义、common、类型检查与 C++ 的边界。
阅读全文
[链接器的世界-实验篇02] 符号与重定位记录:目标文件留下的待办事项
解码符号和显式加数重定位,建立字符串表、符号表与被修补节之间的归属关系,保留后续选择与布局所需信息。
阅读全文
[链接器的世界-原理篇02] 拆开目标文件:一份等待组装的程序
从一个 main.c、一个 add.c 和一份手写汇编出发,看汇编器怎样按伪指令把字节摆进各个节,再用 llvm-objdump 和几十行 Python 一个字节一个字节拆开 ELF 目标文件:ELF 头、节头表、程序头表、节的类型与标志、符号表、机器码里留下的零字节和重定位表。
阅读全文
[链接器的世界-原理篇01] 地址不能总靠手填:链接器的演化之路
从 1949 年 EDSAC 的子程序库讲起,沿着"一个问题催生一个发明"的线索,看重定位、汇编器、目标文件、ELF、共享库、DWARF、.eh_frame、COMDAT、可执行栈标记和四代链接器是怎么一个接一个出现的,以及它们怎样把链接器推到今天的位置。
阅读全文
[链接器的世界-原理篇00] 从一条编译命令出发:程序如何连成整体
从一个正常的跨文件函数调用出发,拆开编译器驱动的预处理、编译、汇编与链接过程,再借类型不一致的程序辨认链接器的信息边界。沿着目标文件、可执行文件与内存映射,建立符号解析、布局、重定位以及编译、链接、加载、运行四个时期的联系,并提供统一的 Linux 本机实验环境。
阅读全文