天天看點

《作業系統真象還原》——0.21 Section和Segment的差別

本節書摘來自異步社群《作業系統真象還原》一書中的第0章,第0.21節,作者:鄭鋼著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

c程式大體上分為預處理、編譯、彙編和連結4個階段。預處理階段是預處理器将進階語言中的宏展開,去掉代碼注釋,為調試器添加行号等。編譯階段是将預處理後的進階語言進行詞法分析、文法分析、語義分析、優化,最後生成彙編代碼。彙編階段是将彙編代碼編譯成目标檔案,也就是轉換成了目标機器平台上的機器指令。連結階段是将目标檔案連接配接成可執行檔案。這裡我們隻關注彙編和連結這兩個階段。

在彙編源碼中,通常用文法關鍵字section或segment來表示一段區域,它們是編譯器提供的僞指令,作用是相同的,都是在程式中“邏輯地”規劃一段區域,此區域便是節。注意,此時所說的section或segment都是彙編文法中的關鍵字,它們在文法中都表示“節”,不是段,隻是不同編譯器的關鍵字不同而已,關鍵字segment在文法中也被認為與section意義相同。首先彙編器根據文法規則,會将彙編源碼中表示“節”的文法關鍵字section或segment在目标檔案中編譯成“節”,此“節”便是我們要讨論的section。經過彙編生成目标檔案之後,由這些section或segment修飾的程式區域便成為了“節”(section)。但作業系統加載程式時并不關心節的數量和大小,作業系統隻關心節的屬性,因為程式必然是要加載到記憶體中才能運作的,而記憶體的通路會涉及到全局描述符表中段描述符的通路權限等屬性,保護模式下對任何記憶體的通路都要經過段描述符才行。比如程式代碼所在的段描述符權限屬性必須是隻讀,資料所在的段描述符的權限屬性必然是可讀寫,程式中那些隻讀的節(比如代碼區域)必然不能指向可讀寫的段描述符,同樣,程式中的資料也不能用隻讀權限的段描述符去通路。如果此時您對段描述符不了解,以後咱們在介紹保護模式下全局描述表時就明白了。作業系統在加載程式時,不需要對逐個節進行加載,隻要給出相同權限的節的集合就行了,例如把所有隻讀可執行的節(如代碼節.text和初始化代碼節.init)歸并到一塊,所有可讀寫的節(如資料節.data和未初始化節.bss)歸并到一塊,這樣作業系統就能為它們配置設定不同的段選擇子,進而指向不同段描述符,實作不同的通路權限了。為了程式能在作業系統上運作,作業系統和編譯器需要互相配合,此時彙編器隻生成了目标檔案,尚未連結,是以這個将“節”合并的工作是由連結器來完成的,連結器将目标檔案中屬性相同的節合并成一個大的section集合,此集合便稱為segment,也就是段,此段便是我們平時所說的可執行程式記憶體空間中的代碼段和資料段。

現在總結一下。

section稱為節,是指在彙編源碼中經由關鍵字section或segment修飾、邏輯劃分的指令或資料區域,彙編器會将這兩個關鍵字修飾的區域在目标檔案中編譯成節,也就是說“節”最初誕生于目标檔案中。

segment稱為段,是連結器根據目标檔案中屬性相同的多個section合并後的section集合,這個集合稱為segment,也就是段,連結器把目标檔案連結成可執行檔案,是以段最終誕生于可執行檔案中。我們平時所說的可執行程式記憶體空間中的代碼段和資料段就是指的segment。

在大多數情況下,這兩者都被混為一談,現在咱們做個實際測試,通過實驗結果來展示出這兩者的不同。其實用一個測試樣例就能得出結果,不過為了消除大家的疑慮,測試得更徹底一點,在這裡給大家準備了兩個小彙編檔案,将它們編譯連結後,我們通過readelf指令檢視其資訊來得出結論。上菜了。

檔案1.asm

《作業系統真象還原》——0.21 Section和Segment的差別

這個彙編檔案是在本地中聲明了字元串,并調用外部的列印函數print,大家可以參考注釋,弄個大概明白就行。

檔案2.asm

《作業系統真象還原》——0.21 Section和Segment的差別

在檔案2.asm中聲明了函數print。下面将這兩個檔案分别編譯成elf格式,這樣友善我們通過readelf來檢視其編譯結果。開始編譯,連結成可執行檔案12。

沒問題,再執行一下。

列印出了hello,world!,結果正确。讓我們用readelf檢視下檔案12的頭資訊,如圖0-12所示。

《作業系統真象還原》——0.21 Section和Segment的差別
《作業系統真象還原》——0.21 Section和Segment的差別

結果好長,為了友善檢視,我對關鍵部分加以注釋,如圖0-13和圖0-14所示。

在上面重點部分我都用文字标出了,要注意section headers的部分,此部分顯示可執行檔案中所有的section,也包括我們在兩個彙編檔案中用關鍵字section定義的部分。從第2個section到第5個section,是1.asm中的自定義資料section: file1data,自定義代碼section: file1text和2.asm中的自定義資料section: file2data和自定義代碼section: file2text。

再往下看program headers部分,此處一共有兩個段,第一個段是我們的代碼段,通過其flg值為re便可推斷,隻讀(readonly)可執行(execute),其memsiz為0x000c3。此段對應section to segment mapping部分中的第00個segment,此segment中包括section: .text file1data file1text file2data file2text。

《作業系統真象還原》——0.21 Section和Segment的差別

第二個段便是我們的資料段,但此資料段中隻包含.bss節(section),它用于存儲全局未初始化資料,故其flg必然可讀寫,其屬性為rw。此段memsiz大小為0x40,即十進制的64,可見,這和1.asm中定義的bss大小一緻,而在2.asm中未定義.bbs section,是以此bss指的就是1.asm中的定義。此段對應section to segment mapping部分中的第01 個segment,而此segment隻包括.bss節,獨立成一個段了。

到此檔案分析完畢,總結一下。

自定義的section名,會在elf的section header 中顯示出來。下面是幾個标準的section(節)名,不是segment(段)名,segment沒有名稱。

在彙編代碼中,若以标準節名定義section,如我們定義的.bss便是标準節名。編譯器會按照以上說明中的要求使用section内的資料。

不管定義了多少節名,最終要把屬性相同的section,或者編譯認為可以放到一塊的,合并到一個大的segment中,也就是elf中說的 program header 中的項。由此可見,某個節(section)屬于某個段(segment),段是由節組成的。另外多說一句,最終給加載器用的也是program header中顯示的段,這才是程序的資源,這部分内容将在加載核心時展開。在第3章中介紹了section在位址配置設定上的内容,大家有興趣可以提前了解下。

繼續閱讀