execve("/usr/bin/ls", ...) → 内核加载 ELF
文件 → 设置用户空间 → 跳转到入口点。整个过程涉及内核
binfmt_elf、mmap、动态链接器。
一、先看图
flowchart TD
EXECVE[execve] --> BINFMT[binfmt_elf<br/>识别 ELF magic]
BINFMT --> PTLOAD[mmap PT_LOAD 段<br/>.text / .data / .bss]
PTLOAD --> INTERP{有 PT_INTERP?}
INTERP -- 是 --> LDSO[加载 ld.so<br/>动态链接器]
INTERP -- 否 --> ENTRY[跳转到 e_entry<br/>静态链接]
LDSO --> RESOLVE[解析共享库<br/>GOT/PLT 初始化]
RESOLVE --> MAIN[_start → __libc_start_main → main]
classDef kern fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef user fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class EXECVE,BINFMT,PTLOAD,INTERP kern
class LDSO,RESOLVE,MAIN,ENTRY user
二、ELF 结构
ELF Header → Program Headers → Sections
关键 Program Header 类型:
| 类型 | 用途 |
|---|---|
| PT_LOAD | 可加载段(.text, .data) |
| PT_INTERP | 动态链接器路径 |
| PT_DYNAMIC | 动态链接信息 |
| PT_GNU_STACK | 栈属性(NX) |
| PT_GNU_RELRO | 只读重定位 |
三、内核加载
// fs/binfmt_elf.c
static int load_elf_binary(struct linux_binprm *bprm)
{
// 1. 读 ELF header
// 2. 遍历 PT_LOAD → mmap 每个段
// 3. 设置 brk(堆起点)
// 4. 加载 PT_INTERP 指定的 ld.so
// 5. 设置 aux vector
// 6. 跳转到入口点
}四、动态链接器(ld.so)
readelf -l /usr/bin/ls | grep INTERP
# [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]内核加载 ld.so → 跳转到 ld.so 入口 → ld.so 完成:
- 解析
.dynamic段 - 加载所有
DT_NEEDED共享库 - 重定位(填 GOT 表)
- 跳转到程序
_start
五、GOT/PLT 延迟绑定
调用 printf → PLT stub → 第一次跳转到 ld.so resolver → 填 GOT → 后续直接跳
PLT(Procedure Linkage Table)+ GOT(Global Offset Table)实现延迟绑定:
- 首次调用:PLT → resolver → 填 GOT
- 后续调用:PLT → GOT → 直接到目标函数
LD_BIND_NOW=1:启动时立即绑定所有符号(加载慢,运行快)。
六、PIE(Position-Independent Executable)
现代发行版默认编译为 PIE:
file /usr/bin/ls
# ELF 64-bit LSB pie executablePIE + ASLR → 每次加载地址随机 → 增加攻击难度。
七、Auxiliary Vector
内核通过栈传递信息给用户态:
// include/uapi/linux/auxvec.h
AT_ENTRY // 程序入口点
AT_PHDR // Program header 地址
AT_BASE // ld.so 基址
AT_RANDOM // 16 字节随机数
AT_SYSINFO_EHDR // vDSO 地址LD_SHOW_AUXV=1 /usr/bin/true八、安全相关
| 特性 | 保护 |
|---|---|
| ASLR + PIE | 地址随机化 |
| NX Stack | 栈不可执行 |
| RELRO | GOT 只读(Full RELRO) |
| Stack Canary | 栈溢出检测 |
| FORTIFY_SOURCE | 缓冲区溢出检测 |
九、观察
readelf -h /usr/bin/ls # ELF header
readelf -l /usr/bin/ls # Program headers
readelf -d /usr/bin/ls # Dynamic section
ldd /usr/bin/ls # 共享库依赖
# 加载过程
LD_DEBUG=all /usr/bin/ls 2>&1 | head -50
strace -e mmap,mprotect execve /usr/bin/ls十、小结
- execve → binfmt_elf → mmap PT_LOAD → 加载 ld.so → 解析共享库 → main
- GOT/PLT 实现延迟绑定
- PIE + ASLR 提供地址随机化
- aux vector 传递内核信息到用户态
参考文献
fs/binfmt_elf.cman 5 elf- John Levine, “Linkers and Loaders.” 2000
- Drepper, “How To Write Shared Libraries.” 2011
工具
readelflddLD_DEBUGstrace
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内核内存调试
内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。
【操作系统百科】VFS 四层抽象
Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。
操作系统百科
Linux 6.x 视角下的操作系统系列索引:110 篇覆盖调度、虚拟内存、文件系统与 I/O、并发、隔离、可观测性,按主题、阅读路径与关键问题三种入口组织。
【操作系统百科】用户态分配器:jemalloc vs tcmalloc
jemalloc 与 tcmalloc 都想解决多线程分配器的老问题:锁争抢、碎片、RSS 膨胀与回收抖动。但两者把优化重点放在了不同位置:tcmalloc 更激进地把热路径推到 per-CPU,jemalloc 则把 arena、extent、decay 和 profiling 做成了一套更完整的内存治理工具箱。