2012年9月17日 星期一

c++ runtime - dtor (0) 原理篇

有正就有反, 有男就有女, 有前就有後, 東西南北, 凸與凹, 扯太多了, 有建構函式必有解構函式, 接續 ctor 的議題:
_GLOBAL__I_io call
_Z41__static_initialization_and_destruction_0ii

_Z41__static_initialization_and_destruction_0ii 除了呼叫 ctor 之外, 還為 dtor 做了準備。g++ 使用 __cxa_atexit 來註冊每一個 dtor, 所以 _Z41__static_initialization_and_destruction_0ii 還呼叫了 __cxa_atexit。

14  1df:   66 b8 3c 03 00 00       mov    $0x33c,%eax
15  1e5:   67 66 c7 44 24 08 6c    addr32 movl $0x106c,0x8(%esp)
16  1ec:   10 00 00 
17  1ef:   67 66 c7 44 24 04 60    addr32 movl $0x1060,0x4(%esp)
18  1f6:   10 00 00 
19  1f9:   67 66 89 04 24          addr32 mov %eax,(%esp)
20  1fe:   66 e8 84 ff ff ff       calll  188 <__cxa_atexit>

__cxa_atexit function prototype:

int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle)

  1. L14, 0x33c 就是 <_ZN2IoD1Ev> (Io 的 destructor),
  2. L17, $0x1060 則是 global io object 的位址 (in .bss section, use readelf -a cppb.elf to see), Io::~Io() 要把自己傳進去, 就是 this 指標, 所以會需要這個參數。
  3. L15, $0x106c 自然是 __dso_handle。
因為有兩個 dtor, 所以 __cxa_atexit 呼叫了兩次。

dtor
 1 
 2 000001b4 <_Z41__static_initialization_and_destruction_0ii>:
 3  1b4:   66 55                   push   %ebp
 4  1b6:   66 89 e5                mov    %esp,%ebp
 5  1b9:   66 83 ec 18             sub    $0x18,%esp
 6  1bd:   67 66 83 7d 08 01       addr32 cmpl $0x1,0x8(%ebp)
 7  1c3:   75 7d                   jne    242 <_Z41__static_initialization_and_destruction_0ii+0x8e>
 8  1c5:   67 66 81 7d 0c ff ff    addr32 cmpl $0xffff,0xc(%ebp)
 9  1cc:   00 00 
10  1ce:   75 72                   jne    242 <_Z41__static_initialization_and_destruction_0ii+0x8e>
11  1d0:   67 66 c7 04 24 60 10    addr32 movl $0x1060,(%esp)
12  1d7:   00 00 
13  1d9:   66 e8 e1 00 00 00       calll  2c0 <_ZN2IoC1Ev>
14  1df:   66 b8 3c 03 00 00       mov    $0x33c,%eax
15  1e5:   67 66 c7 44 24 08 6c    addr32 movl $0x106c,0x8(%esp)
16  1ec:   10 00 00 
17  1ef:   67 66 c7 44 24 04 60    addr32 movl $0x1060,0x4(%esp)
18  1f6:   10 00 00 
19  1f9:   67 66 89 04 24          addr32 mov %eax,(%esp)
20  1fe:   66 e8 84 ff ff ff       calll  188 <__cxa_atexit>
21  204:   67 66 c7 44 24 04 07    addr32 movl $0x7,0x4(%esp)
22  20b:   00 00 00 
23  20e:   67 66 c7 04 24 64 10    addr32 movl $0x1064,(%esp)
24  215:   00 00 
25  217:   66 e8 c1 01 00 00       calll  3de <_ZN2AbC1Ei>
26  21d:   66 b8 3c 04 00 00       mov    $0x43c,%eax
27  223:   67 66 c7 44 24 08 6c    addr32 movl $0x106c,0x8(%esp)
28  22a:   10 00 00 
29  22d:   67 66 c7 44 24 04 64    addr32 movl $0x1064,0x4(%esp)
30  234:   10 00 00 
31  237:   67 66 89 04 24          addr32 mov %eax,(%esp)
32  23c:   66 e8 46 ff ff ff       calll  188 <__cxa_atexit>
33  242:   66 c9                   leavel 
34  244:   66 c3                   retl   
35 
36 
37 0000033c <_ZN2IoD1Ev>:
38  33c:   66 55                   push   %ebp
39  33e:   66 89 e5                mov    %esp,%ebp
40  341:   66 83 ec 18             sub    $0x18,%esp
41  345:   67 66 c7 44 24 04 4d    addr32 movl $0x104d,0x4(%esp)
42  34c:   10 00 00 
43  34f:   67 66 8b 45 08          addr32 mov 0x8(%ebp),%eax
44  354:   67 66 89 04 24          addr32 mov %eax,(%esp)
45  359:   66 e8 05 00 00 00       calll  364 <_ZN2Io5printEPKc>
46  35f:   66 c9                   leavel 
47  361:   66 c3                   retl   
48  363:   90                      nop
49 
50 
51 0000043c <_ZN2AbD1Ev>:
52  43c:   66 55                   push   %ebp
53  43e:   66 89 e5                mov    %esp,%ebp
54  441:   66 83 ec 18             sub    $0x18,%esp
55  445:   67 66 c7 44 24 04 4d    addr32 movl $0x104d,0x4(%esp)
56  44c:   10 00 00 
57  44f:   67 66 8b 45 08          addr32 mov 0x8(%ebp),%eax
58  454:   67 66 89 04 24          addr32 mov %eax,(%esp)
59  459:   66 e8 05 00 00 00       calll  464 <_ZN2Ab5printEPKc>
60  45f:   66 c9                   leavel 
61  461:   66 c3                   retl   
62  463:   90                      nop

再來就是在 __cxa_atexit 裡頭把應該要存的東西存起來, 在適當的時候呼叫他們 (ex: 離開 c++ main()), 就完成解構了。

什麼是要存的東西?
  • dtor
  • dtor argument (this pointer)
  • __dso_handle
這是兩種 dtor 的實作方式之一, 另外一種 g++ 新版已經不用了, 就是使用 .dtor section 來儲存每個 dtor function, 類似 ctor section,  然後一個一個 call 他們就好了, 說來簡單, 不過可能實際上的實作並不容易, 才會改用這種方式。

以上的 objdump 結果來自下面的程式碼。

cppb.cpp
 1 __asm__(".code16gcc\n");
 2 #include "io.h"
 3 
 4 /*
 5  * c bootloader
 6  */
 7 
 8 //#define POINTER_TEST
 9 
10 
11 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx");
12 
13 
14 Io io;
15 Ab ab(7);
16 
17 extern int _start_ctors;
18 extern int _end_ctors;
19 
20 extern "C" void WinMain(void)
21 {
22 
23   BOCHS_MB  
24   #if 1
25   //__asm__ ("mov  %cs, %ax\n");
26   //__asm__ ("mov  %ax, %ds\n");
27   //__asm__ ("mov  %ax, %ss\n");
28 
29 #if 0
30   int ctor_addr_start = _start_ctors;
31   int ctor_addr_end = _end_ctors;
32     typedef void (*FuncPtr)();
33     FuncPtr fp = (FuncPtr)(ctor_addr_start);
34     fp();
35 #endif
36 
37 /* Test for GCC > 3.2.0 
38  * ref: http://gcc.gnu.org/onlinedocs/gcc-3.4.6/cpp/Common-Predefined-Macros.html
39   #if __GNUC__ > 3 || \
40   (__GNUC__ == 3 && (__GNUC_MINOR__ > 2 || \
41   (__GNUC_MINOR__ == 2 && \
42   __GNUC_PATCHLEVEL__ > 0))
43 */
44 
45   #if __GNUC__ >= 4 && __GNUC_MINOR__ <= 4
46   extern void _GLOBAL__I_io();
47   _GLOBAL__I_io();
48   #else // for 4.7
49   extern void _GLOBAL__sub_I_io();
50   _GLOBAL__sub_I_io();
51   #endif
52     #if 0
53   for (int i = ctor_addr_start; i < ctor_addr_end ; ++i)
54   {
55   }
56 
57 #endif
58   //__asm__ ("mov  $0xfff0, %sp\n");
59   {
60   const char *ver=__VERSION__;
61   io.print("hello cpp class\r\n");
62   io.print("g++ version: ");
63   io.print(ver);
64   io.print("\r\n");
65   }
66   #if 0
67   unsigned char *vb = (unsigned char *)0xb8000;
68   *vb = 'A';
69   *(unsigned char *)0xb8001 = 0xc;
70   *(unsigned char *)0xb8002 = 'B';
71   *(unsigned char *)0xb8003 = 0x9;
72   *(unsigned char *)0xb8004 = '@';
73   *(unsigned char *)0xb8005 = 0xc;
74   #endif
75   //while(1);
76   __asm__ ("mov     $0x4c00, %ax\n");
77   __asm__ ("int     $0x21\n"); //   回到 DOS
78 
79   #endif
80 }
81 
82 
83 
84 void *__dso_handle;
85 extern "C"
86 {
87 int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle)
88 {
89   io.print("__cxa_atexit\r\n");
90   return 0;
91 }
92 void __cxa_finalize(void *d)
93 {
94   io.print("hello dtor\r\n");
95 }
96 }

io.h
 1 __asm__(".code16gcc\n");
 2 #ifndef IO_H
 3 #define IO_H
 4 
 5 class Io
 6 {
 7   public:
 8     Io();
 9     ~Io();
10     void print(const char   *s);
11   private:
12     const char *str_;
13 };
14 
15 class Ab
16 {
17   public:
18     Ab(int i);
19     ~Ab();
20     void print(const char   *s);
21   private:
22     const char *str_;
23     int i_;
24 };
25 
26 #endif

io.cpp
 1 #include "io.h"
 2 Io::Io():str_("data member\r\n")
 3 {
 4   print("ctor\r\n");
 5   print(str_);
 6 }
 7 Io::~Io()
 8 {
 9   print("dtor\r\n");
10 }
11 
12 void Io::print(const char   *s)
13 {
14   while(*s)
15   {
16     __asm__ __volatile__ ("int  $0x10" : : "a"(0x0E00 | *s), "b"(7));
17     s++;
18   }
19 }
20 
21 Ab::Ab(int i)
22 {
23   print("Ab::ctor\r\n");
24   i_ = i ; 
25 }
26 
27 Ab::~Ab()
28 {
29   print("dtor\r\n");
30 }
31 
32 void Ab::print(const char   *s)
33 {
34   while(*s)
35   {
36     __asm__ __volatile__ ("int  $0x10" : : "a"(0x0E00 | *s), "b"(7));
37     s++;
38   }
39 }

ref:
http://hilananufi.serw5.com/global-object-destructor-c.php

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。