现在的位置: 首页 > 综合 > 正文

main函数前的秘密

2013年09月09日 ⁄ 综合 ⁄ 共 1589字 ⁄ 字号 评论关闭

    如何让一段代码跑在main()函数前面。一个熟练的C++开发者能够轻易给出答案,即静态初始化。

    除此之外,下面讲一个VC里的方法。

int initBeforeMain()
{
   printf( "initBeforeMain (%p)\r\n", initBeforeMain );
   return 0;
}
int init2BeforeMain()
{
   printf( "init2BeforeMain (%p)\r\n", init2BeforeMain );
   return 0;
}
int initBreak()
{
  DebugBreak();
  return 0;
}
typedef int (*pInit)();
//c初始化化函数指针表
// 把start函数表放在__xi_a and __xi_z之间
#pragma data_seg( ".CRT$XIU" )
    pInit start[] = {initBeforeMain, init2BeforeMain, initBreak, };
#pragma data_seg()
// C++初始化函数指针表
// 把start2函数数组表放在 __xc_a and __xc_z之间
#pragma data_seg( ".CRT$XCU" )
   pInit start2[] = {initBeforeMain, init2BeforeMain, initBreak, };
#pragma data_seg()
// for C
// put following pointer between __xi_a and __xi_z but after start
#pragma data_seg( ".CRT$XIU" )
    pInit start3 = initBreak;
#pragma data_seg()
int main( int argc, char* argv[] ) 
{
    return 0;
}

    在调试器里执行一下,看看效果如何,有没有给你surprise

    解释上面代码之前先说一些背景知识,众所周知,开发者所写的入口函数一般都不是真正的入口,编译器会提供自己的入口函数,再调用开发者的。逻辑很简单,如下:

CRTEntry() 
{ 
   {__CRTInit}; 
   UserEntry(); 
   {__CRTUninit}; 
}

    __CRTInit中做一些初始化工作:包括C、C的初始化函数,C++、C++的初始化函数等。CC++分别有一张表来保存初始化函数指针,每个表又使用2个指针来明确范围,__CRTInit会依次调用这2个表中的函数。

C初始化函数表:[ __xi_a, __xi_z)

C++初始化函数表: [ __xc_a, __xc_z)

现在对照代码注释,就会明白上述那段代码的作用。通过特殊的段名称“.CRT$XIU”,“.CRT$XCU”,链接器会把start表放在“C初始化函数表”中,类似这样,[__xi_a, ..., start, ..., __xi_z).

同理,start2表会被链接器放在“C++初始化函数表”中,象这样,[__xc_a, ..., start2, ..., __xc_z),而最后一个start3变量所对应的指针还是会被放在“C初始化函数表”中,在上面例子中,会放在start之后,[__xi_a, ..., start, ..., start3, ..., __xi_z),这里的start3的位置即函数指针initBreak的位置。

调试结果如下:

可以看见,这个表不小。


C++表也不小

    由于在VS调试器,start3显示的是其值,而非地址,实际上start3的地址是排在start之后的,使用windbg可以观察到。参见 跑在main之前 (2)

Ccall stack


C++的,


如果你在_initterm_e或者_initterm中设置断点的话,会发现,其实它们被调用不止一次,当然,每次的参数pfbeginpfend是不一样的。

抱歉!评论已关闭.