main函数之前执行代码
static void __cdecl doexit (int code, int quick,int retcaller)这样的一个内部函数,那么继续单步调试,那么你就会发现
- /* cache the function to call. */
- function_to_call = (_PVFV)_decode_pointer(*onexitend);
- /* mark the function pointer as visited. */
- *onexitend = (_PVFV)_encoded_null();
- /* call the function, which can eventually change __onexitbegin and __onexitend */
- (*function_to_call)();
- onexitbegin_new = (_PVFV *)_decode_pointer(__onexitbegin);
- onexitend_new = (_PVFV *)_decode_pointer(__onexitend);
这段代码,OK,我们移上去(function_to_call)看看,你会发现是那些全局类变量的析构调用,那么我们继续往下面走,_initterm(__xp_a, __xp_z); 这个函数, 我第一次看到这个函数的时候,莫名其妙想在里面做文章了。。。OK,我们去看看这个函数内部到底是干什么的
- /* from the CRT */
- typedef void (*_PVFV)(void);
- void _initterm(_PVFV *begin, _PVFV *end)
- {
- _PVFV *cur;
- for (cur = begin; cur < end; cur++)
- {
- /* skip NULL pointers */
- if (*cur)
- {
- (**cur)();
- }
- }
- }
很容易就可以看到,他就是一个循环遍历,执行每个元素,而仔细看看,那每个元素不就是一个函数指针吗? OK,我们想实现前面所说的功能,该怎么做呢?怎么能够自动往里面添加我们的函数指针呢? 如果能够自由添加我们的函数指针的话,就可以在main和WinMain之前,之后分别调用我们自己的 “初始化”“退出”函数了。那么我们继续在这个源文件中“crt0dat.c”中看看其他代码,或许你会幸运的发现如下代码:
- extern _CRTALLOC(".CRT$XIA") _PIFV __xi_a[];
- extern _CRTALLOC(".CRT$XIZ") _PIFV __xi_z[]; /* C initializers */
- extern _CRTALLOC(".CRT$XCA") _PVFV __xc_a[];
- extern _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[]; /* C++ initializers */
- extern _CRTALLOC(".CRT$XPA") _PVFV __xp_a[];
- extern _CRTALLOC(".CRT$XPZ") _PVFV __xp_z[]; /* C pre-terminators */
- extern _CRTALLOC(".CRT$XTA") _PVFV __xt_a[];
- extern _CRTALLOC(".CRT$XTZ") _PVFV __xt_z[]; /* C terminators */
MS也给我们写了一点注释, __xi_a[] 到 __xi_z[] 是用于 C语言初始化用的, __xc_a[] 到 __xc_z[] 适用于C++初始化用的,而
__xp_a[] 到 __xp_z[] 适用于C基本库预结束用的, __xt_a[] 到 __xt_z[] 适用于C基本库结束用的。通过这里,我们可以看到那个字符串中的英文字母".CRT$XIA" - ".CRT$XIZ", 我们可以分析出一些简单的规律,
I --> C initialize;
C --> C++ Initialize
P --> C PreUninitialize
T --> CUninitialize
这时候,我们需要查找 _CRTALLOC 宏定义, 通过查找它所引用的头文件,可以看到其中的sect_attribs.h:
- ......................................
- #pragma section(".CRTMP$XCA",long,read)
- #pragma section(".CRTMP$XCZ",long,read)
- #pragma section(".CRTMP$XIA",long,read)
- #pragma section(".CRTMP$XIZ",long,read)
- ......................................
- #define _CRTALLOC(x) __declspec(allocate(x))
OK,得到上述的那些信息,我们可以开始尝试了,输入以下代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- class A
- {
- public:
- A()
- {
- printf("A()\n");
- }
- ~A()
- {
- printf("~A()\n");
- }
- };
- A a;
- bool b = false;
- int foo()
- {
- if (!b)
- {
- printf("init now!\n");
- b = true;
- }
- else
- {
- printf("uninit now\n");
- }
- return 0;
- }
- typedef int (__cdecl *_PVFV)();
- #pragma section(".CRT$XIU", long, read)
- #pragma section(".CRT$XPU", long, read)
- __declspec(allocate(".CRT$XIU")) _PVFV mgt_startup[] = { foo };
- __declspec(allocate(".CRT$XPU")) _PVFV mgt_exit[] = { foo };
- int main()
- {
- printf("main.!\n");
- //exit(1);
- return 0;
- }
那么,你可以Ctrl+F5测试一下, 你可以看到 init A() main() ~A() uninit 这样的顺序,呵呵,总之目标实现啦,其实原理也很简单的,
main函数执行之前主要是初始化系统资源
1、设置栈指针。
2、初始化static静态和global全局变量,即data段内容。
3、将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容。
4、运行全局构造器,估计是C++中构造函数之类的吧
5、将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数