linux 重定位表 符号表,說說編譯鏈接系統中的符號(symbol)、重定位(relocation)、字串表(string-table)和節(section)...

作者:liigo

日期:2009/11

編譯(compile)和鏈接(link),是計算機編程語言的通用處理系統。編譯,是把程序源代碼轉換為目標文件;鏈接,是把目標文件轉換為可執行文件。把編譯和鏈接分成兩個相對獨立的子系統,是為了簡化,是為了分而治之,也有基於通用性的考慮。編譯器(compiler)的任務是把程序源代碼編譯為目標文件。通常每一種編程語言,都會有它自己的編譯器,各種編譯器的輸出都是目標文件(.obj, .lib, .o, .a, ...)。鏈接器(linker)的任務是把編譯器生成的目標文件經過一系列處理,最終生成可執行文件(.exe, .dll, .so, ...)。

56e4e2ebf48c7c94cb2b8000815b1bca.jpe

如果目標文件的規范是唯一的,所有編譯器都輸出特定格式的目標文件,鏈接器也都接收這種格式的目標文件,大概是一種比較理想的狀態,編譯器和鏈接器實現起來也會相對容易,而且通用性好,基至鏈接器只需有一個就行了,這也許是當初設計編譯鏈接系統的初衷之一吧。但現實是很殘酷的,目前來說,目標文件的格式不僅沒有統一,反而分化的很厲害,粗略統計竟有逾十種之多,且互不兼容。編譯器和鏈接器的開發者,往往只能選擇支持一種或少數幾種目標文件格式。通用鏈接器成為一種奢求,從來不曾出現過,這幾乎完全背離了其設計初衷。

好在小范圍的應用通用鏈接器,還是可行的。例如,把D語言源代碼編譯為.obj目標文件,就能和C語言編譯生成的其它.obj目標文件一起,用C語言的鏈接器鏈接生成EXE。又如一款新誕生的編程語言(就說易語言吧),不想重復開發專用鏈接器,可以考慮編譯生成C語言格式的目標文件,進而得以使用現有的C語言鏈接器鏈接生成可執行文件,如此一來可大幅減少開發工作量,降低研發成本的同時還提高了系統的開放性。

目標文件是溝通編譯器和鏈接器的橋梁,它既是編譯器的輸出,又是鏈接器的輸入;而“符號(symbol)”是目標文件中的核心元素,是編譯系統和鏈接系統中最重要的操作對象。通俗的說,編譯器的任務是“創建符號”(以及與符號相關向輔助設施),鏈接器的任務是“使用符號”(以及與符號相關向輔助設施)。

091e2f26a32fdbf189ff004b38260748.jpe

下面結合我(liigo)的個人理解,說說目標文件的基本元素,及其內部結構。就以我目前接觸最多的COFF格式的目標文件為例。

目標文件中的基本元素有,符號(symbol)、重定位(relocation)、字串表(string-table)和節(section)。這里說的是邏輯概念。

符號(symbol)是很什么?說不清楚,因為不好理解(對讀者而言),也不好表達(對作者而言)。舉例吧,假設程序源代碼中有變量有常量有函數,那么編譯之后那些變量常量函數都會各自成為一個符號,供它處引用。是不是可以把符號理解為“比變量常量函數更高層次上的抽象”呢?大概可以吧。正是因為符號是更高層次上的抽象,脫離了編程語言概念上的變量常量和函數,因而鏈接器才有可以做到與具體的編程語言無關。符號的主要屬性有:名稱(符號匹配完全基於名稱文本),所屬節(section)的序號,(符號實體)在節中的偏移,作用域(OBJ內部私有,或全局公開)。符號主要有兩大類:一類是定義性質的(如變量定義、函數定義),其內容(如變量的值、函數體等)存儲於指定的節中某個偏移處;另一類是聲明性質的(如變量聲明、函數聲明),沒有內容(因而不需要所屬節、偏移等屬性),鏈接器會根據名稱在其它obj文件或其它lib文件中找到這個符號的定義。這里體現了鏈接器中“鏈接”二字的含義:一方聲明(依賴、使用)一個符號,另一方定義這個符號,雙方通過符號名稱鏈接到一起。聲明符號可以在定義符號之前,甚至在符號還沒有定義的情況下。聲明一個符號是編譯器的行為,只是表示對該符號的依賴,相應的符號定義可以由他人(或編譯器)在其他時間完成,只要鏈接器工作時能夠(在其他目標文件中)找到定義就OK。從邏輯上說,符號通常指的是變量(變量的地址)和函數(函數可執行體首地址)。在OBJ中存儲時,符號對應某個節(section)中的某處偏移;而在鏈接時(或鏈接的后期),符號則對應某個確定的內存地址(此地址由鏈接器指派,有了地址后才能執行后續的重定位操作)。符號在OBJ文件中是順序存儲的,所有符號的結構體組成一個數組,稱為符號表。在OBJ文件內部,通常通過符號表中的索引(>=0)指代某個符號。如果指代其它OBJ中的符號呢?先在本OBJ內定義一個相同名稱的“聲明性質”的符號,然后通過符號索引指代本OBJ內的這個同名符號,將來鏈接器工作時,所有同名稱的符號都被視為同一個實體並分派唯一的地址。

節(section)是數據的容器,是存儲數據的地方。節內存儲的數據通常有:變量的值,常量的值,函數體,等。節的基本屬性有:數據長度,數據在文件中的偏移,是否可讀可寫可執行,重定位表。在鏈接時,節總是作為一個整體參予鏈接的,它是不可分的。編譯時節划分的比較小比較多,有利於鏈接時按需提取,有利於優化編譯后的EXE或DLL的尺寸。分析VC6編譯器生成的OBJ文件可知,一般一個函數會單獨使用一個節(section)存儲。如果看看C語言標准庫的源代碼,會發現它往往把一個函數寫到一個單獨的源文件中,這樣編譯時一個函數就會生成一個OBJ文件,盡量做到了細化。在OBJ中,所有節的節頭(section-header)順序存儲形成一個數組,稱為節頭表或節表。通常通過OBJ文件內節表中的序號(>=1)指代某個節。

重定位表(relocation)是從屬於節(section的重要元素,用於修正節數據中的地址部分。分析編譯器編譯生成的函數代碼的話,會發現它生成的不是完整的真正可執行的代碼,而只是代碼模板,其中涉及地址之處,往往簡單的使用0x00000000占位,同時在此處綁定一個符號(symbol)用於修正此地址。為什么會這樣呢?因為在編譯器工作時,它並不知道符號(變量、函數等)地址,可能該符號來自另一個OBJ(或另一個LIB),甚至連它有沒有定義都無法知曉。編譯器只能先留下空白給鏈接器。通俗的說,編譯器出了一個完形填空的題目,要鏈接器解答。重定位表可以理解為編譯器給鏈接器提供的信息,它是由多個重定位項組成的數組,其中每一個重定位的基本屬性有:被修正地址在節數據中的偏移,用於提供地址的符號索引,重定位類型(絕對定位、相對定位等)。鏈接器工作時,根據重定位項中的符號索引得到符號名稱,進而查詢得到符號地址(鏈接器負責指派符號地址),根據被修正地址在節中的偏移以及節的地址(鏈接器負責指派節的地址)得到被修正地址的地址,再根據重定位類型,將符號的地址填過去。舉個例子,C語言代碼 int a = 1;,對變量賦值,編譯結果(不考慮編譯優化)可能是 mov dword ptr [0x00000000], 0x12345678,相應的X86指令序列為 C7 05 00 00 00 00 78 56 34 12,中間的四字節的0就是占位符,將來需要鏈接器把變量a的地址覆蓋上去,這是絕對定位;再如C代碼 f();,編譯結果(不考慮編譯優化)可能是 call dword ptr [0x00000000],相應的X86指令序列為 FF 15 00 00 00 00,中間的四字節的0就是占位符,將來需要鏈接器把“函數f的地址與下一指令地址的差值”覆蓋上去,這是相對定位的例子。具體是采用絕對定位還是相對定位還是其它定位方式,是由編譯器生成的重定位表指定的,取決於編譯器選擇生成的指令代碼。地址占位符也不見得一定是零,可以是任意數值(可正可負),表示相對目標地址的前后偏移量,鏈接器重定位時填寫的地址其實是在此數值基礎上與目標地址相加而得到的。以上說的是鏈接生成EXE或DLL時由鏈接器執行的重定位,將來DLL或EXE被載入時PE加載器還會執行一次重定位(重定位表由鏈接器生成,EXE中通常可省略),這兩個階段的重定位雖然細節上不同,但原理是一致的。

字串表(string-table)是OBJ文件或LIB文件中的輔助設施,用於集中存儲一些名稱文本,如長度大於8字節的符號名稱、段名稱,以及長度大於15字節的鏈接成員(link member, 見於LIB中)的名稱。字串表存在的目的主要是用於優化OBJ或LIB文件的尺寸。以符號名稱為例,在OBJ中,一個符號所對應的結構體大小是固定的,共18字節,其中留出8個字節用於存儲符號名稱。如果符號名稱比較短,小於等於8個字節,則直接存到這個結構體中(不存儲C文本結尾字符'\0');如果符號名稱長度大於8字節,則把名稱存到字串表(string-table)中,然后把這個名稱在字串表中的偏移記錄到前面提到的8個字節區域處(在第一個字符前加'/'作為區分名稱和偏移的標記)。

723b481184082ad7675c5bbb9a82c04c.jpe

至於LIB文件,相比OBJ就簡單多了,它僅是OBJ文件的打包整理和索引,完整地包含了庫中所有OBJ文件的內容,並提供了庫中公開符號的名稱索引表(根據一個符號名稱可以快速查詢到它是否在本庫中定義,以及在哪個OBJ中定義)。在物理上,LIB文件的前面部分由三個固定的鏈接成員(linker member)組成,后面是順序存儲各OBJ文件內容(也稱為linker member),每個鏈接成員均有一個數據頭(header)。第一個固定鏈接成員(1st linker member),僅因兼容原因而保留,已被第二個固定鏈接成員(2nd linker member)取代,后者記錄了符號名稱索引信息和后面各OBJ成員的基本信息,第三個固定成員(3rd linker member)記錄長文本(可能被省略)。

寫的不是很條理,有點亂,請多包涵。liigo, 2009。

參考資料:

<>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值