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

沒有留言:

張貼留言

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

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