上篇確立了連接器將 mainCRTStartup 寫入 PE 入口點。但操作系統如何將這個地址變為執行中的指令指針(RIP/EIP)?本文追蹤 **Windows 內核創建進程 → 主執行緒啟動 → CRT 入口** 的完整鏈路。
CreateProcess(或 Shell 執行)NtCreateThreadEx(未公開,ntdll 導出)RtlUserThreadStart (ntdll.dll) —— 這是**所有用戶態執行緒的統一入口**UserThreadStartAddress:用戶指定的執行緒函數(主執行緒為 PE 入口點 mainCRTStartup)UserParameter:傳給執行緒函數的參數(主執行緒通常為 NULL)BaseThreadInitThunk (kernel32.dll)CALL UserThreadStartAddressCALL mainCRTStartup —— **用戶代碼正式開始執行!**內核創建進程
└─ 內核創建主執行緒,EIP = RtlUserThreadStart
└─ RtlUserThreadStart (ntdll)
├─ 建立 SEH 框架
└─ BaseThreadInitThunk (kernel32)
└─ mainCRTStartup (EXE 入口,CRT 初始化)
├─ _initterm(__xi_a, __xi_z) // C++ 全局構造
├─ _initterm_e(__xc_a, __xc_z) // C++ 全局構造 (異常安全)
├─ 初始化 stdio、堆、區域設置、argv/argc/environ
└─ main(argc, argv, envp) // 用戶主函數
└─ ... 用戶代碼 ...
├─ exit(main_ret) / _cexit() // 全局析構、I/O 刷新
└─ ExitProcess() // 進程終止
CreateProcess 只建容器,首個執行緒由系統自動創建並指向 CRT 入口。CreateThread、_beginthreadex、線程池、COM STA,最終都經過它建立 SEH 並跳轉用戶函數。AddressOfEntryPoint 直接成為主執行緒的起始地址。深入 mainCRTStartup 源碼(crt0.c、cinitexe.c 等),逐行拆解:
_heap_init_ioinit_setdefaultprecision、_initterm 全局構造__getmainargs → argc/argvmain 並處理返回值讓我們在 CRT 內部見證「Hello World」如何一步步被準備好。