本页目录

加载与启动: _start → main

覆盖: ELF 加载过程 → _start → _libc_start_main → main → exit → auxv → AT* vectors → init/fini arrays → 静态链接 vs 动态链接 适用: glibc (x86-64 / ARM64), musl 对比

概述

一个 C 程序的入口不是 main——而是 glibc 的 _start。在 main 被调用之前,内核已经做了大量工作(ELF 解析、栈布置、auxv 传递),glibc 也完成了自己的初始化(TLS、vDSO 解析、stdio 缓冲、__attribute__((constructor)))。理解这条路径对于调试"程序还没到 main 就 crash"至关重要。

内核侧: ELF 加载

flowchart TD
    START["execve() → load_elf_binary()"]

    START --> VALIDATE["🔍 验证 ELF header<br/>magic + class + machine + ABI"]

    VALIDATE --> PHDR["读 program header table"]

    PHDR --> LOAD["遍历每个 PT_LOAD"]
    LOAD --> MAP["elf_map() → mmap(segment)<br/>映射代码和数据到内存"]

    MAP --> INTERP{"PT_INTERP<br/>存在?"}

    INTERP -->|"是 (动态链接)"| LD["加载 ld.so<br/>设置 ld.so 入口地址"]
    LD --> RET_LD["内核返回到 ld.so 入口<br/>(非程序的 _start)"]

    INTERP -->|"否 (静态链接)"| ENTRY["设置 e_entry"]
    ENTRY --> RET_START["内核返回到程序的 _start"]

    classDef start fill:#e3f2fd,stroke:#1565c0
    classDef step fill:#f3e5f5,stroke:#7b1fa2
    classDef decision fill:#fff3e0,stroke:#ef6c00
    classDef done fill:#e8f5e9,stroke:#2e7d32
    class START start
    class VALIDATE,PHDR,LOAD,MAP step
    class INTERP decision
    class LD,ENTRY,RET_LD,RET_START done

动态链接: ld.so 运行

flowchart TD
    START["内核返回到 ld.so entry"] --> BOOT["① ld.so 自举 (bootstrap)<br/>ld.so 自己是动态链接的<br/>先有限制地重定位自己"]

    BOOT --> DYNAMIC["② 读主程序 .dynamic"]
    DYNAMIC --> NEEDED["DT_NEEDED → libc.so.6, libfoo.so, ..."]
    DYNAMIC --> DEBUG["DT_DEBUG → 设置 debug 结构<br/>(gdb 读取)"]

    NEEDED --> DEPS["③ _dl_map_object_deps()<br/>拓扑排序 → 逐个 mmap .so"]

    DEPS --> RELOC["④ _dl_relocate_object()<br/>符号解析 + 重定位<br/>填充 GOT, 解析 IFUNC"]

    RELOC --> INIT["⑤ 调库的 init 函数<br/>DT_INIT / DT_INIT_ARRAY<br/>→ __libc_init_first()"]

    INIT --> START_["⑥ 调主程序入口<br/>e_entry → _start"]

    classDef start fill:#e3f2fd,stroke:#1565c0
    classDef step fill:#f3e5f5,stroke:#7b1fa2
    classDef done fill:#e8f5e9,stroke:#2e7d32
    class START start
    class BOOT,DYNAMIC,NEEDED,DEBUG,DEPS,RELOC,INIT step
    class START_ done

用户态: _start

# glibc sysdeps/x86_64/start.S
_start:
    xorl  %ebp, %ebp          # 标记最外层帧 (ebp=0 → 回溯终点)
    movq  %rdx, %r9           # rdx=destructor function (rtld_fini)
    popq  %rsi                # argc
    movq  %rsp, %rdx          # argv
    andq  $-16, %rsp          # 16 字节栈对齐 (ABI 要求)
    pushq %rax                # 对齐 filler
    pushq %rsp                # stack_end (用于 __libc_stack_end)
    xorl  %r8d, %r8d          # 第 8 个参数 (glibc internal flag)
    call __libc_start_main    # → libc 初始化 → main
    hlt                       # 永不到达 (main 通过 exit 返回)

__libc_start_main

// csu/libc-start.c (简化)
int __libc_start_main(
    int (*main)(int, char **, char **),  // main 函数
    int argc,
    char **argv,
    void (*init)(void),                   // __libc_csu_init
    void (*fini)(void),                   // __libc_csu_fini
    void (*rtld_fini)(void),              // ld.so 清理函数
    void *stack_end)
{
    // 1. 设置 TLS (线程局部存储)
    __pthread_initialize_minimal();

    // 2. 设置栈保护 (canary)
    __stack_chk_guard = _dl_setup_stack_chk_guard();

    // 3. 读 auxv → 初始化全局变量:
    //    __environ = envp;
    //    __libc_stack_end = stack_end;
    //    _dl_phdr = AT_PHDR;
    //    _dl_phnum = AT_PHNUM;
    //    __page_size = AT_PAGESZ;

    // 4. 调用 init → __libc_csu_init()
    //    → 遍历 .init_array → 执行 __attribute__((constructor)) 函数
    //    → 包括: _init() (如果存在)
    (*init)(argc, argv, envp);

    // 5. 调用 main
    result = (*main)(argc, argv, __environ);

    // 6. main 返回 → exit(result)
    exit(result);
}

auxv (Auxiliary Vector)

// 内核在 execve 时构建, 放在栈上 envp 之后
// 格式: { type, value } pairs, 终止于 AT_NULL

// 关键 AT_* types:
AT_PHDR:       program header 地址 (用于 dl_iterate_phdr)
AT_PHENT:      program header entry size
AT_PHNUM:      program header 数量
AT_PAGESZ:     页大小 (4KB / 16KB / 64KB)
AT_BASE:       ld.so 的加载基址
AT_ENTRY:      _start 的地址
AT_UID / AT_EUID / AT_GID / AT_EGID: 进程凭证
AT_SECURE:     是否需要安全模式 (setuid → 1)
AT_SYSINFO_EHDR: vDSO 地址
AT_RANDOM:     16 bytes of random data (用于 ASLR, stack canary)
AT_PLATFORM:   CPU 平台字符串 ("x86_64")

读取 auxv

#include <sys/auxv.h>
unsigned long hwcap = getauxval(AT_HWCAP);  // CPU feature flags

// 手动遍历:
#include <elf.h>
extern char **environ;  // envp 之后就是 auxv
# LD_SHOW_AUXV: 让 ld.so 打印 auxv
LD_SHOW_AUXV=1 /bin/true

静态链接对比

静态链接:
  - 无 PT_INTERP → 内核直接跳转到 _start
  - _start 仍然调用 __libc_start_main (链接到 libc.a)
  - 但无动态链接 → 无 PLT/GOT → 无 lazy binding
  - 整个 libc 链接进去 → 二进制巨大 (~800KB for hello world)
  - 但启动极快 (无符号解析, 无重定位)

musl 的静态链接非常流行 (容器世界):
  - musl 从 _start → __libc_start_main 到 main 只需 ~100 行代码
  - 比 glibc 的启动路径短 ~10x

调试

# 追踪启动过程
strace -e trace=mmap,open,read /bin/true

# ld.so 调试输出
LD_DEBUG=all /bin/true 2>&1 | head -100

# 查看 auxv
LD_SHOW_AUXV=1 /bin/true
cat /proc/self/auxv | xxd

# 查看 init_array
readelf -x .init_array /bin/ls

# 静态分析 startup 路径
gdb /bin/ls
(gdb) break _start
(gdb) run

参考

  • glibc 源码⁠: sysdeps/x86_64/start.S, csu/libc-start.c, elf/dl-start.c, elf/rtld.c
  • musl 源码⁠: crt/crt1.c, ldso/dlstart.c — 比 glibc 简单 10x, 学习推荐
  • 内核源码⁠: fs/binfmt_elf.c — load_elf_binary
  • LWN: "How programs get run", "The init and fini arrays"

关键词: _start, __libc_start_main, auxv, AT_PHDR, vDSO, .init_array, constructor, TLS, ld.so bootstrap, PT_INTERP