关于GUN C析构函数的那些事

简介:

    GNU C编译器在编译PE文件时,构造了称为.dtos和.ctors的特殊表格项,分别对应

于构造和析构函数,注意这个和C++里的构造函数不太一样,这里的函数是C语言中全

局型的构造和析构函数,我们这里只看析构函数dtos。

static void clear(void) __attribute__((destructor));

void clear(void)
{
	puts("bye hopy!");
}

int main(void)
{
	puts("get ready!");
	printf("clear : %p\n",clear);
	getchar();
	return 0;
}

    书中称编译后的PE文件中会出现.dtors段,也许在linux系统上是这样,但在windows

中没有这个段。那么在win下__attribute__((destructor))或者称dtos机制如何实现呢?

    使用windbg快速载入exe,在clear设置断点,然后g,中断后可以看到堆栈调用链:

0:000:x86> k
ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
0028fed4 004018ae image00000000_00400000+0x133b
0028fee4 7716c3e9 image00000000_00400000+0x18ae
0028ff18 771737df msvcrt!_cinit+0xc1
0028ff28 004010c0 msvcrt!_cexit+0xb
0028ff68 00401284 image00000000_00400000+0x10c0
0028ff88 753933aa image00000000_00400000+0x1284
0028ff94 77ce9ef2 kernel32!BaseThreadInitThunk+0xe
0028ffd4 77ce9ec5 ntdll32!__RtlUserThreadStart+0x70
0028ffec 00000000 ntdll32!_RtlUserThreadStart+0x1b

可以看到最后一个追溯到的“知名”函数为msvcrt!_cexit,同样的有4个类似的函数,附带

简单说一下:

void __cdecl exit ( int status )
{
        doexit(status, 0, 0); /* full term, kill process */
}
 
void __cdecl _exit ( int status)
{
        doexit(status, 1, 0); /* quick term, kill process */
}
 
void __cdecl _cexit ( void )
{
        doexit(0, 0, 1);    /* full term, return to caller */
}
 
void __cdecl _c_exit ( void )
{
        doexit(0, 1, 1);    /* quick term, return to caller */
}


A:exit函数先进行清理工作(比如析构处理、关闭所有标准IO流),然后利

用main 函数返回的status 来终结当前进程;

B:_exit 函数用于快速终结进程,它并不进行那些“高层次”的清理
C: _cexit 同exit 函数一样执行清理,它并不终结进程
D: _c_exit 同_exit 一样执行清理,它并不终结进程


通俗的说exit 是 _exit 的安全增强版,_cexit是_c_exit 的安全增强版。不过从它们的

实现上看,本质上都是 doexit 函数在起作用。在doexit 的内部负责进行各种清理,

然后再终结进程或者返还控制权给程序。不难看出_cexit完成的是全析构,并且返回

调用者。在VS2010的\VC\crt\src路径中可以在crt0dat.c中找到它的定义:

static void __cdecl doexit (
        int code,
        int quick,
        int retcaller
        )
{
#ifdef _DEBUG
        static int fExit = 0;
#endif  /* _DEBUG */

#ifdef CRTDLL
        if (!retcaller && check_managed_app())
        {
            /*
               Only if the EXE is managed then we call CorExitProcess.
               Native cleanup is done in .cctor of the EXE
               If the Exe is Native then native clean up should be done
               before calling (Cor)ExitProcess.
            */
            __crtCorExitProcess(code);
        }
#endif  /* CRTDLL */

        _lockexit();        /* assure only 1 thread in exit path */
        __TRY

        if (_C_Exit_Done != TRUE) {
            _C_Termination_Done = TRUE;

            /* save callable exit flag (for use by terminators) */
            _exitflag = (char) retcaller;  /* 0 = term, !0 = callable exit */

            if (!quick) {

                /*
                 * do _onexit/atexit() terminators
                 * (if there are any)
                 *
                 * These terminators MUST be executed in reverse order (LIFO)!
                 *
                 * NOTE:
                 *  This code assumes that __onexitbegin points
                 *  to the first valid onexit() entry and that
                 *  __onexitend points past the last valid entry.
                 *  If __onexitbegin == __onexitend, the table
                 *  is empty and there are no routines to call.
                 */

                _PVFV * onexitbegin = (_PVFV *) DecodePointer(__onexitbegin);
                if (onexitbegin) {
                    _PVFV * onexitend = (_PVFV *) DecodePointer(__onexitend);
                    _PVFV function_to_call = NULL;

                    /* save the start and end for later comparison */
                    _PVFV * onexitbegin_saved = onexitbegin;
                    _PVFV * onexitend_saved = onexitend;

                    while (1)
                    {
                        _PVFV * onexitbegin_new = NULL;
                        _PVFV * onexitend_new = NULL;

                        /* find the last valid function pointer to call. */
                        while (--onexitend >= onexitbegin && *onexitend == _encoded_null())
                        {
                            /* keep going backwards. */
                        }

                        if (onexitend < onexitbegin)
                        {
                            /* there are no more valid entries in the list, we are done. */
                            break;
                        }

                        /* cache the function to call. */
                        function_to_call = (_PVFV) DecodePointer(*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 *) DecodePointer(__onexitbegin);
                        onexitend_new = (_PVFV *) DecodePointer(__onexitend);

                        if ( ( onexitbegin_saved != onexitbegin_new ) || ( onexitend_saved != onexitend_new ) )
                        {
                            /* reset only if either start or end has changed */
                            onexitbegin = onexitbegin_saved = onexitbegin_new;
                            onexitend = onexitend_saved = onexitend_new;
                        }
                    }
                }
#ifndef CRTDLL
                /*
                 * do pre-terminators
                 */
                _initterm(__xp_a, __xp_z);
#endif  /* CRTDLL */
            }

#ifndef CRTDLL
            /*
             * do terminators
             */
            _initterm(__xt_a, __xt_z);
#endif  /* CRTDLL */

#ifdef _DEBUG
            /* Dump all memory leaks */
            if (!fExit && _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_LEAK_CHECK_DF)
            {
                fExit = 1;
#ifndef CRTDLL
                __freeCrtMemory();
                _CrtDumpMemoryLeaks();
#endif  /* CRTDLL */
            }
#endif  /* _DEBUG */

        }
        /* return to OS or to caller */

        __FINALLY
            if (retcaller)
                _unlockexit();      /* unlock the exit code path */
        __END_TRY_FINALLY

        if (retcaller)
            return;


        _C_Exit_Done = TRUE;

        _unlockexit();      /* unlock the exit code path */

        __crtExitProcess(code);
}

简单分析可知,如果是非quick的调用(_cexit正好如此),则会从__onexitbegin

和__onexitend中抽出每个_onexit 或者 atexit 中已经注册了的函数,以LIFO的方

式逆序执行。那么我们上面提到的dtos功能是否在里面被执行呢?实践看一下吧:

77173340 0f839490ffff    jae     msvcrt!_cinit+0xb6 (7716c3da)           [br=1]
msvcrt!_cinit+0xb6:
7716c3da 8b45e4          mov     eax,dword ptr [ebp-1Ch] ss:002b:0028fefc=005b0eac
msvcrt!_cinit+0xb9:
7716c3dd 8b00            mov     eax,dword ptr [eax]  ds:002b:005b0eac=00401898
msvcrt!_cinit+0xbb:
7716c3df 85c0            test    eax,eax
msvcrt!_cinit+0xbd:
7716c3e1 0f84526f0000    je      msvcrt!_cinit+0xad (77173339)           [br=0]
msvcrt!_cinit+0xbf:
7716c3e7 ffd0            call    eax {image00000000_00400000+0x1898 (00401898)}
image00000000_00400000+0x1898:
00401898 55              push    ebp
image00000000_00400000+0x1899:
00401899 89e5            mov     ebp,esp
image00000000_00400000+0x189b:
0040189b 83ec08          sub     esp,8
image00000000_00400000+0x189e:
0040189e a108704000      mov     eax,dword ptr [image00000000_00400000+0x7008 (00407008)] ds:002b:00407008=004064bc
image00000000_00400000+0x18a3:
004018a3 8b00            mov     eax,dword ptr [eax]  ds:002b:004064bc=0040133b
image00000000_00400000+0x18a5:
004018a5 85c0            test    eax,eax
image00000000_00400000+0x18a7:
004018a7 741a            je      image00000000_00400000+0x18c3 (004018c3) [br=0]
image00000000_00400000+0x18a9:
004018a9 8d7600          lea     esi,[esi]
image00000000_00400000+0x18ac:
004018ac ffd0            call    eax {image00000000_00400000+0x133b (0040133b)}
image00000000_00400000+0x18ae:
004018ae a108704000      mov     eax,dword ptr [image00000000_00400000+0x7008 (00407008)] ds:002b:00407008=004064bc
image00000000_00400000+0x18b3:
004018b3 8d5004          lea     edx,[eax+4]
image00000000_00400000+0x18b6:
004018b6 891508704000    mov     dword ptr [image00000000_00400000+0x7008 (00407008)],edx ds:002b:00407008=004064bc
image00000000_00400000+0x18bc:
004018bc 8b4004          mov     eax,dword ptr [eax+4] ds:002b:004064c0=00000000
image00000000_00400000+0x18bf:
004018bf 85c0            test    eax,eax
0:000:x86> p
image00000000_00400000+0x18c1:
004018c1 75e9            jne     image00000000_00400000+0x18ac (004018ac) [br=0]
image00000000_00400000+0x18c3:
004018c3 c9              leave
image00000000_00400000+0x18c4:
004018c4 c3              ret

见第11行,很明显调用了地址为0x401898的函数,这个函数被联编在exe中,可以查

查他是干嘛滴的,用gdb载入(因为我使用gcc编译的该exe),同样在clear设断点,

中断后看栈回调链:

(gdb) bt
#0  clear () at D:/WORK/SRC/c_src/cl_src/ccon/main.c:11
#1  0x004018ae in __do_global_dtors () at ../mingw/gccmain.c:32
#2  0x7716c3e9 in msvcrt!isspace () from C:\Windows\syswow64\msvcrt.dll
#3  0x771737df in msvcrt!_cexit () from C:\Windows\syswow64\msvcrt.dll
#4  0x00000000 in ?? ()

可以看到其为gcc库中的__do_global_dtors函数,查找对应的源代码可以清楚地的看

到它做了类似的抽取函数并且调用的操作,只不过它支持的是gcc本身的dtos功能:

typedef void (*func_ptr) (void);
extern func_ptr __CTOR_LIST__[];
extern func_ptr __DTOR_LIST__[];

void
__do_global_dtors (void)
{
  static func_ptr *p = __DTOR_LIST__ + 1;
  /**
   * Call each destructor in the destructor list until a null pointer
   * is encountered.
   */
  while (*p)
    {
      (*(p)) ();
      p++;
    }
}

现在谜底揭晓鸟:GUN C中的dtos机制,是由gcc编译器调用__do_global_dtors函

数,msvcrt的_onexit或atexit函数回调的帮助下完成的。可以看汇编单步运行的

第29行,清楚可以看到调用了clear函数。


参考资料:

从Entry Point到main函数调用

gccmain.c源代码



相关文章
|
3月前
|
C++
C++析构函数定义为virtual虚函数,有什么作用?
C++析构函数定义为virtual虚函数,有什么作用?
28 0
|
5月前
|
C++
53 C++ - 虚析构函数
53 C++ - 虚析构函数
19 0
为什么要把父类的析构函数定义成虚函数?
为什么要把父类的析构函数定义成虚函数?
|
1月前
|
存储 编译器 C语言
【C++】类和对象之构造函数、析构函数、拷贝构造函数(二)
【C++】类和对象之构造函数、析构函数、拷贝构造函数(二)
|
2月前
this函数和析构函数
this函数和析构函数
9 0
|
3月前
|
编译器 C++
第九章:C++构造函数和析构函数详解
第九章:C++构造函数和析构函数详解
42 1
|
17天前
|
C++
[C++/PTA] 派生类使用基类的成员函数
[C++/PTA] 派生类使用基类的成员函数
40 0
|
4月前
|
编译器 C语言 C++
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解(上)
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解
|
4月前
|
编译器 程序员 C++
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解(下)
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解
|
10月前
|
存储 Java 编译器