.obj是目標(biāo)文件表窘,所以可以知道目標(biāo)文件是指編譯后生成的文件蹋砚,目標(biāo)文件幾乎和可執(zhí)行文件相同只是稍微有點(diǎn)不同而已亥揖。其不同之處在于有些符號(hào)和地址沒(méi)有被調(diào)整妙同。
正是因?yàn)槟繕?biāo)文件與可執(zhí)行文件幾乎相同砚蓬,所以它們的存儲(chǔ)格式是一樣的矢门,可以把它們近似看成同一種文件。
Linux下的動(dòng)態(tài)鏈接庫(kù)格式為.so灰蛙,Windows和Linux下的靜態(tài)鏈接庫(kù)格式分別為.lib和.a祟剔。
靜態(tài)鏈接庫(kù)是一個(gè)文件,該文件包含了很多目標(biāo)文件摩梧,它是一個(gè)整體物延。
Linux下的可執(zhí)行文件是按照ELF格式存儲(chǔ)的,ELF標(biāo)準(zhǔn)包含4種文件仅父,請(qǐng)看P81叛薯。我所熟悉的Windows下的DLL就屬于共享目標(biāo)文件浑吟。
目標(biāo)文件一般包含了哪些內(nèi)容?編譯后的機(jī)器指令代碼耗溜、數(shù)據(jù)组力、連接所需的信息、符號(hào)表抖拴、調(diào)試信息燎字、字符串等。
目標(biāo)文件把信息按照屬性的不同分段存儲(chǔ)城舞。寫到這里我感覺(jué)這書上說(shuō)的與老師課上講的程序在內(nèi)存中的分段方法有些相似轩触。在目標(biāo)文件中,編譯后的機(jī)器指令代碼放在代碼段(Code
Section)中家夺,段名一般為.code和.text脱柱。全局變量和靜態(tài)變量放在數(shù)據(jù)段(Data
Section)中,段名一般為.data拉馋。
BSS段(Block Started By Symbol)用來(lái)存儲(chǔ)未初始化的靜態(tài)變量和全局變量榨为。話雖如此bss中并沒(méi)有這些變量的內(nèi)容,它只是為這些變量按照所占空間大小預(yù)留空間而已煌茴。由于這些變量默認(rèn)就是0随闺,所以壓根沒(méi)必要再為它們分配一個(gè)數(shù)據(jù)0,也沒(méi)有必要讓它們待在data段中蔓腐。因此bss的作用是為這些變量預(yù)留空間矩乐。
另外目標(biāo)代碼還有一個(gè)文件頭用來(lái)保存該目標(biāo)文件的信息,它里面還有一個(gè)段表回论。
源代碼被編譯以后生成兩種段數(shù)據(jù)段和指令段散罕,.code.text屬于指令段.data.bss屬于數(shù)據(jù)段。
這樣分主要有3點(diǎn)好處:
1傀蓉、防止程序被有意無(wú)意篡改欧漱。這是因?yàn)橹噶疃沃蛔x,數(shù)據(jù)段可讀寫葬燎。
2误甚、提高了緩存命中率。
3谱净、節(jié)省內(nèi)存空間窑邦。因?yàn)橹噶疃慰杀欢鄠€(gè)副本共享,但是副本可以擁有自己的數(shù)據(jù)段壕探。
原來(lái)目標(biāo)文件中的段還有只讀數(shù)據(jù)段(.rodata)奕翔、注釋信息段(.comment)、堆棧提示段(.note.GNU-stack)浩蓉。
從書中所給的例子來(lái)看一個(gè)ELF文件只有4個(gè)段是由內(nèi)容的派继,即.data宾袜、.text、.rodata驾窟、.comment庆猫。
從圖3-3可以看出在內(nèi)存中,從低地址到高地址是按照ELF
header绅络、text月培、data、rodata恩急、comment杉畜、other
data的順序存放的。
由本小節(jié)可知衷恭,全局變量可能因?yàn)檎Z(yǔ)言和編譯器的不同不一定存放在bss段此叠,但是靜態(tài)變量一定存放在bss段。
雖說(shuō)bss存放的是未初始化的靜態(tài)和全局變量随珠,但是有些變量如果被初始化為0灭袁,它也會(huì)被放在bss中,這是編譯器的優(yōu)化窗看,有時(shí)候這種優(yōu)化會(huì)帶來(lái)麻煩茸歧。
表3-2列出了其他段及意義。
此外显沈,這個(gè)段還可以自定義软瞎。
圖3-4展示了ELF的層次結(jié)構(gòu)。
最重要的兩個(gè)部分就是ELF文件頭和段表拉讯。ELF文件頭描述整個(gè)文件的基本屬性涤浇,段表描述各段的信息。
清單3-2清楚地描述了ELF文件頭的信息遂唧,P95黑體部分列舉了ELF文件頭包含的信息芙代。
ELF文件兼容各平臺(tái)吊奢,它的文件結(jié)構(gòu)和相關(guān)參數(shù)定義在”/usr/include/elf.h”里盖彭,它有32位和64位兩種。
表3-3展示了elf.h的自定義變量體系页滚。
表3-4展示了ELF文件頭結(jié)構(gòu)成員含義召边。
ELF魔數(shù):ELF文件頭的第一個(gè)字段是Magic,包含16bytes裹驰,對(duì)應(yīng)于Elf32_Ehdr中的e_ident成員隧熙。Magic用來(lái)表示平臺(tái)的各種屬性。
1~4個(gè)字節(jié)是所有ELF文件都相同的標(biāo)識(shí)碼幻林,分別對(duì)應(yīng)del贞盯、E音念、L、F躏敢,這四個(gè)字節(jié)就是ELF魔數(shù)闷愤。操作系統(tǒng)通過(guò)確認(rèn)魔術(shù)是否正確以決定是否加載可執(zhí)行文件。
第5個(gè)字節(jié)用來(lái)表示ELF文件是32位的還是64位的件余。
第6個(gè)字節(jié)用來(lái)表示ELF字節(jié)序讥脐。
第7個(gè)字節(jié)用來(lái)表示ELF文件版本號(hào)。
后面的9個(gè)字節(jié)用來(lái)預(yù)留啼器,有些平臺(tái)可能用來(lái)作為擴(kuò)展標(biāo)志旬渠。
Elf32_Ehdr中的e_type成員表示ELF文件類型,ELF總共有三種文件類型如表3-5所示端壳。操作系統(tǒng)是通過(guò)判斷文件類型而不是擴(kuò)展名來(lái)確定ELF文件類型的告丢。
Elf32_Ehdr中的e_machine成員表示ELF文件的平臺(tái)屬性。雖然ELF遵循統(tǒng)一標(biāo)準(zhǔn)但不代表同一ELF文件可以在不同平臺(tái)上使用更哄。
它用來(lái)表示各個(gè)段的信息芋齿,ELF文件中的段是由段表決定的。
一個(gè)ELF文件不僅僅包含像data成翩、text觅捆、bss這樣的段,還包括其他的輔助性段麻敌。
段表是一個(gè)Elf32_Shdr類型的結(jié)構(gòu)體數(shù)組栅炒,元素的個(gè)數(shù)代表段的個(gè)數(shù),每個(gè)元素對(duì)應(yīng)一個(gè)段术羔。這個(gè)Elf32_Shdr被稱為段描述符赢赊。
表3-7描述了Elf32_Shdr中各字段的意義。
段的名稱對(duì)于編譯和鏈接有意義级历,對(duì)操作系統(tǒng)無(wú)意義释移。決定段的類型的是段的類型字段,并不是段的后綴名和名稱寥殖。
段的類型和段的標(biāo)志位字段決定了段的屬性玩讳。表3-8展示了段的各種類型。
段的標(biāo)志位表示該段在進(jìn)程虛擬地址空間中的屬性嚼贡,如是否可讀熏纯。表3-9列出了段的各種屬性。
表3-10列出了系統(tǒng)保留段的各種屬性粤策。
段的連接信息包括sh_link和sh_info樟澜,它們與鏈接相關(guān),如表3-11所示。
目標(biāo)文件中有一個(gè)SHT_REL的.rel.text字段秩贰,它是重定位表霹俺。重定位發(fā)生在連接的過(guò)程中,這個(gè)在前面已經(jīng)講過(guò)毒费,重定位表記錄了重定位相關(guān)信息吭服。
顧名思義,就是用來(lái)表示各種名稱的字符串的表蝗罗。它是一個(gè)裝有各種字符串的表格艇棕,每個(gè)字符在表中都有一個(gè)固定的位置。
這種表在ELF文件中保存為2種形式——.strtab和.shstrtab串塑,它們分別是字符串表和段字符串表沼琉,它們?cè)贓LF文件中都以獨(dú)立的段而存在。為了輕松地找到這個(gè)段桩匪,在ELF文件頭中包含了這兩個(gè)段的下標(biāo)打瘪,名為e_shstrndx。
鏈接是組合目標(biāo)文件的過(guò)程傻昙,目標(biāo)文件是根據(jù)彼此之間的地址相互引用闺骚,從而組合成可執(zhí)行文件的。而妆档,這個(gè)地址可以簡(jiǎn)單地理解為目標(biāo)文件中的函數(shù)和變量僻爽。在這里,函數(shù)和變量統(tǒng)稱為符號(hào)贾惦,函數(shù)名和變量名統(tǒng)稱為符號(hào)名胸梆。
鏈接器的著眼點(diǎn)主要在定義在本目標(biāo)文件和定義在其他目標(biāo)文件的全局性符號(hào),因?yàn)橹挥羞@些涉及到目標(biāo)文件之間的組合须板。
ELF文件的符號(hào)表是一個(gè)段碰镜,段名為“.symtab”,它是一個(gè)Elf32_sym類型的數(shù)組习瑰,每個(gè)數(shù)組元素代表一個(gè)符號(hào)绪颖。
在Elf32_sym結(jié)構(gòu)體中有一個(gè)32bit成員叫st_info,低4bit表示符號(hào)的類型甜奄,高28bit符號(hào)的綁定信息柠横。綁定信息具體可見(jiàn)表3-15,符號(hào)類型可參見(jiàn)表3-16贺嫂。
Elf32_sym.st_shndx:如果符號(hào)定義在本目標(biāo)文件中滓鸠,它表示該符號(hào)所在的段在段表中的下標(biāo)雁乡,否則它具有其他意義第喳。st_shndx具體信息可見(jiàn)表3-17。
Elf32_sym.st_value:每個(gè)符號(hào)都有一個(gè)對(duì)應(yīng)值踱稍,它一般為變量和函數(shù)的地址曲饱。st_value的意義有如下幾種:
1悠抹、如果符號(hào)定義在目標(biāo)文件中,并且它不是COMMON塊類型扩淀,則st_value代表符號(hào)在段中的偏移楔敌。
2、如果符號(hào)定義在目標(biāo)文件中并且是COMMON塊類型驻谆,則st_value表示符號(hào)的對(duì)齊屬性卵凑。
3、在可執(zhí)行文件中st_value表示符號(hào)的虛擬地址胜臊。
鏈接器本身自帶的勺卢,不是你定義的,定義在鏈接腳本中的象对,但是你可以用的黑忱,這樣的符號(hào)是特殊符號(hào)。它們存在的時(shí)機(jī)是鏈接器鏈接生成可執(zhí)行文件時(shí)勒魔,此時(shí)鏈接器會(huì)將它們解析成正確的值甫煞,
書中P110舉了幾個(gè)具有代表性的特殊符號(hào)。
本小節(jié)明確了函數(shù)簽名的概念冠绢。
函數(shù)簽名:主要是指函數(shù)名和參數(shù)類型抚吠,其次是所在類和命名空間等。它用于區(qū)分不同函數(shù)弟胀。
編譯器和連接器會(huì)使用名稱修飾的辦法加工函數(shù)簽名使之成為修飾后名稱埃跷,在C++中為符號(hào)名。
不同的編譯器對(duì)函數(shù)簽名的修飾方法不同邮利,這導(dǎo)致不同種類的目標(biāo)文件無(wú)法互連弥雹。
原來(lái)C++編譯器已經(jīng)默認(rèn)定義了宏__cplusplus來(lái)兼容C語(yǔ)言和C++。
在不同目標(biāo)文件中含有相同全局性符號(hào)定義延届,這種情況被稱為強(qiáng)符號(hào)剪勿,它會(huì)引起符號(hào)重定義。
C/C++編譯器認(rèn)為未初始化的全局變量是弱符號(hào)方庭。
這個(gè)強(qiáng)弱符號(hào)是可以被定義的厕吉,所以強(qiáng)弱之別是根據(jù)定義來(lái)劃分的,并不針對(duì)符號(hào)的引用械念,P117代碼說(shuō)明了這一點(diǎn)头朱。
鏈接器根據(jù)符號(hào)的強(qiáng)弱來(lái)處理和選擇定義的全局變量:
1、不允許多次定義強(qiáng)符號(hào)龄减,否則報(bào)錯(cuò)项钮。
2、同一個(gè)符號(hào)在各目標(biāo)文件中出現(xiàn)了多次,但只有一個(gè)是強(qiáng)符號(hào)烁巫,那么編譯器選擇強(qiáng)符號(hào)的那個(gè)署隘。
3、如果一個(gè)符號(hào)在所有目標(biāo)文件中都是弱符號(hào)亚隙,那么編譯器選擇占用空間最大的一個(gè)磁餐。由此可見(jiàn)編譯器對(duì)于弱符號(hào)的選擇并不明顯,所以由弱符號(hào)造成的錯(cuò)誤也相對(duì)難以發(fā)現(xiàn)阿弃。
強(qiáng)引用:目標(biāo)文件對(duì)于非本目標(biāo)文件的符號(hào)引用诊霹,在鏈接成可執(zhí)行文件的過(guò)程中,如果找不到該符號(hào)的定義渣淳,就報(bào)未定義錯(cuò)誤畅哑。
弱引用:與強(qiáng)引用差不多,只不過(guò)在找不到符號(hào)時(shí)不報(bào)錯(cuò)水由。
強(qiáng)弱引用主要用于庫(kù)的鏈接荠呐。對(duì)于未定義的弱引用,編譯器為便于識(shí)別把它看作是某一值砂客,一般為0泥张。
弱符號(hào)與COMMON塊聯(lián)系較密切。
弱引用是可以手動(dòng)聲明的鞠值,如P118第一段代碼所示媚创。
弱符號(hào)的作用在于提供一個(gè)默認(rèn)的庫(kù)符號(hào),但是當(dāng)用戶想要自定義該符號(hào)的時(shí)候彤恶,該自定義符號(hào)就獲得了更高的優(yōu)先級(jí)钞钙。而弱引用的作用在于增強(qiáng)了程序的可擴(kuò)展性,因?yàn)橛辛巳跻贸绦蚬δ芨鼜?qiáng)声离,沒(méi)有弱引用程序也能正常運(yùn)行芒炼。
目標(biāo)文件和可執(zhí)行文件中都可能保存調(diào)試信息,ELF文件采用DWARF格式保存調(diào)試信息术徊。
由于調(diào)試信息與可執(zhí)行文件最終結(jié)果無(wú)關(guān)本刽,而且占用大量空間,所以在發(fā)布軟件時(shí)應(yīng)該去掉這些調(diào)試信息赠涮。