顯示具有 g_obj_dtor 標籤的文章。 顯示所有文章
顯示具有 g_obj_dtor 標籤的文章。 顯示所有文章

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

2012年9月16日 星期日

c++ runtime - dtor (1) 實作篇

在原理篇之後, 我們來實作如何喚起 global object dtor。

cpp_abi.h
 1 __asm__(".code16gcc\n");
 2 
 3 #ifndef CPP_ABI_H
 4 #define CPP_ABI_H
 5 
 6 #define DOBJS_SIZE 3
 7 
 8 struct DObjs
 9 {
10   void (*dtor_) (void *);
11   void *arg_;
12   void *dso_handle_;
13 };
14 
15 extern "C"
16 {
17   int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle);
18   void g_dtors(void);
19 }
20 
21 #endif

cpp_abi.h 補上需要的資訊, struct DObjs, 用來紀錄要調用呼叫的 dtor。

 6 #define DOBJS_SIZE 3

我宣告 size 為 3 的 DObjs array, 目前要用到的只有 io, ab 這兩個 globle object, 正確實作的話, 應該要去知道有幾個 global objects, 不過這裡我們就偷懶一下, 寫死就好。

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

則是要資訊紀錄在 DObjs 裡頭, 把每一個 dtor, this pointer, 紀錄下來,  __dso_handle 沒有用到, 我目前還搞不定 share objects。

void g_dtors(void);
呼叫每一個 global object dtor (依照建構時相反的順序)

參考以下 cpp_abi.cpp 實作。

cpp_abi.cpp
 1 #include "cpp_abi.h"
 2 
 3 void *__dso_handle;
 4 
 5 static DObjs dobjs[DOBJS_SIZE];
 6 int obj_count=0;
 7 
 8 void print_str(const char   *s);
 9 void s16_print_int(int i, int radix);
10 
11 extern "C"
12 {
13 
14   int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle)
15   {
16     __asm__ __volatile__("xchg %bx, %bx");
17     print_str("atexit\r\n");
18     s16_print_int(obj_count, 10);
19     print_str("\r\n");
20 
21     dobjs[obj_count].dtor_ = destructor;
22     dobjs[obj_count].arg_ = arg;
23     dobjs[obj_count].dso_handle_ = __dso_handle;
24     ++obj_count;
25     return 0;
26   }
27   void g_dtors(void)
28   {
34     for (int i=obj_count-1 ; i >= 0 ; --i)
35     {
37       dobjs[i].dtor_(dobjs[i].arg_);
39     }
41   }
42 }

再來的程式就簡單了,  L84 就是呼叫 g_dtors, 直接把之前紀錄的 dtor 一個一個執行, 就完成在退出 cpp_main 時執行 global object 的解構。

整個 global objects ctor/dtor 的實作就是這樣, 沒有那麼神祕了吧!

cpp_init.S
  1 # init c++ runtime
  2 
  3 #define DOS
  4 
 30 .code16
 31 .extern __bss_start__
 32 .extern __bss_end__
 33 .extern __start_ctors
 34 .extern __end_ctors
 35 .extern cpp_main
 36 .extern obj_count
 37 
 38 
 39 .text
 40   jmp _start
 41 .global _start
 42 _start:
 43   xchg %bx, %bx
 44 #if 1
 45   mov %cs, %ax
 46   mov %ax, %ds
 47   mov %ax, %ss
 48 
 49 #endif
 
 73 
 74   call init_bss_asm # in dos need not init bss by myself
 
 80   # call _GLOBAL__I_XX invoke global object ctor, 
 81   # in dos environment use 16 or 32 bit address? look like 32 bit
 82   calll init_cpp_global_asm
 83   calll cpp_main
 84   calll g_dtors
 85 #  call disp_str2
 86   mov     $0x4c00, %ax
 87   int     $0x21   # 回到 DOS
 88 


109 
110 # call global object ctor
111 init_cpp_global_asm:
112 #if __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
113   mov $__end_global_ctor__, %edi    /* Destination */
114   mov $__start_global_ctor__, %esi   /* Source */
115 #else
116   mov $__end_ctors, %edi    /* Destination */
117   mov $__start_ctors, %esi   /* Source */
118 #endif
119   jmp 2f
120 1:
121   mov %esi, %ebx 
122   calll *(%ebx)
123   add $4, %esi
124 2:
125   cmp %edi, %esi
126   jne 1b
127   ret
128 
129 # init bss
130 init_bss_asm:
131   movw $__bss_end__, %di    /* Destination */
132   movw $__bss_start__, %si   /* Source */
133   movw %ds, %bx
134   movw %bx, %es
135   jmp 2f
136 1:
137   mov $0, %eax
138   movw %si, %ax
139   movb $0x0, %es:(%eax)
140   add $1, %si
141 
142 
143   # wait key
144 #  xor %eax,%eax
145 #  int $0x16
146 
147   
148 2:
149   cmpw %di, %si
150   jne 1b
151 
152   ret
153 

再來要聊聊 local static object。

完整程式碼:
simple os: git clone git@github.com:descent/simple_os.git
git branch: cpp_runtime
git commit: 92e0183c722166c84da8b28aa11fd8afeff0ff6c
directory: simple_os/cpp_runtime/global_object/dos_cpp