1. 程式人生 > >elf檔案格式與動態連結庫(非常之好)-----不可不看

elf檔案格式與動態連結庫(非常之好)-----不可不看

}

當我們把hello.c編譯為目標檔案時,我們並沒有在原始檔中定義printf這個函式,所以彙編器也不知道printf這個函式的具體的地址,所以在目標檔案中就會留下printf這個符號。以下的工作就交給聯結器了,聯結器會找到這個函式的入口地址然後傳遞給這個檔案最終形成可執行檔案。這個過程就叫做relocations。a.out格式的可執行檔案是沒有這種relocation的功能的,核心不會執行其中還有未知函式的入口地址的可執行檔案的。在目標檔案中當然可以relocation,只不過聯結器需要把未知函式的入口地址完全找到,生成可執行檔案才行。這樣就有一個很尷尬的問題,在 a.out格式中極其難以實現動態連線技術。要知道為什麼現在的Unix幾乎都是用的elf格式的可執行檔案就要了解a.out格式的短處。

a.out的符號是極其有限的,在/usr/include/linux/asm/a.out.h中定義了一個結構exec就是:
程式碼:

struct exec { unsigned long a_info; /*Use macros N_MAGIC, etc for access */ unsigned a_text; /* length of text, in bytes */ unsigned a_data; /* length of data, in bytes */ unsigned a_bss; /* length of uninitialized data area for file, in bytes*/ unsigned a_syms; /* length of symbol table data in file, in bytes */ unsigned a_entry; /* start address */ unsigned a_trsize; /*length of relocation info for text, in bytes */ unsigned a_drsize; /*length of relocation info for data, in bytes */ };

在這個結構中更本沒有指示每個段在檔案中的開始位置,核心載入器具有一些非正式的方法來載入可執行檔案的。明顯的,a.out 是不支援動態連線的。(在內部不支援動態連線,用某些技術也是可以實現a.out的動態連線)

要了解elf可執行檔案的執行方式,我們有必要討論一下動態連線技術。很多人對動態連線技術十分熟悉,但是很少有人真正瞭解動態連線的內部工作方式。回想沒有動態連線的日子,程式設計師寫程式時不用什麼都從頭開始,他們可以呼叫定義的很好的函式,然後再用聯結器與函式庫連線。這樣的話使得程式設計師更加有效率,但是一個十分重要的問題出現了:這樣產生的可執行檔案就會很大。因為聯結器把程式需要用的所有函式的程式碼都複製到了可執行檔案中去了。這種連線方式就是所謂的靜態連線,與之相對的就是動態連線。聯結器在可執行檔案中標記出程式呼叫外部函式的位置,並不把程式碼複製進去,只是標出函式在動態連線庫中的位置。用這樣的方式生成的特殊可執行檔案就是動態連線的。在執行這種動態程式時,系統在執行時把該程式呼叫的外部函式地址對映到程式地址,這就是所謂的動態連線,系統就有一個程式叫做動態聯結器,在動態連線的程式執行前都要先把地址對映好。很顯然的,必須有一種機制保證動態連線的程式中的函式地址正確地指向了動態連線庫的某個函式地址。這就需要討論一下elf可執行檔案格式處理動態連線的機制了。

elf的動態連線庫是記憶體位置無關的,就是說你可以把這個庫載入到記憶體的任何位置都沒有影響。這就叫做position independent。而a.out的動態連線庫是記憶體位置有關的,它一定要被載入到規定的記憶體地址才能工作。在編譯記憶體位置無關的動態連線庫時,要給編譯器加上 -fpic選項,讓編譯器產生的目標檔案是記憶體位置無關的還會盡量減少對變數引用時使用絕對地址。把庫編譯成記憶體位置無關會帶來一些花費,編譯器會保留一個暫存器來指向全域性偏移量表(global offset table (or GOT for short)),這就會導致編譯器在優化程式碼時少了一個暫存器可以使用,但是在最壞的情況下這種效能的減少只有3%,在其他情況下是大大小於3%的。

Elf的另一個特點是它的動態連線庫是在執行時處理符號的,這是通過用符號表和再佈置(relocation)表來實現的。在載入檔案時並不能立即執行,要在處理完符號表把所有的地址都relocation完後才可以執行。這個聽起來有點複雜而且可能導致檔案執行慢,不過對elf做了很大的優化後,這種減慢已經是微不足道的了。理論上說不是用-fpic選項編譯出來的目標檔案也可以用作動態連線庫,但是在執行時會需要做數目極大的 relocation,這是對執行速度有極大影響的。這樣的程式效能是很差的,幾乎沒有可用性。

當從動態連線庫中讀一個全域性變數時與從非-fpic編譯的目標檔案讀是不同的。讀動態連線的庫中的變數是通過GOT來尋找到目標變數的,GOT已經由某一個暫存器指向了。GOT本生就是一個指標列表,找到GOT中的某一個指標就可以讀到所要的全域性變量了,有了GOT我們要讀出一個變數只要做一次 relocation。

下面我們來看看elf檔案中到底有些什麼資訊:
程式碼:

$:cat hello.c