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

2012年10月7日 星期日

c++ runtime - local static object (0)

env: g++ 4.7.1

local static object 比起 global object 複雜多了 (因為 global object 要做的事情 local static object 都要做), 我真的還要花心力在這上面嗎?我猶豫了, 很怕這是個無底洞。畢竟我想完成的是 os, 沒有一定要用 c++ 來完成。除了參考 osdev 上的文章之外, 幾乎都靠自己使用 objdump, nm, readelf 這些工具辛苦的查探。看著我不熟悉的組合語言, 這真的很辛苦, 而為了不讓辛苦的查探白費, 我又得更辛苦的紀錄下來。

而說實在的, 除了滿足我自己的求知慾望之外, 似乎對我沒什麼幫助, 我又不會寫 c++ compiler。不過像驢子般的耐力似乎是我的優點之一, 最後我還是決定繼續下去, 又由於我已經搞清楚了, 所以再 blog 一篇。若你願意看這篇文章, 那真是讓我高興, 我的文筆並不那麼優秀, 所以我知道這並不是很好讀。

以下是相關 c++ 程式碼:

cppb.cpp
 1 __asm__(".code16gcc\n");
 2 
 3 #include "io.h"
 4 #include "obj.h"
 5 
 6 typedef signed char s8;
 7 typedef signed short s16;
 8 typedef signed int s32;
 9 
10 typedef unsigned char u8;
11 typedef unsigned short u16;
12 typedef unsigned int u32;
13 
14 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx");
15 
16 void s16_print_int(int i, int radix);
17 void print_str(const char   *s);
18 
19 //Io io;
20 
21 extern "C" int cpp_main(void)
22 {
23   BOCHS_MB
24   static Io io;
25 
26   return 0;
27 }

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 #if 0
22     dobjs[0].dtor_ = destructor;
23     dobjs[0].arg_ = arg;
24     dobjs[0].dso_handle_ = __dso_handle;
25 #else
26     //if (obj_count >= DOBJS_SIZE)
27       //return -1;
28      #if 1
29     dobjs[obj_count].dtor_ = destructor;
30     dobjs[obj_count].arg_ = arg;
31     dobjs[obj_count].dso_handle_ = __dso_handle;
32     #endif
33     ++obj_count;
34     //print_str("__cxa_atexit: register dtor here\r\n");
35 #endif
36     return 0;
37   }
38   void g_dtors(void)
39   {
40     print_str("g_dtors\r\n");
41     s16_print_int(obj_count, 10);
42     print_str("\r\n");
43     //print_str("g_dtors\r\n");
44     #if 1
45     for (int i=obj_count-1 ; i >= 0 ; --i)
46     {
47       //print_str("xx g_dtors\r\n");
48       //dobjs[i].dtor_(dobjs[i].arg_);
49       dobjs[i].dtor_(0);
50     }
51     #endif
52   }
53 }
54 
55 
56 int __cxa_guard_acquire()
57 {
58   return 1;
59 }
60 
61 int __cxa_guard_release()
62 {
63   return 1;
64 }

static object 除了和 global object 一樣要自己補上

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

還需要補上

int __cxa_guard_acquire()
int __cxa_guard_release()

ref: cpp_abi.cpp L56 ~ L64, function prototype 我是亂寫的 (其實是錯的), 不過這不是這篇的重點, 化繁為簡才容易搞懂。

objdump
 1 000001cc <cpp_main>:
 2  1cc:   66 55                   push   %ebp
 3  1ce:   66 89 e5                mov    %esp,%ebp
 4  1d1:   66 83 ec 18             sub    $0x18,%esp
 5  1d5:   87 db                   xchg   %bx,%bx
 6  1d7:   66 b8 c0 07 00 00       mov    $0x7c0,%eax
 7  1dd:   67 66 0f b6 00          movzbl (%eax),%eax
 8  1e2:   84 c0                   test   %al,%al
 9  1e4:   75 5a                   jne    240 <cpp_main+0x74>
10  1e6:   67 66 c7 04 24 c0 07    movl   $0x7c0,(%eax,%eax,1)
11  1ed:   00 00 
L11 在 bochs 反組譯是這樣 $0x000007c0, %ss:(%esp) 比較正確, 傳個參數給 __cxa_guard_acquire
12  1ef:   66 e8 c3 02 00 00       calll  4b8 <__cxa_guard_acquire>
13  1f5:   66 85 c0                test   %eax,%eax
14  1f8:   0f 95 c0                setne  %al
15  1fb:   84 c0                   test   %al,%al
16  1fd:   74 41                   je     240 <cpp_main+0x74>
17  1ff:   67 66 c7 04 24 c8 07    movl   $0x7c8,(%eax,%eax,1)
18  206:   00 00 
19  208:   66 e8 3e 00 00 00       calll  24c <_ZN2IoC1Ev>
20  20e:   67 66 c7 04 24 c0 07    movl   $0x7c0,(%eax,%eax,1)
21  215:   00 00 
22  217:   66 e8 aa 02 00 00       calll  4c7 <__cxa_guard_release>
23  21d:   67 66 c7 44 24 08 e0    movl   $0x7e0,0x8(%eax,%eax,1)
24  224:   07 00 00 
25  227:   67 66 c7 44 24 04 c8    movl   $0x7c8,0x4(%eax,%eax,1)
26  22e:   07 00 00 
27  231:   67 66 c7 04 24 a0 02    movl   $0x2a0,(%eax,%eax,1)
28  238:   00 00 
29  23a:   66 e8 24 01 00 00       calll  364 <__cxa_atexit>
30  240:   66 b8 00 00 00 00       mov    $0x0,%eax
31  246:   66 c9                   leavel 
32  248:   66 c3                   retl   

12  1ef:   66 e8 c3 02 00 00       calll  4b8 <__cxa_guard_acquire>
22  217:   66 e8 aa 02 00 00       calll  4c7 <__cxa_guard_release>

這便是我們補上的那兩個 function。只要宣告了 local static object, g++ 便會為我們生成產生調用呼叫這兩個 function 的程式碼。哇!真是邪惡, 我們應當習慣這件事情了。

12  1ef:   66 e8 c3 02 00 00       calll  4b8 <__cxa_guard_acquire>
13  1f5:   66 85 c0                test   %eax,%eax
14  1f8:   0f 95 c0                setne  %al
15  1fb:   84 c0                   test   %al,%al
16  1fd:   74 41                   je     240 <cpp_main+0x74>
17  1ff:   67 66 c7 04 24 c8 07    movl   $0x7c8,(%eax,%eax,1)
18  206:   00 00 
19  208:   66 e8 3e 00 00 00       calll  24c <_ZN2IoC1Ev> 

從這裡可以看出會判斷 int __cxa_guard_acquire() 的傳回值 (compare %eax, function 的傳回值會紀錄在 %eax, L13 ~ 16), 所以要 return 1 才會 call L19 的 Io::Io(), 我一開始 return 0, 結果 Io::Io() 一直沒被呼叫, 看了反組譯的組合語言之後才理解。

我不貼執行結果了, 如果你跟著我的一系列文章到這裡, 應該可以自己完成測試, 如果不行, 我相信你應該會問我如何做這樣的測試, 我很樂意回答這樣的問題。

static object 這樣就結束了嗎? 當然沒這麼簡單, 這只是能讓 g++ 編譯器編譯過而已, 行為不是正確的, 還會有《c++ runtime - static object (1)》。

2012年10月6日 星期六

c++ runtime - local static object (1)

test environment:
linux 32bit (ubuntu  64 bit chroot debian 32 bit)
g++ 4.7.1

c++ runtime - static object (0) 那篇後, 我們可以 compile local static object,  接著用以下的程式碼來測試 local static object。

cppb.cpp
 1 __asm__(".code16gcc\n");
 2 #include "io.h"
 3 #include "obj.h"
 4 #include "type.h"
 5 
 6 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx");
 7 
 8 void s16_print_int(int i, int radix);
 9 void print_str(const char   *s);
10 void test_static_obj();
11 
12 
13 extern "C" int cpp_main(void)
14 {
15   //BOCHS_MB
16   test_static_obj();
17   test_static_obj();
18 
19   return 0;
20 }
21 
22 void test_static_obj()
23 {
24   static Io io;
25   // for compare static object
26   //Io io;
27 }

來看看結果:
git commit:  68191b2311d900492397c2456ea0b0fbd9b66e8f



Io::Io() 畫面上的 Io cotr: 字串竟然被喚起了兩次, dtor 也是, 這不就和 local object 一樣了嗎?和 c++ 定義的 static object 只能有一次的初始化動作 (喚起一次 ctor) 似乎相違背。一定是有哪裡搞錯了, 我們合理懷疑是 __cxa_guard_acquire(), __cxa_guard_release() 這兩個 function 寫錯了。

原本的寫法:

56 int __cxa_guard_acquire()
57 {
58   return 1;
59 }
60 
61 int __cxa_guard_release()
62 {
63   return 1;
64 }

改成這樣:
1 int __cxa_guard_acquire(u32 *myself)
2 {
3   return !(*((char*)myself));
4 }
5 
6 void __cxa_guard_release(u32 *myself)
7 {
8   *((char*)myself) = 1;
9 }

結果就正常了。



Io:Io() 只會被喚起一次,這是什麼樣的魔法呢?組合語言面前沒有秘密, 一如往常, 我們來看看反組譯的結果, 以下是兩份反組譯的程式碼, 對照著看幫助理解, 一份是 g++ -S 產生, 一份是 objdump 產生。

看 void test_static_obj() 的部份就可以了。

開始苦工了 ...

objdump -d -m i8086 cppb.elf
  1 
  2 cppb.elf:     file format elf32-i386
  3 
  4 
  5 Disassembly of section .text:
  6 
  7 00000100 <_start-0x3>:
  8  100: e9 00 00              jmp    103 <_start>
  9 
 10 00000103 <_start>:
 11  103: 87 db                 xchg   %bx,%bx
 12  105: 8c c8                 mov    %cs,%ax
 13  107: 8e d8                 mov    %ax,%ds
 14  109: 8e d0                 mov    %ax,%ss
 15  10b: b8 00 b8              mov    $0xb800,%ax
 16  10e: 8e e8                 mov    %ax,%gs
 17  110: b0 ef                 mov    $0xef,%al
 18  112: 66 e8 7e 00 00 00     calll  196 <disp_al>
 19  118: b0 12                 mov    $0x12,%al
 20  11a: 66 e8 76 00 00 00     calll  196 <disp_al>
 21  120: 8b 1e 84 09           mov    0x984,%bx
 22  124: 66 e8 ae 00 00 00     calll  1d8 <disp_bx>
 23  12a: b0 aa                 mov    $0xaa,%al
 24  12c: 66 e8 64 00 00 00     calll  196 <disp_al>
 25  132: e8 40 00              call   175 <init_bss_asm>
 26  135: 8b 1e 84 09           mov    0x984,%bx
 27  139: 66 e8 99 00 00 00     calll  1d8 <disp_bx>
 28  13f: 66 e8 11 00 00 00     calll  156 <init_cpp_global_asm>
 29  145: 66 e8 a1 00 00 00     calll  1ec <cpp_main>
 30  14b: 66 e8 0a 04 00 00     calll  55b <g_dtors>
 31  151: b8 00 4c              mov    $0x4c00,%ax
 32  154: cd 21                 int    $0x21
 33 
 58 

103 000001ec <cpp_main>:
104  1ec: 66 55                 push   %ebp
105  1ee: 66 89 e5              mov    %esp,%ebp
106  1f1: 66 83 ec 08           sub    $0x8,%esp
107  1f5: 66 e8 10 00 00 00     calll  20b <_Z15test_static_objv>
108  1fb: 66 e8 0a 00 00 00     calll  20b <_Z15test_static_objv>
109  201: 66 b8 00 00 00 00     mov    $0x0,%eax
110  207: 66 c9                 leavel 
111  209: 66 c3                 retl   
112 
113 0000020b <_Z15test_static_objv>:
114  20b: 66 55                 push   %ebp
115  20d: 66 89 e5              mov    %esp,%ebp
116  210: 66 83 ec 18           sub    $0x18,%esp
117  214: 66 b8 60 09 00 00     mov    $0x960,%eax
118  21a: 67 66 0f b6 00        movzbl (%eax),%eax
119  21f: 84 c0                 test   %al,%al
120  221: 75 5a                 jne    27d <_Z15test_static_objv+0x72>
121  223: 67 66 c7 04 24 60 09  movl   $0x960,(%eax,%eax,1)
122  22a: 00 00 
123  22c: 66 e8 d3 03 00 00     calll  605 <__cxa_guard_acquire>
124  232: 66 85 c0              test   %eax,%eax
125  235: 0f 95 c0              setne  %al
126  238: 84 c0                 test   %al,%al
127  23a: 74 41                 je     27d <_Z15test_static_objv+0x72>
128  23c: 67 66 c7 04 24 68 09  movl   $0x968,(%eax,%eax,1)
129  243: 00 00 
130  245: 66 e8 39 00 00 00     calll  284 <_ZN2IoC1Ev>
131  24b: 67 66 c7 04 24 60 09  movl   $0x960,(%eax,%eax,1)
132  252: 00 00 
133  254: 66 e8 da 03 00 00     calll  634 <__cxa_guard_release>
134  25a: 67 66 c7 44 24 08 80  movl   $0x980,0x8(%eax,%eax,1)
135  261: 09 00 00 
136  264: 67 66 c7 44 24 04 68  movl   $0x968,0x4(%eax,%eax,1)
137  26b: 09 00 00 
138  26e: 67 66 c7 04 24 2c 03  movl   $0x32c,(%eax,%eax,1)
139  275: 00 00 
140  277: 66 e8 1b 02 00 00     calll  498 <__cxa_atexit>
141  27d: 66 c9                 leavel 
142  27f: 66 c3                 retl   
143  281: 00 00                 add    %al,(%bx,%si)
144  ...
145 
 
373 
374 00000605 <__cxa_guard_acquire>:
375  605: 66 55                 push   %ebp
376  607: 66 89 e5              mov    %esp,%ebp
377  60a: 66 83 ec 18           sub    $0x18,%esp
378  60e: 67 66 c7 04 24 0c 09  movl   $0x90c,(%eax,%eax,1)
379  615: 00 00 
380  617: 66 e8 67 00 00 00     calll  684 <_Z9print_strPKc>
381  61d: 67 66 8b 45 08        mov    0x8(%ebp),%eax
382  622: 67 66 0f b6 00        movzbl (%eax),%eax
383  627: 84 c0                 test   %al,%al
384  629: 0f 94 c0              sete   %al
385  62c: 66 0f b6 c0           movzbl %al,%eax
386  630: 66 c9                 leavel 
387  632: 66 c3                 retl   
388 
389 00000634 <__cxa_guard_release>:
390  634: 66 55                 push   %ebp
391  636: 66 89 e5              mov    %esp,%ebp
392  639: 66 83 ec 18           sub    $0x18,%esp
393  63d: 67 66 c7 04 24 22 09  movl   $0x922,(%eax,%eax,1)
394  644: 00 00 
395  646: 66 e8 38 00 00 00     calll  684 <_Z9print_strPKc>
396  64c: 67 66 8b 45 08        mov    0x8(%ebp),%eax
397  651: 67 c6 00 01           movb   $0x1,(%eax)
398  655: 66 c9                 leavel 
399  657: 66 c3                 retl   
400  659: 00 00                 add    %al,(%bx,%si)
401  ...
402 

cppb.s
  1  .file "cppb.cpp"
  2  .section .debug_abbrev,"",@progbits
  3 .Ldebug_abbrev0:
  4  .section .debug_info,"",@progbits
  5 .Ldebug_info0:
  6  .section .debug_line,"",@progbits
  7 .Ldebug_line0:
  8  .text
  9 .Ltext0:
 10  .cfi_sections .debug_frame
 11 #APP
 12  .code16gcc
 13 
 14  .code16gcc
 15 
 16  .code16gcc
 17 
 18  .code16gcc
 19 
 20 #NO_APP
 21 .globl cpp_main
 22  .type cpp_main, @function
 23 cpp_main:
 24 .LFB0:
 25  .file 1 "cppb.cpp"
 26  .loc 1 14 0
 27  .cfi_startproc
 28  pushl %ebp
 
 50 .globl _Z15test_static_objv
 51  .type _Z15test_static_objv, @function
 52 _Z15test_static_objv:
 53 .LFB1:
 54  .loc 1 23 0
 55  .cfi_startproc
 56  pushl %ebp
 57 .LCFI2:
 58  .cfi_def_cfa_offset 8
 59  movl %esp, %ebp
 60  .cfi_offset 5, -8
 61 .LCFI3:
 62  .cfi_def_cfa_register 5
 63  subl $24, %esp
 64 .LBB2:
 65  .loc 1 24 0
 66  movl $_ZGVZ15test_static_objvE2io, %eax
 67  movzbl (%eax), %eax
 68  testb %al, %al
 69  jne .L5
 70  movl $_ZGVZ15test_static_objvE2io, (%esp)
 71  call __cxa_guard_acquire
 72  testl %eax, %eax
 73  setne %al
 74  testb %al, %al
 75  je .L5
 76  movl $_ZZ15test_static_objvE2io, (%esp)
 77  call _ZN2IoC1Ev
 78  movl $_ZGVZ15test_static_objvE2io, (%esp)
 79  call __cxa_guard_release
 80  movl $_ZN2IoD1Ev, %eax
 81  movl $__dso_handle, 8(%esp)
 82  movl $_ZZ15test_static_objvE2io, 4(%esp)
 83  movl %eax, (%esp)
 84  call __cxa_atexit
 85 .L5:
 86 .LBE2:
 87  .loc 1 27 0
 88  leave
 89  ret
 90  .cfi_endproc
 91 .LFE1:
 92  .size _Z15test_static_objv, .-_Z15test_static_objv
 93  .local _ZZ15test_static_objvE2io
 94  .comm _ZZ15test_static_objvE2io,8,4


參考 cppb.s L50 ~ L94,  L71, L79 分別傳 $_ZGVZ15test_static_objvE2io 給 __cxa_guard_acquire, __cxa_guard_release。

只執行一次 ctor 的秘密在 L85 .L5, 什麼時候會跳到 .L5?

 69  jne .L5
 75  je .L5

 77  call _ZN2IoC1Ev

是呼叫 ctor,而 L77 要只執行一次。所以看完這段組合語言之後, 就知道要怎麼填入 __cxa_guard_acquire, __cxa_guard_release 的內容了。當然我沒那麼強, 我是看 http://wiki.osdev.org/C%2B%2B 這篇文章再反推回來的。

 66  movl $_ZGVZ15test_static_objvE2io, %eax
 67  movzbl (%eax), %eax
 68  testb %al, %al
 69  jne .L5
 
 
這是第一關, $_ZGVZ15test_static_objvE2io 指的位址必須是 0 才行, 有這麼剛好的事情嗎?當然沒有, 這是經過某種環節產生的, 在我的平台上, $_ZGVZ15test_static_objvE2io 位於 bss section, 而我的 bss init function 會把 bss section 都初始為 0, 所以過了第一關。

 69  jne .L5
 70  movl $_ZGVZ15test_static_objvE2io, (%esp)
 71  call __cxa_guard_acquire
 72  testl %eax, %eax
 73  setne %al
 74  testb %al, %al
 75  je .L5 

現在程式可以來到 __cxa_guard_acquire, 因為 $_ZGVZ15test_static_objvE2io 指的位址是 0, 所以 __cxa_guard_acquire 回傳 1 (%eax = 1), 而 L75 就不會跳到 .L5,  L76, 77 就執行 ctor。

bochs disassemble
1 00011d9c: (                    ): call .+979                ; 66e8d3030000
2 00011da2: (                    ): testl %eax, %eax          ; 6685c0
3 00011da5: (                    ): setnz %al                 ; 0f95c0
4 00011da8: (                    ): testb %al, %al            ; 84c0
5 00011daa: (                    ): jz .+67                   ; 7443

我在 bochs 反組譯看到的是 jz 而不是 je。

 78  movl $_ZGVZ15test_static_objvE2io, (%esp)
 79  call __cxa_guard_release

呼叫 __cxa_guard_release 把 $_ZGVZ15test_static_objvE2io 指的位址設為 1, 這就是 local static object 只會執行一次 ctor 的關鍵, 回憶一下 L66 ~ L69, 等到第二次呼叫 void test_static_obj(), 這時候 $_ZGVZ15test_static_objvE2io 指的位址設為 1, 直接跳到 .L5, 不會再執行 __cxa_guard_acquire(), __cxa_guard_release()。

寫的很亂, 這不太好描述, 又參雜了兩份不同的反組譯 code, 不過概念就是這樣, 要真的理解, 得仔細的看這些組合語言程式碼, 我花了不少時間看, 所以有了這篇文章 (事實上我做的事情比文章上還多), 但要真能理解, 也必須要做一樣的苦工。

看不懂其實沒有關係 (這篇大概是這系列以來寫的最亂的), 這不能說是很重要的知識, 只要知道怎麼填 __cxa_guard_acquire, __cxa_guard_release 這兩個 function, 就足以完成 local static object 的動作。

source code:
git@github.com:descent/simple_os.git
git checkout origin/cpp_static_obj -b cpp_static_obj

git commit: e80d208503a883a37020ed41e96b721dad1103fe
git checkout e80d208503a883a37020ed41e96b721dad1103fe
cd cpp_runtime/global_object/dos_cpp