1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建一个名为 .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

1
2
3
4
5
int __usercall wmainCRTStartup@<eax>(int Code@<esi>)
{
__security_init_cookie();
return _scrt_common_main_seh(Code);
}

跟进 _scrt_common_main_seh 可以看到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
	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

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

1
2
3
4
call    __initterm_e
push offset ___xc_z ; Last
push offset ___xc_a ; First
call __initterm

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

1
2
3
4
5
6
7
8
9
.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中,主要就是通过迭代将数组中的函数全都执行一遍。