目標檔案裡有什麼?

2021-10-13 12:50:30 字數 3491 閱讀 9704

目標檔案從結構上講,它是已編譯後的可執行檔案格式(windows的.obj或linux的.o),只是還沒有經過鏈結的過程。它跟可執行檔案的內容和結構很類似,所以一般跟可執行檔案格式一起採用一種格式儲存。

無論是linux下的elf(executable linkable format)或windows下的pe(portable executable), 都是coff(common file format)格式的變種。目標檔案與可執行檔案的內容和結構很相似,動態鏈結庫dll和靜態鏈結庫(windows的.lib或linux的.a)都是按照可執行檔案格式儲存

linux下的elf分類:

l可執行檔案:包含可以直接執行的程式

l共享目標檔案:包含**和資料,鏈結器可以使用這種檔案與其他的可重定位檔案和共享目標檔案鏈結,產生新的目標檔案。動態鏈結器也可以用這種檔案與可執行檔案結合,作為程序映像的一部分來執行

目標檔案的內容至少有編譯後的機器指令**、資料。還有鏈結時所需的一些資訊。比如符號表、除錯資訊、字串等。一些目標檔案將這些資訊按不同的屬性,以「節」的形式儲存,有時也叫「段」。

為什麼要分段:

1、程式被裝載以後資料和指令分別被放在兩個虛存區域,乙個唯讀、乙個可讀寫,避免程式指令有意無意的修改

2、快取記憶體的應用,指令和資料區的分離有利於提高程式的區域性性

3、防止多個副本同時存在於記憶體中

源**編譯後的機器指令常放在**段 「.text或 .code」,已初始化的全域性變數和區域性靜態變數資料常放在資料段「.data」。未初始化的全域性變數和區域性靜態變數一般放在「.bss」段裡。elf檔案的開頭是乙個「檔案頭」,描述了整個檔案的檔案資訊,包括檔案是否可執行、靜態鏈結or動態鏈結、入口位址、目標硬體等等。檔案頭還包含乙個段表,描述了檔案中各個段在檔案中的偏移位置及段的屬性等。段表其實就是乙個描述檔案中各個段的陣列。檔案頭後面就是各個段的內容。

總體來說,程式原始碼被編譯後主要分成兩端:程式指令和程式資料,**段屬於程式指令,而資料段和.bss段屬於程式資料。除了之外,還有唯讀資料段(.rodata,const變數、字串常量)、注釋資訊段(.comment)和堆疊提示段(.note.gnu-stack)。

static int x1=0;

static int x2=1; 

這是x1會存放在.bss中,x2存放在.data中,賦值為0,認為是未初始化。

elf目標檔案的格式最前部是elf檔案頭,它包含了描述整個檔案的基本屬性,比如elf檔案版本、目標機器型號、程式入口位址等資訊。緊接著是elf檔案的各個段。其中elf檔案中與段有關的重要結構就是段表。該錶描述了elf檔案包含的所有段的資訊。

elf檔案頭中定義了:elf魔數、檔案機器位元組長度、資料儲存方式、版本、執行平台、abi版本、elf重定位型別、硬體平台、硬體平台版本、入口位址、程式頭入口和長度、段表的位置和長度以及段的數量等。

魔數:16個位元組被elf標準規定為用來標識elf檔案平台屬性。

段表:段表描述了elf的各段的資訊,比如每個段的段名、段的長度、在檔案中的偏移、讀寫許可權及段的其他屬性。段表在elf中的位置由elf標頭檔案中的「e_shoff」成員決定。段表的結構比較簡單,它是乙個以「elf32_shdr」結構體為元素的陣列。陣列元素的個數等於段的個數,每個「elf_shdr」結構體對應乙個段。「elf32_shdr」又被稱為段描述符。每個段描述符為40個位元組。對於編譯器和聯結器來說,主要決定段的屬性的是段的型別(sh_type)和段的標誌位(sh_flags)。段的標誌位(sh_flag)表示該段在程序虛擬位址空間中的屬性,比如是否可寫,是否可執行等。

編譯器、聯結器和裝載器都是依靠段表來定位和訪問各個段的屬性的。

重定位表:「.rel.text」段,它的型別(sh_type)為「sht_rel」,也就是說它是乙個重定位表(relocationtable)。「.rel.text」段表示針對「.text」段的重定位表。

字串表:字串表把字串集中起來存放到乙個表,然後使用字串在表中的偏移來引用字串。一般字串表在elf檔案中也以段的形式儲存,常見的名字為「.strtab」或「.shstrtab」。分別表示字串表和段字串表。

目標檔案b用到目標檔案a中的函式「foo」,那麼我們稱目標檔案a定義了函式「foo」,稱目標檔案b引用了目標檔案a中的函式「foo」。

在鏈結中,我們講函式和變數統稱為符號(symbol),函式名和變數名就是符號名(symbol name).

每個目標檔案都會有乙個相應的符號表(symbol table),這個表裡面記錄了目標檔案所用到的所有符號。每個定義的符號乙個乙個對應的值,叫做符號值,對於變數和函式來說,符號值就是他們的位址。符號進行分類:

l全域性符號,定義在本目標檔案中,可以被其他目標檔案引用

l 外部符號,在本目標檔案引用,卻沒有定義

l 段名,這種符號由編譯器產生,它的值就是該段的起始位址

l 區域性符號,編譯單元內部可見

l 行號資訊,可選的

elf符號表的成員:符號名、對應的值、符號大小、型別和繫結資訊、符號所在的段

特殊符號:ld作為可執行檔案來鏈結產生可執行檔案時,它會為我們定義很多特殊符號,這些符號並沒有在你的程式中定義,但是你可以直接宣告並且引用它,我們稱之為特殊符號。比如:程式起始位址、**段結束位址、資料段結束位址、程式結束位址

符號修飾與函式簽名:防止符號名稱衝突,c語言源**檔案中的所有全部變數和函式編譯後在相應的符號前加下劃線"_"

c++引入namespace來解決多模組的符號衝突

gcc通過引數"-fleading-underscore"和"-fno-leading=underscore"來開啟和關閉是否在c語言符號前加下劃線

c++符號修飾:參見gcc的名稱修飾標準

c++的源**編譯後的目標檔案中所使用的符號名是相應的函式和變數的修飾後名稱。不同的編譯器採用不同的名字修飾方法。

extern   「   c 」,在 c++   編譯器中不起作用

gcc的基本c++名稱修飾方法:所有的符號都以"_z"開頭,對於巢狀的名字(在命名空間或者在類裡面的),後面緊跟著乙個"n",然後是各個命名空間和類的名字,每個名字前是名字字串的長度,再以"e"結尾。

對於 c/c++語言來說,編譯器預設函式和初始化了的全域性變數為強符號,未初始化的全域性變數為弱符號。gcc中__attribute__((weak))來定義任何乙個強符號為弱符號。(針對符號定義而非引用)

針對強符號的概念,鏈結器就按如下規則處理與選擇多次定義的全域性符號:不允許強符號被多次定義(即不同的目標檔案中不能有同名的強符號);如果有多個強符號定義,則鏈結器報重複定義錯誤。在強符號和弱符號同時存在的乙個符號選擇強符號。

某個在所有目標中都是弱符號,則選擇占用空間最大的弱引用指定_attribute__((weakref)),對未定義的弱引用,鏈結器預設為0,不會報錯。

目標檔案裡有什麼

1.text段是 段,比如main程式就存放在這裡 data段存放已初始化的資料而且初始化不為0 bss段存放未初始化或初始化為0的資料 英文含義 以符號開始的塊 在這裡我們引發出幾個問題 1 我們都知道.bss段不佔空間,它到底不佔 的空間?虛擬位址空間還是檔案空間?答案是檔案空間,因為對於.bs...

目標檔案裡有什麼 揭秘目標檔案

程式源 被編譯後生成的機器指令被放在 段 text 全域性變數和區域性靜態變數被放在資料段 data 除此之外還有程式裡邊的唯讀變數 如const修飾的變數 和字串常量被分配在唯讀資料段 rodata 注釋資訊段 comment 堆疊提示段 nute.gnu stack 未初始化的全域性變數和靜態區...

知識積累 目標檔案裡有什麼(1)

程式源 被編譯後主要分成兩種段 程式指令和程式資料,屬於程式指令,而資料段和.bss屬於程式資料。為什麼程式指令和資料不混在在一起而是分開放?1.因為程式被裝載後,資料和指令分別被對映到兩個虛存區域。由於資料區域對於程序來說是可讀寫的。而指令區域對於程序來說是可讀的。所以這兩個虛存區域的許可權可以分...