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
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。