the 1st edition: 20120229 (3)
the 2nd edition: 20161214 (3)
之前在《
bss section (0)》留下來的問題要在這篇解答。
先來看看 b.c, 這程式很簡單, b.c L6 int i; 會放在 bss, p() 會把 c=i+1 用 itoa() 轉成 string 印出。預期得到 1, 因為在 C 中, 教科書都告訴我們 int i; 雖然沒有初值, 但 c 會初始為 0, 這不是憑空得來的, 裡頭有魔法, 讓我們來惡搞一下 bss 吧!
這是 bare-metal 程式, 和一般程式的編譯以及執行方式有些不同。
由 c_init.s _start 開始執行, 執行 init_bss_asm 呼叫 p(), p() 把 i+1 印出來。i 位於 bss, 所以應該看到 1 被印出來 (fig 1), 不過執行結果是 16843010, 怎麼不是 1, 我又搞錯了嗎? 非也, 這是刻意營造的結果, 我把 bss 初始為 0x01010101 , 加上 0x1 後為 0x01010102 = 十進位 16843010。 因為受限於開機磁區的 512 byte 限制, 所以編譯這段程式碼需要加上 -Os, 讓它可以小於 512 byte。
我加入以下選項, 讓 gcc 不要產生 .eh_frame section, .eh_frame section 對 bare-metal 用處不大, 還佔了 0x8c 的空間。
-fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables
.eh_frame, .eh_frame_hdr -
ref:
how to remove that trash
之前用 gcc 4.x 好像不會產生 .eh_frame section, 這次用 gcc 5.4 就需要這些選項來避免產生 .eh_frame section。
|
fig 1 |
初始化 bss, c_init.s L17 call init_bss_asm 把 0x1 copy 到這個 bss 區域 (c_init.s L52), bss 開頭和結束可由 Linker Scripts 得知, bss.lds L18, L23 可以看到 __bss_start__, __bss_end__, 這就是 c_init.s 用到的那兩個變數, 神奇吧!「還可以這樣用的哦!」gnu toolchain 實在太 ... 厲害了。當然在 Linker Scripts 直接指定一個位址也可以, 不過這樣用比較帥 (帥阿! 老皮), 也比較方便。
bss 區的變數不會存放在執行檔裡, 需要靠 c runtime library 來初始化, 我們能痛快的使用這些變數, 便是這些初始化程式碼的功勞。
沒有初始值或是初始值為 0 的 static, global 變數, 就是放在 bss。
list 5 L88 i 為於 0x7d60, 而 list 5 L27 指出的 .bss 從 0x7d60 開始, 總共 4 byte, 剛好就是 i 的位址。
0x7d60 位於 .bss 區, 長度4 byte, 自然就是 int i 佔據的大小。若你不相信, 把 bochs run 一次, 反組譯後就可得証。
以上為 x86 真實模式下的 bss 環境。那麼在 x86 保護模式下呢? 原理是一樣的, 就算是 arm 也是一樣, 只是切入保護模式後 segment register 要改用 selector, 確認使用的 selector:offset 是 bss 區域即可。這說來簡單, 我可花了不少時間才搞清楚:
- selector:offset
- 程式整個載入位址
- 整個程式的定址空間
用組合語言來初始化 bss 不太好, 我後來寫了 c 語言的版本, 可攜性較高。在 OS 環境的庇護下寫程式, 真是很幸福, 不需要知道很多事情; 也很不幸, 因為有很多事情被遮蔽起來, 讓我們無法了解細節。
ref:
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。