上篇介紹了進程地址空間佈局,本篇聚焦 從輸入 ./program 到 main 執行前 內核完成的所有工作。
fork():複製父進程地址空間、文件描述符表、信號處理等exec 將繼續運行父進程代碼int execve(const char *pathname, char *const argv[], char *const envp[]);
/* 內核入口 */
int do_execve(const char *pathname, char *const argv[], char *const envp[],
struct pt_regs *regs);
pt_regs:保存用戶態寄存器(EIP, ESP, EAX...),因 Linux 系統調用用寄存器傳參,進入內核前須保存regs 後續用於構建用戶棧、設置返回地址struct linux_binprm {
char buf[BINPRM_BUF_SIZE]; // 文件頭 128 字節(魔數、架構判斷)
struct page *page[MAX_ARG_PAGES]; // 參數/環境變量字符串頁
struct mm_struct *mm; // 新進程內存描述符
unsigned long p; // 當前用戶棧頂
struct file *file; // 可執行文件 struct file
int argc, envc; // 參數/環境變量個數
char *filename, *interp; // 文件名/解釋器名
/* ... capability, security ... */
};
do_execve 填充此結構,後續格式處理器使用。
buf 中 ELF 魔數 0x7F 'E' 'L' 'F'load_elf_binary()flush_old_exec)mmap):.text .rodata .data .bsssetup_arg_pages 映射參數/環境變量頁 → create_elf_tables 壓入 argc, argv, envp, auxv高地址 +------------------------+ | 環境變量字符串 | +------------------------+ | argv 字符串 | +------------------------+ | auxv (AT_* 向量) | +------------------------+ | envp[] 指針數組 | <-- envp +------------------------+ | NULL | +------------------------+ | argv[] 指針數組 | <-- argv +------------------------+ | argc | <-- 栈底對齊處 +------------------------+ | ... 對齊填充 ... | 低地址 (初始 ESP)
#define start_thread(regs, new_eip, new_esp) do { \
set_fs(USER_DS); \
regs->xds = regs->xes = regs->xss = USER_DS; \
regs->xcs = USER_CS; \
regs->eip = new_eip; /* ELF 入口點 _start */ \
regs->esp = new_esp; /* 用戶棧頂 */ \
} while (0)
new_eip = elf_entry(通常是 _start,C 運行時啟動代碼)new_esp = bprm->p(棧頂)iret 返回用戶態,CPU 從 _start 開始執行_start:
xor ebp, ebp # 標記幀鏈結束
pop esi # argc
mov ecx, esp # argv
and esp, -16 # 16 字節對齊
push eax # glibc 內部用
push esp # auxv
push edx # envp
call __libc_start_main
__libc_start_main 執行:初始化 libc、調用構造函數、註冊析構函數、最後 exit(main(argc,argv,envp))。
./program │ ▼ fork() → 子進程 │ ▼ execve() → do_execve() │ ▼ load_binprm() 填充 linux_binprm │ ▼ search_binary_handler() → load_elf_binary() │ ├─ 釋放舊地址空間 │ ├─ mmap 映射 ELF 段 │ └─ 構建用戶棧 (argc/argv/envp/auxv) │ ▼ start_thread(regs, elf_entry, stack_top) │ ▼ iret → 用戶態 _start │ ▼ __libc_start_main → main()
# 靜態分析 readelf -h program # ELF 頭、入口點 readelf -l program # Program Headers、加載地址 # 動態追蹤 strace -f ./program # 觀察 execve、mmap、brk gdb ./program (gdb) break _start (gdb) run (gdb) info registers (gdb) x/20x $esp # 觀察初始棧 argc/argv
可執行文件加載是內核與動態鏈接器協作的精密過程:格式識別 → 內存映射 → 棧構建 → 上下文切換。理解此流程,對逆向分析、啟動優化、容器技術、嵌入式引導開發均有核心價值。