為什麼彙編程序入口是 _start,而 C 程序入口是 main?本文通過 gcc 編譯鏈接全過程分析,揭示 crt1.o、crti.o、libc 如何協作完成運行時初始化、調用 main 並處理返回值。
gcc -S main.c # 1. 預處理+編譯 → main.s (彙編)
gcc -c main.s # 2. 彙編 → main.o (目標文件)
gcc main.o # 3. 鏈接 → a.out (可執行文件)
# 實際鏈接命令等價於:
ld /usr/lib/crt1.o /usr/lib/crti.o main.o -lc -dynamic-linker /lib/ld-linux.so.2 -o a.out
$ nm /usr/lib/crt1.o
U main # 未定義,需用戶提供
T _start # 定義:真正入口點
U __libc_start_main # libc 初始化函數
$ nm /usr/lib/crti.o
T _init, _fini # 初始化/終結函數
_start:
push %ebp
mov %esp, %ebp
push $main # 參數 4: main 地址
push $__libc_csu_fini
push $__libc_csu_init
push $0 # 參數 1: argc (稍後填充)
push $0 # 參數 2: argv
push $0 # 參數 3: envp
call __libc_start_main # 不返回
hlt
__libc_start_main(glibc 內部)完成:
argc/argv/envp;_init(構造函數、C++ 全局對象構造);main(argc, argv) 獲取返回值 ret;_fini(析構函數、atexit 註冊函數);exit(ret) 終止進程。__libc_start_main 位於 libc.so.6,**不鏈接進可執行文件**;.plt (Procedure Linkage Table) 存儲存根代碼,首次調用通過 .got.plt 解析真實地址;ld-linux.so.2 完成符號綁定。// 啟動代碼等價邏輯 exit(main(argc, argv));
main 返回 int,傳給 exit();exit() 執行清理(刷新流、調用 atexit)、最終調用 _exit() 系統調用;return -1 → echo $? 輸出 255。# 彙編 hello.s (入口 _start, 無 main)
as hello.s -o hello.o
# 鏈接失敗:缺 main 符號
ld hello.o -o hello # error: undefined reference to 'main'
# 加上 crt1.o 即可(但仍需 main)
ld /usr/lib/crt1.o hello.o -lc -dynamic-linker /lib/ld-linux.so.2 -o hello
crt1.o 提供;main → 處理返回值 → exit;libc 初始化推遲到運行時,節省內存與啟動時間。理解這一流程,對調試啟動崩潰、分析二進制文件、編寫裸機/嵌入式啟動代碼至關重要。