首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C程序啟動過程反彙編深度解析:從 _start 到 main 的完整鏈路

     編輯:關於C語言

C程序啟動過程反彙編深度解析:從 _start 到 main 的完整鏈路

C 程序的 main 並非真正入口。本文基於 GCC/MinGW、MSVC 6.0、OllyDbg 實測,從編譯鏈接視角剖析 crt 啟動代碼、運行時初始化、參數傳遞、堆棧建立全過程,還原 _start → __libc_start_main → main 完整調用鏈。

工具鏈與測試代碼

// 最小 C 程序
int main() { return 0; }
  • GCC (MinGW) + -nostdlib 觀察鏈接依賴
  • MSVC 6.0 反彙編 mainCRTStartup
  • OllyDbg 動態單步追蹤系統調用

GCC 鏈接視角:啟動文件與符號依賴

# 編譯不鏈接標準庫
gcc main.c -nostdlib -o main.exe
# 錯誤:undefined reference to `__main'
# 說明 main 依賴編譯器內部符號(如 stack checking)

關鍵發現: -nostartfiles 可單獨通過編譯,說明 main 本身不依賴 crt1.o 等啟動文件;但 -nodefaultlibs 大量報錯,證明 main 依賴 libc 內部符號(__main、異常處理框架等)。

標準啟動流程(Linux/glibc 模型)

_kernel_ → _start (crt1.o)
    → __libc_start_main (libc.so)
        → 初始化:堆、線程、I/O、__cxa_atexit
        → __libc_csu_init (調用 .init_array 構造函數)
        → main(argc, argv, envp)
        → __libc_csu_fini (調用 .fini_array 析構函數)
        → exit(main_ret)

MSVC 6.0 反彙編:mainCRTStartup 完整邏輯

mainCRTStartup PROC
    ; 1. 獲取命令行參數
    call    GetCommandLineA
    ; 2. 獲取環境變量
    call    GetEnvironmentStrings
    ; 3. 初始化堆
    call    _heapinit
    ; 4. 初始化 I/O (stdin/stdout/stderr)
    call    _ioinit
    ; 5. 初始化 C++ 全局構造函數
    call    _initterm_e, _initterm
    ; 6. 調用用戶 main
    push    envp
    push    argv
    push    argc
    call    main
    ; 7. 退出處理
    push    eax
    call    exit
mainCRTStartup ENDP

main 函數幀結構(MSVC x86)

_main PROC
    push    ebp              ; 保存舊幀指針
    mov     ebp, esp         ; 建立新幀
    sub     esp, 40h         ; 分配局部變量空間 (64 bytes)
    push    ebx              ; 保存被調用者保存寄存器
    push    esi
    push    edi
    lea     edi, [ebp-40h]   ; 目標地址
    mov     ecx, 10h         ; 計數 16 dwords
    mov     eax, 0CCCCCCCCh  ; 填充模式 (未初始化檢測)
    rep stos dword ptr [edi] ; 填充棧空間
    ; ... 用戶代碼 ...
    xor     eax, eax         ; return 0
    pop     edi              ; 恢復寄存器
    pop     esi
    pop     ebx
    mov     esp, ebp         ; 恢復棧指針
    pop     ebp
    ret                      ; 返回
_main ENDP

OllyDbg 動態追蹤關鍵節點

選項含義效果
-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 // 當前目錄

編譯器運行時初始化職責清單

  1. 堆初始化:malloc/free 可用前提
  2. I/O 子系統:stdin/stdout/stderr 綁定文件描述符 0/1/2
  3. 線程局部存儲 (TLS):__thread/thread_local 變量初始化
  4. C++ 全局構造/析構:.init_array/.fini_array 遍歷調用
  5. 異常處理框架:__cxa_atexit 註冊析構、__gxx_personality_v0 人格函數
  6. 安全檢查:_FORTIFY_SOURCE、棧保護金絲雀、ASLR 協調

嵌入式/裸機環境啟動差異

  • 無操作系統:_start 直接初始化硬件(時鐘、內存控制器、外設)
  • 無標準庫:使用 newlib-nano、picolibc 或自定義 crt0.S
  • 入口函數常命名為 Reset_Handler(ARM Cortex-M)
  • main 前需手動初始化 .data(拷貝初始值)、.bss(清零)

逆向分析實戰檢查清單

  1. [ ] 確認入口點(PE: AddressOfEntryPoint,ELF: e_entry)
  2. [ ] 識別運行時初始化代碼(__libc_start_main/mainCRTStartup)
  3. [ ] 定位 main 真實地址(符號表/特徵碼/交叉引用)
  4. [ ] 分析參數構造(argc/argv/envp 來源)
  5. [ ] 追蹤安全機制(Stack Cookie/SEH/Control Flow Guard)
  6. [ ] 映射用戶代碼控制流圖(CFG)

理解 C 程序啟動全鏈路,不僅是逆向工程的必修課,更能幫助你排查「靜態變量未初始化」「全局構造函數拋出異常」「參數解析錯誤」「堆損壞早期現象」等棘手工程問題。建議在 Linux 下閱讀 glibc/sysdeps/generic/start.S、csu/libc-start.c 源碼,親手編譯一個 -nostdlib 的最小程序,在 GDB 中單步見證每一條指令。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved