csapp:鏈接

從c源代碼變爲可執行文件的四個步驟

  1. 預處理:得到.i文件。預處理之後還是一個可讀文本文件,裏面不存在宏定義。所以預處理做的事情有:(1)刪除#define並展開所定義的宏 (2)處理預編譯指令如#ifdef (3)刪掉所有註釋 (4)插入頭文件到#include處等。
  2. 編譯:得到.s文件。變成彙編文件,還是可讀文本文件
  3. 彙編:得到.o文件。變成可重定向目標文件,不可讀二進制代碼
  4. 鏈接:前三個階段將.c文件變成了可重定位目標文件.o,鏈接是將多個可重定位目標文件合併,變成可執行目標文件。

鏈接要完成的兩個任務

  1. 符號解析。符號:函數、全局變量、靜態變量。符號解析就是將每一個符號定義與符號引用關聯起來。
  2. 重定位:編譯器和鏈接器生成地址從0開始的代碼與數據節。鏈接的符號解析就是爲了將符號定義與內存位置關聯起來,從而重定位這些節,然後修改所有對這些節的引用,使得它們指向這個內存地址。

鏈接的好處

  1. 可模塊化:可將源程序分爲多個文件,可以構建公共函數庫。
  2. 效率高:時間上,分開編譯,修改時只需要重新編譯那一個模塊即可。空間上,不需要將整個公共庫的代碼加載進內存,只要加載調用函數代碼。

鏈接本質:合併相同的"節"。

目標文件三種形式

  • 可重定位目標文件.o。所有變量和函數地址從0開始。
  • 可執行目標文件。無後綴名或默認爲a.out。已經有確定的地址,爲虛擬地址中的地址。
  • 共享目標文件.so。特殊的可執行目標文件,能在運行時被裝入到內存並自動被鏈接。

ELF

標準的目標文件格式:可執行可鏈接格式。

最小單位爲節,有.text、.data、.bss等節

段中包含多個訪問狀態相同的節,如可讀段

兩種視圖:

  • 鏈接視圖:可重定位目標文件
  • 執行試圖:可執行目標文件

可重定位目標文件格式

 

下面例子中elf文件的查看結果以main.c與func_of_static.c兩個函數獲得:

/* main.c */
#include<stdio.h>

int array[2] = {1, 2};
int f();
int g();
int h();

int main() 
{
    int val_f = f();
	int val_g = g();
	int val_h = h();
	printf("%d %d %d\n",val_f,val_g,val_h); 
    return 0;
}
/* func_of_static.c*/
static int x = 15;

int f() {
    static int x = 17;
    return x++;
}

int g() {
    static int x = 19;
    return x += 14;
}

int h() {
    return x += 27;
}

ELF頭

  • ELF頭文件的頭幾個字節爲魔數,用來標識文件的類型,如是可重定位的目標文件還是可執行的目標文件。
  • 定義了機器的信息、魔數、節頭表的文件偏移、起始位置、節頭表中條目的大小和數量、程序的入口地址等
  • 看可重定位目標文件ELF頭:readelf -h main.o

example:

對於main.c,查看ELF文件頭如下:

先用gcc 加-c參數編譯生成.o文件。

readelf -h main.o

從elf頭開始,我們可以看到魔數。看出文件類型爲可重定位文件。所以該elf文件沒有程序頭,入口地址爲0.

節頭表

  • readelf -S test.o
  • 節頭表定義了各節的節名、起始地址(可重定位目標文件中爲0)、偏移、訪問屬性等

example

我們再用-S看一下func_of_static.o的節頭表

readelf -S func_of_static.o

從圖中我們可以清晰地看出每一個節的大小何偏移量以及節頭表的偏移量,因爲是可重定位文件,所以地址都是從0開始。

根據這節頭表,我們就可以畫出可重定位目標文件func_of_static.o的結構:

符號表

  • .symtab節
  • readelf -s main.o
  • 記錄符號在對應節中 偏移量(value)、符號對應目標字節數(size)、符號類型、對應目標所在節等信息。

example

注意在func_of_static.c代碼中定義了三次靜態變量x,但它們表示不同的變量,可以看到符號表中name項將變量重新命了名。

可執行目標文件格式

  • 多一個程序表頭(段頭表):程序頭表描述可執行文件中的節與虛擬 空間中的存儲段之間的映射關係,一個表項(32B)說明虛擬地址空間中 一個連續的段或一個特殊的節
  • 多一個.init節,定義了_init()函數,用於可執行文件執行時的初始化。
  • 少兩個.rel節,因爲無需重定位

程序表頭(段頭表)

  • readelf -l main
  • 描述可執行文件中的節與虛擬空間中的存儲段之間的映射關係
  • 一個表項說明虛擬地址空間中一個連續的段或一個特殊的節

example

以main文件爲例

 

 

 

 

 

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