这一章实现了对 PIE(Position-Independent Executable)程序的支持,也就是不加 -static 编译出来的"动态"程序。
问题:PIE 和静态 ELF 的区别
用 musl-gcc 编译一个简单的 hello world:
# 静态(之前一直用的)
musl-gcc -static -O2 -o hello hello.c
# 动态(PIE)
musl-gcc -O2 -o hello_pie hello.c
file 查看:
hello: ELF 64-bit LSB executable, x86-64, statically linked
hello_pie: ELF 64-bit LSB pie executable, x86-64, dynamically linked
区别在于:
| 属性 | 静态 (ET_EXEC) | PIE (ET_DYN) |
|---|---|---|
e_type |
2 |
3 |
| 虚地址 | 固定(如 0x400000) | 从 0 开始,需加载时指定 base |
| 重定位 | 无 | .rela.dyn 表 |
| 外部依赖 | 无 | 通常有(但 musl PIE 已内嵌 libc) |
为什么 musl PIE 没有"真正的"动态链接?
musl-gcc 默认把所有 libc 代码静态链接进去,只是编译为 PIE 格式(支持 ASLR)。所以运行时:
- 无 DT_NEEDED(没有要加载的 .so)
- 重定位只有
R_X86_64_RELATIVE(base + addend)和少量弱符号(__cxa_finalize等,可置0)
这意味着在内核里实现 PIE 支持非常简单,不需要真正的动态链接器。
实现:内核内置"ld.so"
第一步:确定加载基地址
uint64_t base = (ehdr.e_type == ET_DYN) ? PIE_LOAD_BASE : 0;
uint64_t entry = base + ehdr.e_entry;
PIE_LOAD_BASE = 0x400000,加载时所有虚地址都加上这个偏移。
第二步:读取 PT_DYNAMIC,找重定位表
if (phdr.p_type == PT_DYNAMIC)
dynamic_va = base + phdr.p_vaddr;
加载完所有 PT_LOAD 段后,扫描 .dynamic 找 DT_RELA(重定位表地址)和 DT_RELASZ(大小)。
第三步:应用重定位
R_X86_64_RELATIVE: *target = base + addend
R_X86_64_GLOB_DAT: *target = 0 // 弱符号,忽略
就这两种类型,搞定!
结果
hello_pie
hello from PIE!
动态编译的程序在内核上正常运行,不需要 /lib/ld-musl-x86_64.so.1,内核自己完成了链接器的工作。
小结
| 组件 | 改动 |
|---|---|
elf.h |
添加 ET_DYN, PT_DYNAMIC, Elf64Dyn, Elf64Rela 等定义 |
elf.c |
elf_load_into 支持 base 偏移 + 扫描 PT_DYNAMIC + 应用 RELA |
核心思路:musl PIE 的"动态"只是格式,不需要外部库;内核只需处理 R_X86_64_RELATIVE 重定位,3 行代码搞定。