編譯鏈接

先來通過一張圖看看這一過程:


那在預編譯、編譯、彙編以及鏈接過程中具體都做了哪些工作呢?


預編譯:自處理過程,帶#都是預處理(包括#if0),字處理(刪除註釋)。

1、將所有的“#define”刪除,並且展開所有的宏定義;

2、處理所有條件預編譯指令,例如“#if”、“#ifdef”、“#elif”、“#else”、“endif”;

3、處理“#include”預編譯指令,將被包含的文件插入到該預編譯指令的位置;

4、刪除所有的註釋“//”和“/**/”;

5、添加行號和文件名錶示,比如#2 “hello.c” 2,以便於編譯時編譯器產生調試用的行號信息以及編譯時產生編譯錯誤或警告能夠顯示行號;

6、保留所有的#pragma編譯器指令,因爲編譯器必須要使用他們。

編譯:詞法分析、語法分析、語義分析、代碼優化、彙總所有的符號。

彙編:將彙編語言翻譯成機器碼,構建.o文件(二進制可重定位的目標文件)的組成格式。

鏈接

1、合併所有.obj文件的段,調整段地址偏移和段長度;

2、合併所有符號,進行符號解析(所有.obj符號表中對符號引用的地方都要找到該符號定義的地方);

3、空間與地址分配;

4、符號重定位(所有.obj文件的global符號進行處理,local的符號不做任何處理)。


我們通過上一篇《程序加載》中的例子來具體看一下:

  int gdata1 = 10;
  int gdata2 = 0;
  int gdata3;
  
  static int gdata4 = 11;
  static int gdata5 = 0;
  static int gdata6;
  
  int main()
  {
      int a = 12;
      int b = 0;
      int c;
 
      static int d = 13;
      static int e = 0;
      static int f;
  
      return 0;
  }         

放在.text段的有main、a、b、c;放在.data段的有gdata1、gdata4、d;放在.bss段的有gdata2、gdata3、gdata5、gdata6、e、f。我們來看看到底是不是這樣?




從上圖可以看出虛擬內存地址(VMA)、加載的內存地址(LMA)都是0也就說明編譯的過程是不分配內存地址的,鏈接過程中符號解析完成後才分配。.text的File off也就是偏移並不是從0開始,也就說前邊還有一部分。這裏也有一個比較奇怪的現象,.bss大小爲20字節,但是起始偏移和.comment段起始偏移是一樣的,也就說明.bss並沒有佔用文件的空間。這也就回答了上一篇提出的問題,.bss節省的是文件的空間而不是虛擬地址空間。


從下圖可以看到所有段的信息。



從下圖可以看到文件頭部分。


從圖中可以看出文件頭52字節,換算成十六進制是0x34 ,我們假設文件頭爲0x00,那麼ELF Header偏移到0x34處。正好接下來是.text段。


通過下面的命令打印各段的內容如下圖所示:



如果有一個字符常量呢?比如char*p = “hello world!”


可以看到多了一個.rodata段,也就是隻讀數據段,字符串常量放在這個段中。


.bss存都不存我們怎麼知道有哪些數據呢?通過讀文件頭可以知道section table 在哪裏。文件的段表section table,保存了文件有哪些段,偏移量以及大小。初始化爲0的沒有初始化的都是0,沒必要存儲,但是要知道它是存在的,把它的信息存儲下來。


通過上面的分析,我們可以得到.o文件的結構:


上面說了放在.data段的有gdata1、gdata4、d,一共12字節;放在.bss段的有gdata2、gdata3、gdata5、gdata6、e、f,應該是24字節,換算成十六進制應該是18,從上面的圖中看到的是14,少了哪一個數據?又放在哪呢?

這就涉及到C語言中的強類型和弱類型,gdata3爲弱類型。在編譯時不能確定其他文件中是否有比它更強的符號或內存佔用更大的符號,但是加static變量只是本文件可見,鏈接時所有.obj文件的global符號進行處理,local的符號不做任何處理。


從上圖可以看出gdata3放在了*COM*塊。



在main.o中gdata10和sum只是符號引用,前邊是*UND*沒有定義的,所以鏈接時所有.obj符號表中對符號引用的地方都要找到該符號定義的地方。


指令編譯時,沒有分配地址,gdata10只能是0x00000000,前邊說了這個地址是不能訪問的,函數的地址爲0xfffffffc,這個地址是內核空間,也是不能用的。函數調用函數時涉及到指令的跳轉,當前指令執行完後,怎麼知道下一行指令的地址呢?pc寄存器記錄了下一條要執行的指令的地址。當我們執行31行指令時,pc寄存器存放的是36行地址,函數地址就是pc寄存器+偏移量,即36-4=32。


.obj對齊方式爲4字節對齊,而可執行文件爲頁面對齊。所以鏈接時合併所有.obj的段。那麼如何合併呢?


所有相同屬性的段(可讀可寫,可讀可執行,只讀)進行合併,組織在一個頁面上。要重新調整偏移量的段長度,合併符號表。


接下來就該符號重定位,這一步又做了什麼呢?我們把兩個文件鏈接起來,通過查看符號表看到每個符號都有地址,之前沒有地址的地方就要改變,這也就是符號的重定位。





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