// 创建一个名为 .CRT$XCU 的节
#pragma section(".CRT$XCU", read, write)
// 定义一个函数指针类型
typedef void(__cdecl* FPTR)(void);
// 定义一个函数,它将在 main 之前运行
// 添加__declspec(noinline)确保函数不被优化
__declspec(noinline) void my_init_function() {
	volatile PVOID hVEH = AddVectoredExceptionHandler(0, VEH_SIGNLE_STEP);
	if (!hVEH) {
		std::cerr << "Failed to add VEH handler." << std::endl;
		exit(1);
	}
}
// 在 .CRT$XCU 节中put my_init_function 的地址
__declspec(selectany) __declspec(allocate(".CRT$XCU"))
FPTR init_ptr = my_init_function;
// 使用一个引用来确保 init_ptr 不会被优化掉
void* force_init_ptr_ref = &init_ptr;

如上代码,函数 my_init_function 在 main 函数之前执行。


MSVC 编译器的 CRT 入口点通常为 wmainCRTstartup / mainCRTstartup

int __usercall wmainCRTStartup@<eax>(int Code@<esi>)
{
  __security_init_cookie();
  return _scrt_common_main_seh(Code);
}

跟进 _scrt_common_main_seh 可以看到如下代码

	call    __initterm_e
	pop     ecx
	pop     ecx
	test    eax, eax
	jz      short loc_401E02
	mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
	mov     eax, 0FFh
	jmp     loc_401EF1
loc_401E02:
    push    offset ___xc_z  ; Last
    push    offset ___xc_a  ; First
    call    __initterm

这部分代码有大部分冗余,重要的是以下部分

call    __initterm_e
push    offset ___xc_z  ; Last
push    offset ___xc_a  ; First
call    __initterm

___xc_a___xc_z 分别为初始化函数指针的头尾地址

.rdata:00403138 ___xc_a         dd 0                    ; DATA XREF: __scrt_common_main_seh+75↑o
.rdata:0040313C ; void (__cdecl *pre_cpp_initializer)()
.rdata:0040313C pre_cpp_initializer dd offset pre_cpp_initialization
.rdata:00403140 ; void (__cdecl *init_ptr)()
.rdata:00403140 ?init_ptr@@3P6AXXZA dd offset ?my_init_function@@YAXXZ
.rdata:00403140                                         ; DATA XREF: .data:void * force_init_ptr_ref↓o
.rdata:00403140                                         ; my_init_function(void)
.rdata:00403144 ; void (__cdecl *__xc_z[])()
.rdata:00403144 ___xc_z         dd 0                    ; DATA XREF: __scrt_common_main_seh:loc_401E02↑o

可以看到, 连同语言初始化函数 pre_cpp_initializer , 我们自定义的 init_ptr也在里面


__initterm 是将函数指针压栈后调用它们的过程,逻辑在ucrtbase.dll中,主要就是通过迭代将数组中的函数全都执行一遍。