[转]ELF文件结构简述
http://blog.endlesscode.com/2010/05/31/elf-file-structure-short-intro/
ELF文件結構簡述
?Stephen??2010/05/31現在PC平臺流行的可執行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它們都是COFF(Common File Format)格式的變種。目標文件就是源代碼編譯后但未進行鏈接的那些中間文件(Windows的.obj和Linux下的.o),它跟可執行文件的內容與結構很相似,所以一般跟可執行文件格式一起采用一種格式存儲。
Linux的.o/Windows的.obj、/bin/bash或Windows的exe、Linux的.so/Windows的.dll分別是什么文件?
ELF目標文件的格式是怎樣?
ELF文件除了包括了上述圖示中的機器指令代碼、數據,還包括了鏈接時所需要的一些信息,比如符號表、調試信息、字符串等。這些信息按不同的屬性,以“段“(Segment)的形式存儲。程序源代碼編譯后的機器指令經常放在代碼段(Code Section)里,代碼段常見的名字有”.code”或”.text”;全局變量和局部靜態變量經常放在數據段(Data Section),數據段一句名字叫為”.data”。而ELF文件的開頭是一個“文件頭”,它描述了整個文件的文件屬性,包括文件是否可執行、是靜態鏈接還是地動態鏈接及入口地址(如果是可執行文件)、目標硬件、目標操作系統等信息,文件頭還包括一個段表的位置信息,段表在ELF文件中是一個描述文件中各個段的數組,包括各個段在文件的偏移位置,段的屬性,段的名稱等等。
程序源代碼被編譯以后主要分成兩種段:程序指令和程序數據。代碼段(.text)屬于程序指令,而數據段(.data)和.bss段屬于程序數據。 一般C語言的編譯后執行語句都編譯成機器代碼,保存在.text段;已初始化的全局變量和局部靜態變量都保存在.data段;未初始化的全局變量和局部靜 態變量一般放在.bss段,這些未初始化的全局變量和局部靜態變量默認值都為0,放在.data段顯示沒有必要,因此放在了.bss段。所以.bss段只 是為未初始化的全局變量和局部靜態變量預留位置,它并沒有內容,所以它在文件中也不占據空間。
一般程序被裝載后,數據和指令分別被映射到兩個虛存區域。由于數據區域對于進程來說是可讀可寫的,而指令區域對于進程來是只讀的,所以這兩個虛存區域的權限可以被分別設置成可讀寫和只讀。這樣可以防止程序的指令被有意或無意地改寫。
| 常用的段名 | 說明 |
| .rodata1 | Read Only Data,這種段里存放的是只讀數據,比如字符串常量、全局const變量。跟”.rodata”一樣 |
| .comment | 存放的是編譯器版本信息 |
| .debug | 調試信息 |
| .dynamic | 動態鏈接信息 |
| .hash | 符號哈希表 |
| .line | 調試時的行號表 |
| .note | 額外的編譯器信息。比如程序的公司名、發布版本號等 |
| .strtab | String Table.字符串表 |
| .symtab | Symbol Table.符號表 |
| .shstrtab | Section String Table.段名表 |
| .plt .got | 動態鏈接的跳轉表和全局入口表 |
| .init .fini | 程序初始化與終結代碼段 |
其中,”.rodata”段存放的是只讀數據,一般是程序里面的只讀變量(如const修飾的變量)和字符串常量。單獨設立”.rodata”段有很多好 處,不光是在語義上支持了C++的const關鍵字,而且操作系統在加載的時候可以將”.rodata”段的屬性映射成只讀,這樣對于這個段的任何修改操 作都會作為非法操作處理,保證程序的安全性。當然,有些編譯器也會把字符串放到”.data”段,而不會單獨放在”.rodata”段。
如C中的printf(“%d\nHello World!”)中的字符”%d\nHello World!”是存儲在哪里的?
ELF文件中用到了很多字符串,比如段名、變量名等。因為字符串的長度往往是不定的,所以用固定的結構來表示它比較困難。一種很常見的做法是把字符串集中起來存放到一個表,然后使用字符串在表中的偏移來引用字符串。如下圖所示:
通過這種方法,在ELF文件中引用字符串只須給出一個數字下標即可,不用考慮字符串長度的問題。一般字符串表在ELF文件中也以段的形式保存,常見的段名為”.strtab“或”.shstrtab“。這兩個字符串分別為字符串表(String Table)和段表字符串表(Section Header String Table)。顧名思義,字符串表用來保存普通的字符串,比如符號的名字;段表字符串表用來保存段表中用到的字符串,最常見的就是段名。
段表是結構是怎樣?存儲了哪些信息?
段表的結構其實就是一個數組,數組的元素是固定的結構,每個元素包括了各個段的信息,比如每個段的段名、段的長度、在文件中的偏移、讀寫權限、段的鏈接信息以及段的其他屬性。其中描述段名的只是一個偏移,是段名字符串在”.shstrtab”段中的偏移。那么如何根據ELF文件頭和段表來定位文件中的一個段呢?從ELF文件頭中獲取段表在文件中的偏移以及段表的大小,而從獲取到了文件中的段表,再分析段表數組中的每個元素,得知段的偏移、段的長度和段的類型,就可以準確地定位一個段了。
字符串表和符號表有什么不同?
其實是完全不同的兩個表,但是兩個表又有關聯。簡單地說,字符串表就是記錄ELF文件中的字符串常量,變量名等等。而符號表的存在意義是體現在多個目標文件進行鏈接的時候,在鏈接中,目標文件之間相互拼合實際上是目標文件之間對地址的引用,即對函數和變量的地址的引用,而函數和變量可以統稱為符號(Symbol),函數名或變量名就是符號名(Symbol Name)。我們可以將符號看作是是鏈接中的粘合劑,整個鏈接過程就是基于符號才能夠正確完成。在符號表”.symtab“中,其也是像段表的結構一樣,是一個數組,每個數組元素是一個固定的結構來保存符號的相關信息,比如符號名(不是字符串,而是該符號名在字符串表的下標)、符號對應的值(可能是段中的偏移,也可能是符號的虛擬地址)、符號大小(數據類型的大小)等等。符號表中記錄的一般是全局符號,比如全局變量、全局函數等等。
Visual Studio里面提示的Undefined Symbol,為什么是類似(?NewL@CHtmlControl@@SAPAV1@PAVCCoeControl@@@Z)這么詭異的symbol?
程序里面肯定是沒有定義過”?NewL@CHtmlControl@@SAPAV1@PAVCCoeControl@@@Z”這么詭異的變量名的,但為什么變量名會變成這么詭異的?這是因為符號修飾的原因。符號修飾很大程度上是為了防止符號名沖突。比如復雜的C++擁有類、繼承、虛擬機制、重載、名稱空間等這些特性,如果兩個函數func(int)和func(float)不進行符號修飾而直接存儲變量名的話,然后只是單單根據函數名來判別的話,那么符號表就會出現2個一樣的符號了。根據函數簽名來修飾兩個重載函數就不會出現這樣的符號二義性的問題了。因為符號修飾的規則是各編譯器廠商都不一樣,因此再詭異也是沒有什么出奇的。
如果兩個目標文件定義了相同的全局變量?
在編輯過程中可能會遇到一種情況叫符號重復定義。這涉及到強符號(Strong Symbol)和弱符號(Weak Symbol)的定義。對于C/C++語言來說,編譯默認函數和初始化了的全局變量為強符號,未初始化的全局變量為弱符號。我們也可以通過GCC的”__attribute__((weak))”來定義任何一個強符號為弱符號,而用”extern int ext”這樣的方式定義并非弱符號也非弱符號,因為”ext”只是一個外部變量的引用。針對強弱符號的概念,鏈接器一般會按如下規則選擇全局符號:
- 不允許強符號被多次定義,否則鏈接器會報符號重復定義的錯誤。
- 如果一個符號在某個目標文件中是強符號,在其他文件中都是弱符號,那么選擇強符號。
- 如果一個符號在所有目標文件中都是弱符號,那么選擇其中占用空間最大的一個。
預編譯過程主要做了哪些處理?
轉載于:https://www.cnblogs.com/hengli/archive/2012/09/28/2707060.html
總結
以上是生活随笔為你收集整理的[转]ELF文件结构简述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的第一篇blog 【随意】
- 下一篇: Hlg 1407 【最小点权覆盖】.cp