深入編譯,鏈接和運行

一.編譯和鏈接

1.預處理

  命令:gcc -E hello.c -o hello.i

  主要處理.c文件中以“#”開頭的預編譯指令

2.編譯

  命令:gcc -S hello.i -o hello.s

[1]詞法分析

[2]語法分析

[3]語義分析

  編譯器只能分析靜態語義(編譯期確定的語義)

  靜態語義有聲明,類型轉換,類型匹配

[4]優化後生成相應的彙編代碼文件

  中間語言生成,目標代碼生成與優化。

3.彙編

  命令:gcc -c hello.s -o hello.o

  彙編器是將彙編代碼轉化成機器可以執行的指令。

4.鏈接

重定位:絕對地址引用的位置“打補丁”,使其指向正確的地址

符號:函數或變量的起始地址

[1]地址和空間分配

[2]符號決議

[3]重定位


二.目標文件

基礎知識:

1】可執行文件格式有windows下的PE和linux下的ELF,都是COFF格式的變種。

2】靜態鏈接庫(windows下的.lib,linux下的.a)動態鏈接庫(windows下的.dll,linux下的.so)都按可執行文件格式存儲。


爲何將可執行文件的代碼段和數據段分開存放?

1】代碼段只讀,數據段可讀可寫,有利於分別保護

2】現代cpu的緩存被設計爲指令緩存和數據緩存分離,分開存放可提高cpu的緩存命中率。

3】運行多個進程時,有各自的數據段,共享代碼段,節省內存


程序示例:



查看目標文件的結構和內容:



目標文件 段的基本分佈


1.代碼段(.text)

  存放機器指令

2.數據段(.data)

  存放已經初始化的靜態變量,全局變量

3.只讀數據段(.rodata)

  存放只讀數據,一般爲只讀變量(const修飾的變量)和常量字符串

4.數據段(.bss)

  存放未初始化或初始化爲0的靜態變量,全局變量

  因爲數據全爲0,.data段存儲數據0是沒有必要的,因此在目標文件中.bss是預留的,沒有內容,不佔內存空間,運行時的確佔內存空間

  注:未初始化的全局變量在.comment段,故.bss的大小爲0x14=20字節,並非24字節。






ELF文件結構描述

1.文件頭




2.段表:描述每個段的基本信息

編譯器,鏈接器,裝載器都是通過段表來訪問和定位段的屬性的


ELF32_Shdr段描述符結構:每一個ELF32_Shdr結構體對應一個段



mian.o的段表及所有段的位置和長度

注:以2^2=4字節對齊,故有一小部分空餘



3.重定位表

.rel.text是針對.text的重定位表。在.text段有絕對地址的引用,那就是printf函數。.data段包含幾個常量,沒有絕對地址的引用。

4.字符串表

字符串表(.strtab):保存普通的字符串,比如符號名。

段表字符串表(.shstrtab)保存段表中的字符串,比如段名。


鏈接的接口----符號

1】在鏈接中,目標文件的相互拼合實際上是目標文件之間對地址的引用,即對函數和變量的地址的引用。

比如目標文件B用到了目標文件A中的foo函數,則稱目標文件A定義了foo函數,目標文件B引用了目標文件A中的foo函數。(同樣適用於變量)

2】在鏈接中,將函數和變量統稱爲符號,函數名和變量命爲符號名。

3】每一個目標文件有一個相對應的符號表。

符號表記錄了目標文件的所有符號,每一個符號對應一個符號值。對函數和變量來說,符號值就是它們的地址。


符號的類型

1】全局符號               @@@@@@鏈接過程只關心全局符號的相互粘合。

    1)定義在本目標文件的,可以被其他目標文件引用。eg:main,gdata1,gdata2,gdata3 

    2)外部符號:沒有定義在本目標文件,在本目標文件中引用。eg:printf 

2】局部符號

     只在編譯單元內部可見,對於鏈接過程沒有作用。eg:d.e.f,gdata4,gdata5,gdata6


5.符號表(.symtab)



符號修飾與函數簽名

1】爲了避免庫文件中的函數和全局變量名與目標文件中的名字起衝突,函數經編譯後要在符號名前加"_"。eg:foo----->_foo   (C語言)

2】名稱空間:解決多模塊的符號衝突問題    (c++)

3】c++符號修飾

   函數簽名:用於識別不同的函數。包含了函數的所有信息,包括函數名,參數列表,它所在的名稱空間和類。

   c++編譯器在編譯時會將函數(函數簽名)和變量的名字進行修飾,形成符號名。


extern"c"         符號的引用

c++編譯器會將 extern"c" 大括號內部的代碼當作C語言代碼處理。

C語言不支持 extern "c" 語法,爲兼容C語言和c++定義兩套頭文件,c++的宏"_cplusplus",c++編譯器在c++編譯程序時默認調用該宏。


弱符號與強符號-----》針對符號的定義,並非符號的引用。       (只適用於C語言)

1】 強符號:函數和初始化了的全局變量。弱符號:未初始化的全局變量。(在.COMMON塊)

2】鏈接器按如下規則處理不同目標文件中重複定義的符號:

    1)同名強符號,編譯錯誤。

    2)同名強,弱符號,選擇強符號。

    3)同名弱符號,選擇佔用內存大的。

3】強引用和弱引

強引用:若沒有找到符號的定義,鏈接器會報符號未定義的錯誤。

弱引用:若符號有定義鏈接器將該符號的引用決議。若沒有定義,鏈接器不會報錯。主要用於庫的鏈接過程。


爲何將未初始化的全局變量放在.comment段,不放在.bss段?????

答:未初始化的全局變量放在.comment段只針對編譯後的目標文件。在鏈接時,兩個目標文件鏈接爲一個可執行文件,若兩個目標文件出現了同名的弱符號,則選擇內存佔用大的,實際上未初始化的全局變量在鏈接後是放在.bss段的(此時已經選擇出了佔用內存大的弱符號)。而在編譯時,並不確定在別的源文件中是否有同名的弱符號,不可確定其最終的大小,因此將未初始化的全局變量暫時存放在.comment段。



三.靜態鏈接

空間與地址分配

1】相似段合併:相同性質的段進行合併,obj文件以2^2=4字節對齊,合併後以頁面(4k)對齊。

    .bss段不佔目標文件和可執行文件的空間,裝載時爲其分配空間,其只有虛擬地址空間。

2】調整段偏移和段長度,合併符號表。

程序示例:

             

a.c b.c編譯爲目標文件a.o b.o

a.o b.o 鏈接爲ab可執行文件


查看鏈接前後地址分配情況:




符號解析與重定位

1】重定位



2】重定位表


3】符號解析

鏈接時符號未定義的原因:1)鏈接時缺少了某個庫。2)輸入目標文件路徑不正確。3)符號的聲明與定義不一樣。


鏈接器掃描完所有輸入目標文件後,目標文件中未定義的符號應該能夠在全局符號表中找到,否則鏈接器報符號未定義錯誤。

(所有obj符號表中對符號引用的地方要找到符號定義的地方)


四.可執行文件的裝載與進程

可執行文件只有裝載到內存才能被CPU執行。

1.進程虛擬地址空間

  1】進程和程序的區別:

   程序:靜態的概念,預先編譯好的數據和指令的集合的文件。

   進程:動態的概念,運行中的程序。

  2】虛擬地址空間的大小與CPU的位數有關。

   32位CPU大小爲2^32=4G

  硬件決定了地址空間的最大理論上限,即硬件的尋址空間大小。

2.裝載的方式

  1】靜態裝入:將程序運行時需要的指令和數據全部加載到內存中執行。

  2】動態裝入:程序所需要的內存大於物理內存。

  思想:程序需要哪個模塊,就把哪個模塊裝入內存,如果不需要,就將其存放在磁盤。

      1)覆蓋裝入

      2)頁映射  頁面置換算法:FIFO  LRU

3.從操作系統的角度看可執行文件的加載

1】進程的建立

  1)創建獨立的虛擬地址空間。

    頁映射函數:創建虛擬地址空間到物理空間的映射關係。

  2)讀取可執行文件頭,建立可執行文件與虛擬地址空間的映射。

    程序執行發生頁錯誤時,操作系統在物理內存中分配一個物理頁,將缺頁從磁盤讀取到物理內存,建立虛擬頁到物理頁的映射關係。同時操作系統要知道缺頁位於可執行文件的哪個位置,於是建立可執行文件與虛擬地址空間的映射關係。

    可執行文件又叫映像文件。

    Linux中將虛擬地址空間的一個段叫做虛擬內存區域(VMA)。

  3)將CPU的指令寄存器設置爲可執行文件的入口地址,啓動運行。

2】頁錯誤

4.進程虛存空間分佈

1】ELF文件鏈接視圖和執行視圖

  段數量增多時,爲減少空間浪費,可執行文件到虛擬地址空間的映射時,對於相同權限的段,合併到一起當作一個段進行映射。映射到同一個VMA。

  合併後的一個段叫做"segment",其中包含一個或多個屬性類似的"section"。

  從鏈接的角度,可執行文件按“section”存儲,可執行文件爲鏈接視圖。從裝載的角度,可執行文件按“segment”劃分,可執行文件爲執行視圖。

  目標文件鏈接成可執行文件時,鏈接器儘量將相同權限屬性的段分配在同一空間。可執行文件映射時,是以“segment”來映射的。

  正如描述“section”屬性的結構叫做段表,而描述“segment”屬性的結構叫做程序頭(program header)。描述了ELF文件該如何被操作系統映射到進程的虛擬空間。




ELF可執行文件與進程虛擬空間的映射關係


2】堆和棧

通過查看/proc來查看進程虛擬空間分佈:


進程虛擬地址空間的概念:操作系統通過將進程劃分成一個個VMA來管理進程的虛擬空間,基本原則是將相同屬性的,有相同映像文件的映射成一個VMA。

3】堆的最大申請數量

4】段地址對齊

各個段接壤的部分共享一個物理頁面,然後將該物理頁面分別映射兩次。

4】進程棧初始化

5】Linux內核裝載ELF過程簡介

詳情請參見《程序員的自我修養-鏈接,裝載與庫》

  



  










































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章