---
title: '加载与启动: _start → main'
url: https://doc.liz6.com/systems-programming/01-elf-and-binary-formats/03-loading-and-startup-start-main
locale: zh
area: systems-programming
tags:
- systems-programming
- ELF与二进制格式
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: ELF 加载过程 → _start → __libc_start_main → main → exit → auxv → AT_* vectors → init/fini arrays → 静态链接 vs 动态链接 适用: glibc (x86-64 / ARM64), musl 对比'
---

# 加载与启动: _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 加载

```mermaid
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 运行

```mermaid
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

```asm
# 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

```c
// 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)

```c
// 内核在 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

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

// 手动遍历:
#include <elf.h>
extern char **environ;  // envp 之后就是 auxv
```

```bash
# 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
```

---

## 调试

```bash
# 追踪启动过程
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*
