C 程序的 main 並非真正入口。本文基於 GCC/MinGW、MSVC 6.0、OllyDbg 實測,從編譯鏈接視角剖析 crt 啟動代碼、運行時初始化、參數傳遞、堆棧建立全過程,還原 _start → __libc_start_main → main 完整調用鏈。
// 最小 C 程序
int main() { return 0; }
-nostdlib 觀察鏈接依賴mainCRTStartup# 編譯不鏈接標準庫 gcc main.c -nostdlib -o main.exe # 錯誤:undefined reference to `__main' # 說明 main 依賴編譯器內部符號(如 stack checking)
| 選項 | 含義 | 效果 |
|---|---|---|
-nostdlib | 不鏈接標準庫+啟動文件 | 需手動提供 _start |
-nostartfiles | 不使用標準啟動文件 | 保留標準庫鏈接 |
-nodefaultlibs | 不使用默認庫 | 需手動指定 -lc -lgcc |
| 地址/階段 | 關鍵動作 | 觀察現象 |
|---|---|---|
| 入口點 | mainCRTStartup | 非 main,OD 顯示 Entry Point 為此 |
GetVersion | 獲取 OS 版本 | 控制台程序返回 DOS 版本信息 |
HeapCreate | 創建進程堆 | 含配對 HeapDestroy |
GetCommandLineA | 獲取命令行字符串 | 解析 argc/argv |
GetEnvironmentStrings | 獲取環境塊 | Unicode → ANSI 轉換 |
調用 main | 用戶代碼入口 | 堆棧已就緒,參數已壓棧 |
// 內核傳遞給 _start 的棧佈局 (Linux x86-64) argc: 8(%rsp) argv: 16(%rsp) // char*[] envp: 16(%rsp) + 8*argc + 8 // char*[] 以 NULL 結尾 // Windows PEB (Process Environment Block) 關鍵字段 PEB->ProcessParameters->CommandLine // UNICODE_STRING PEB->ProcessParameters->Environment // 環境塊指針 PEB->ProcessParameters->CurrentDirectory // 當前目錄
malloc/free 可用前提stdin/stdout/stderr 綁定文件描述符 0/1/2__thread/thread_local 變量初始化.init_array/.fini_array 遍歷調用__cxa_atexit 註冊析構、__gxx_personality_v0 人格函數_FORTIFY_SOURCE、棧保護金絲雀、ASLR 協調_start 直接初始化硬件(時鐘、內存控制器、外設)newlib-nano、picolibc 或自定義 crt0.SReset_Handler(ARM Cortex-M)main 前需手動初始化 .data(拷貝初始值)、.bss(清零)AddressOfEntryPoint,ELF: e_entry)__libc_start_main/mainCRTStartup)main 真實地址(符號表/特徵碼/交叉引用)理解 C 程序啟動全鏈路,不僅是逆向工程的必修課,更能幫助你排查「靜態變量未初始化」「全局構造函數拋出異常」「參數解析錯誤」「堆損壞早期現象」等棘手工程問題。建議在 Linux 下閱讀 glibc/sysdeps/generic/start.S、csu/libc-start.c 源碼,親手編譯一個 -nostdlib 的最小程序,在 GDB 中單步見證每一條指令。