这一章实现了对 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 段后,扫描 .dynamicDT_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 行代码搞定。