分享

Jserv's blog: 窺探 .bss section

 astrotycoon 2019-05-29

窺探 .bss section

幾年前只是對系統設計感到困惑,沒想到「分析 GCC 對 Hello World 的重重布幕」一類的舉動,竟成為激勵自我成長的目標,實在始料未及。拜 C 語言這種「披著高階語言羊皮的低階語言之狼」所賜,我們可透過稍早 blog [自我印列 ELF 簽名] 所提及的途徑,探索記憶體位址背後的意義。同樣地,我們也可從實驗觀察 GNU/Linux 中 ELF (executable and linkable format) 格式執行檔裡頭 .bss section 的呈現,關於這部份的背景知識,可參閱 Jollen 整理的 [.bss section:C 語言所種下的因] 與 [BSS Section 觀念教學] 等文章,本文則針對「窺探」的手法作補充。

「窺探」ELF 執行檔有許多途徑,我們當然可用 binutils 裡面的 readelf / objdump 工具,但這裡我們直接用程式自我列印,筆者給定的程式如下:
#include <stdio.h>

extern int __bss_start, _end;

int a, b, c, d;  /* un-initialized */

int main()
{
        int *ptr;
        a = 1, b = 2, c = 3, d = 4;

        for (ptr = &__bss_start; ptr != &_end; ptr++) {
                printf("%d\n", *ptr);
        }
        return 0;
}
為行文便利,此小程式命名為 [bss.c],咱們就先試著執行看看。在筆者的電腦安裝有 gcc 3.4 與 4.3 兩個版本,先用 gcc-3.4 看看:
$ gcc-3.4 -xc bss.c && ./a.out
0
4
1
2
3
由上可見,C 語言程式碼中的 int a, b, c, d 在宣告的時候,並未給定數值,也就是「未作初始化」,這樣的變數在 ELF 的角度來看,就存放於 .bss section,而在 main() 中,這四個變數都在執行時期 (runtime) 被給定數值,上述的程式透過迴圈,將給定的 1, 2, 3, 4 等值都印列出來 (儘管順序不是預期的遞增排列,不過本文不會深入分析),這是怎麼做到的呢?關鍵之處就在於一開始宣告的這行:
extern int __bss_start, _end;
注意到此行前方的 "extern" 關鍵字,在 GNU Toolchain 會對名稱為 "__bss_start" 與 "_end" 的符號作特別處理,在預設的 linker script 中,會給定輸出 ELF 執行檔的 .bss section 的資訊,重點是,經過這樣的操作後,"__bss_start" 與 "_end" 只是 label 而非真正的變數,所以,並不佔用真正的記憶體空間。在 C 語言中,我們可取得其位址作指標的尋訪過程,以逐一得知 .bss section 各元素的內容值,這下似乎明暸了,但回顧剛剛的執行輸出,我們不免對其中的 "0" 感到困惑,是啊,這值到底從哪邊來?

在找尋答案之前,筆者改用 gcc-4.3 來作測試,其執行輸出如下:
$ gcc-4.3 -xc bss.c && ./a.out
0
0
4
1
2
3
感覺起來就更離奇了,「又」多了一個 "0" 的輸出?!看來是 GNU Toolchain 對 ELF 執行檔額外施加了「魔法」,看來得搬出其他工具來分析。先觀察 objdump 對 .bss section 的分析:
$ gcc-3.4 -xc bss.c && objdump --section=.bss -x a.out
...
SYMBOL TABLE:
08049598 l    d  .bss	00000000              .bss
08049598 l     O .bss	00000001              completed.1
0804959c g     O .bss	00000004              d
080495a0 g     O .bss	00000004              a
080495a4 g     O .bss	00000004              b
080495a8 g     O .bss	00000004              c
可以發現,事實上程式碼的 &__bss_start 勢必指向 ELF 執行檔透過 Program Loader 映射到記憶體中的 BSS 區域,而我們在程式中尋訪 .bss section 中的元素,大抵就是依照上面的 SYMBOL TABLE 的排列方式,而之前那個印列出的 "0" 數值,就是 "completed.1" 這個符號的內含值。同理,我們觀察透過 gcc-4.3 編譯時的分析結果:
$ gcc-4.3 -xc bss.c && objdump --section=.bss -x a.out
...
SYMBOL TABLE:
0804a014 l    d  .bss	00000000              .bss
0804a014 l     O .bss	00000001              completed.6625
0804a018 l     O .bss	00000004              dtor_idx.6627
0804a01c g     O .bss	00000004              d
0804a020 g     O .bss	00000004              a
0804a024 g     O .bss	00000004              b
0804a028 g     O .bss	00000004              c
在這份輸出中,我們看到形似剛剛 "completed.1" 的 "completed.6625" 符號,也多了名稱為 "dtor_idx.6627" 的符號。為了揭開謎團的真相,筆者又用 gcc-4.1 與 gcc-4.2 作實驗,這兩者得到與 gcc-3.4 編譯時相仿的輸出,但 "completed." 符號後方的數值名稱是不一樣的,由此可歸納,gcc-4.3 引入了一些我們未察覺的修改,而在 gcc-3.4 到 gcc-4.2 之間的 GNU Toolchain 所編譯的 ELF 執行檔,其 .bss section 也隱含我們不甚明暸的細節。

未完,待續
由 jserv 發表於 June 19, 2008 05:33 PM  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多