main函数之前执行代码
有的时候,需要会遇到这样的一个情况,“想要在main或者WinMain函数执行前初始化我们的系统框架,和在之后释放我们的系统框架”, 如果这样,我们该怎么做呢?笔者今天放假,来公司继续解决昨天剩余的问题,然后调试exit函数的时候,会发现它会调用
static void __cdecl doexit (int code, int quick,int retcaller)这样的一个内部函数,那么继续单步调试,那么你就会发现
[cpp] view plain copy print ?
-
- function_to_call = (_PVFV)_decode_pointer(*onexitend);
-
-
- *onexitend = (_PVFV)_encoded_null();
-
-
- (*function_to_call)();
-
- onexitbegin_new = (_PVFV *)_decode_pointer(__onexitbegin);
- onexitend_new = (_PVFV *)_decode_pointer(__onexitend);
<textarea style="DISPLAY: none" class="cpp" rows="12" cols="85" name="code"> /* 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); </textarea>
这段代码,OK,我们移上去(function_to_call)看看,你会发现是那些全局类变量的析构调用,那么我们继续往下面走,_initterm(__xp_a, __xp_z); 这个函数, 我第一次看到这个函数的时候,莫名其妙想在里面做文章了。。。OK,我们去看看这个函数内部到底是干什么的
[cpp] view plain copy print ?
-
- typedef void (*_PVFV)(void);
- void _initterm(_PVFV *begin, _PVFV *end)
- {
- _PVFV *cur;
- for (cur = begin; cur < end; cur++)
- {
-
- if (*cur)
- {
- (**cur)();
- }
- }
- }
<textarea style="DISPLAY: none" class="cpp" rows="15" cols="85" name="code">/* 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)(); } } } </textarea>
很容易就可以看到,他就是一个循环遍历,执行每个元素,而仔细看看,那每个元素不就是一个函数指针吗? OK,我们想实现前面所说的功能,该怎么做呢?怎么能够自动往里面添加我们的函数指针呢? 如果能够自由添加我们的函数指针的话,就可以在main和WinMain之前,之后分别调用我们自己的 “初始化”“退出”函数了。那么我们继续在这个源文件中“crt0dat.c”中看看其他代码,或许你会幸运的发现如下代码:
[cpp] view plain copy print ?
- extern _CRTALLOC(".CRT$XIA") _PIFV __xi_a[];
- extern _CRTALLOC(".CRT$XIZ") _PIFV __xi_z[];
- extern _CRTALLOC(".CRT$XCA") _PVFV __xc_a[];
- extern _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[];
- extern _CRTALLOC(".CRT$XPA") _PVFV __xp_a[];
- extern _CRTALLOC(".CRT$XPZ") _PVFV __xp_z[];
- extern _CRTALLOC(".CRT$XTA") _PVFV __xt_a[];
- extern _CRTALLOC(".CRT$XTZ") _PVFV __xt_z[];
<textarea style="DISPLAY: none" class="cpp" rows="8" cols="85" name="code">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 */</textarea>
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:
[cpp] view plain copy print ?
- ......................................
-
- #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))
<textarea style="DISPLAY: none" class="cpp" rows="11" cols="81" name="code">...................................... #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))</textarea>
OK,得到上述的那些信息,我们可以开始尝试了,输入以下代码:
[cpp] view plain copy print ?
- #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");
-
- return 0;
- }
<textarea style="DISPLAY: none" class="cpp" rows="15" cols="81" name="code">#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; } </textarea>
那么,你可以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函数