適於鏈接的可重定位文件(relocatable file),包含二進制代碼和數據,能與其他可重定位對象文件在編譯時合並創建出一個可執行文件。
這是由匯編器匯編生成的 .o 文件。後面的鏈接器(link editor)拿一個或一些 Relocatable object files 作為輸入,經鏈接處理後,生成一個可執行的對象文件 (Executable file) 或者一個可被共享的對象文件(Shared object file)。我們可以使用 ar 工具將眾多的 .o Relocatable object files 歸檔(archive)成 .a 靜態庫文件。如何產生 Relocatable file,你應該很熟悉了,請參見我們相關的基本概念文章和JulWiki。另外,可以預先告訴大家的是我們的內核可加載模塊 .ko 文件也是 Relocatable object file。
可執行的對象文件(Executable file)
適於執行的可執行文件(executable file),包含可以直接拷貝進行內存執行的二進制代碼和數據。用於提供程序的進程映像,加載的內存執行。
這我們見的多了。文本編輯器vi、調式用的工具gdb、播放mp3歌曲的軟件mplayer等等都是Executable object file。你應該已經知道,在我們的 Linux 系統裡面,存在兩種可執行的東西。除了這裡說的 Executable object file,另外一種就是可執行的腳本(如shell腳本)。注意這些腳本不是 Executable object file,它們只是文本文件,但是執行這些腳本所用的解釋器就是 Executable object file,比如 bash shell 程序。
可被共享的對象文件(Shared object file)
共享目標文件(shared object file),一種特殊的可重定位對象文件,能在加載時或運行時,裝載進內存進行動態鏈接。連接器可將它與其它可重定位文件和共享目標文件連接成其它的目標文件,動態連接器又可將它與可執行文件和其它共享目標文件結合起來創建一個進程映像。
這些就是所謂的動態庫文件,也即 .so 文件。如果拿前面的靜態庫來生成可執行程序,那每個生成的可執行程序中都會有一份庫代碼的拷貝。如果在磁盤中存儲這些可執行程序,那就會占用額外的磁盤空間;另外如果拿它們放到Linux系統上一起運行,也會浪費掉寶貴的物理內存。如果將靜態庫換成動態庫,那麼這些問題都不會出現。動態庫在發揮作用的過程中,必須經過兩個步驟:
鏈接編輯器(link editor)拿它和其他Relocatable object file以及其他shared object file作為輸入,經鏈接處理後,生存另外的 shared object file 或者 executable file。
在運行時,動態鏈接器(dynamic linker)拿它和一個Executable file以及另外一些 Shared object file 來一起處理,在Linux系統裡面創建一個進程映像。
本質上,對象文件只是保存在磁盤文件中的一串字節,每個系統的文件格式都不盡相同:
Bell實驗室的第一個Unix系統使用 a.out格式。
System V Unix的早期版本使用 Common Object File Format(COFF)。
Windows NT使用COFF的變種,叫做 Portable Executable(PE)。
現代Unix系統,包括Linux、新版System V、BSD變種、Solaris都使用 Executable and Linkable Format(ELF)。
ELF(Executable and Linking Format)是一種對象文件的格式,用於定義不同類型的對象文件(Object files)中都放了什麼東西、以及都以什麼樣的格式去放這些東西。它自最早在 System V 系統上出現後,被 xNIX 世界所廣泛接受,作為缺省的二進制文件格式來使用。可以說,ELF是構成眾多xNIX系統的基礎之一。
ELF代表Executable and Linkable Format。他是一種對可執行文件、目標文件和庫使用的文件格式。
他在Linux下成為標准格式已經很長時間, 代替了早年的a.out格式。ELF一個特別的優點在於, 同一文件格式可以用於內核支持的幾乎所有體系結構上, 這不僅簡化了用戶空間工具程序的創建, 也簡化了內核自身的程序設計。例如, 在必須為可執行文件生成裝載程序例程時。
但是文件格式相同並不意味著不同系統上的程序之間存在二進制兼容性, 例如, FreeBSD和Linux都使用ELF作為二進制格式。盡管二者在文件中組織數據的方式相同。但在系統調用機制以及系統調用的語義方面, 仍然有差別。這也是在沒有中間仿真層的情況下, FreeBSD程序不能在linux下運行的原因(反過來同樣是如此)。
有一點是可以理解的, 二進制程序不能在不同體系結構交換(例如, 為Alpha CPU編譯的Linux二進制程序不能在Sparc Linux上執行), 因為底層的體系結構是完全不同的。但是由於ELF的存在, 對所有體系結構而言, 程序本身的相關信息以及程序的各個部分在二進制文件中編碼的方式都是相同的。
Linux不僅將ELF用於用戶空間程序和庫, 還用於構建模塊。內核本身也是ELF格式。
ELF是一種開放格式, 其規范可以自由獲取。
在ELF格式出來之後,TISC(Tool Interface Standard Committee)委員會定義了一套ELF標准。你可以從這裡(http://refspecs.freestandards.org/elf/)找到詳細的標准文檔
20世紀90年代,一些廠商聯合成立了一個委員會(TISC委員會),起草並發布了一個ELF文件格式標准供公開使用,並且希望所有人能夠遵循這項標准並且從中獲益。1993年,委員會發布了ELF文件標准。當時參與該委員會的有來自於編譯器的廠商,如Watcom和Borland;來自CPU的廠商如IBM和Intel;來自操作系統的廠商如IBM和Microsoft。1995年,委員會發布了ELF 1.2標准,自此委員會完成了自己的使命,不久就解散了。所以ELF文件格式標准的最新版本為1.2。
文件類型 e_type成員表示ELF文件類型,即前面提到過的3種ELF文件類型,每個文件類型對應一個常量。系統通過這個常量來判斷ELF的真正文件類型,而不是通過文件的擴展名。相關常量以“ET_”開頭,
TISC委員會前後出了兩個版本,v1.1和v1.2。兩個版本內容上差不多,但就可讀性上來講,我還是推薦你讀 v1.2的。因為在v1.2版本中,TISC重新組織原本在v1.1版本中的內容,將它們分成為三個部分(books):
a) Book I
介紹了通用的適用於所有32位架構處理器的ELF相關內容
b) Book II
介紹了處理器特定的ELF相關內容,這裡是以Intel x86 架構處理器作為例子介紹
c) Book III
介紹了操作系統特定的ELF相關內容,這裡是以運行在x86上面的 UNIX System V.4 作為例子介紹
值得一說的是,雖然TISC是以x86為例子介紹ELF規范的,但是如果你是想知道非x86下面的ELF實現情況,那也可以在http://refspecs.freestandards.org/elf/中找到特定處理器相關的Supplment文檔。比方ARM相關的,或者MIPS相關的等等。另外,相比較UNIX系統的另外一個分支BSD Unix,Linux系統更靠近 System V 系統。所以關於操作系統特定的ELF內容,你可以直接參考v1.2標准中的內容。
#include
#include
// 不指定寄存器實現兩個整數相加
int Add(int a, int b)
{
__asm__ __volatile__
(
//"lock;\n"
"addl %1,%0;\n"
: "=m"(a)
: "r"(b), "m"(a)
// :
);
return a;
}
#include
#include
// 不指定寄存器實現兩個參數相減
int Sub(int a, int b)
{
__asm__ __volatile__
(
"subl %1, %0;"
: "=m"(a)
: "r"(b), "m"(a)
// :
);
return a;
}
#include
#include
int main(void)
{
int a = 3, b = 5;
printf("%d + %d = %d\n", a, b, Add(a, b));
printf("%d - %d = %d\n", a, b, Sub(a, b));
return EXIT_SUCCESS;
}
target=testelf_normal testelf_dynamic testelf_static
MAIN_OBJS=testelf.o
SUBS_OBJS=add.o sub.o
DYNA_FILE=libtestelf.so
STAT_FILE=libtestelf.a
all:$(target)
%.o : %.c
$(CC) -c $^ -o $@
clean :
rm -rf $(MAIN_OBJS) $(SUBS_OBJS)
rm -rf $(DYNA_FILE) $(STAT_FILE)
rm -rf $(target)
# Complie the execute
testelf_normal:$(MAIN_OBJS) $(SUBS_OBJS)
gcc $^ -o $@
testelf_dynamic:$(MAIN_OBJS) $(DYNA_FILE)
gcc $^ -o $@ -L./ -ltestelf
testelf_static:$(MAIN_OBJS) $(STAT_FILE)
gcc testelf.o -o $@ -static -L./ -ltestelf
# Complie the Dynamic Link Library libtestelf.so
libtestelf.so:$(SUBS_OBJS)
gcc -fPCI -shared $^ -o $@
# Complie the Static Link Library libtestelf.so
libtestelf.a:$(SUBS_OBJS)
ar -r $@ $^
我們編寫了兩個庫函數分別實現add和sub的功能, 然後編寫了一個測試代碼testelf.c調用了Add和Sub.
然後我們的Mmakefile為測試程序編寫了3分程序
普通的程序testelf_normal, 由add.o sub.o 和testelf.o直接鏈接生成
動態鏈接程序testelf_dynamic, 將add.o和sub.o先鏈接成動態鏈接庫libtestelf.so, 然後再動態鏈接生成testelf_dynamic
靜態鏈接程序testelf_static, 將add.o和sub.o先靜態鏈接成靜態庫libtestelf.a, 然後再靜態鏈接生成可執行程序testelf_staticke
我們在源代碼目錄執行make後會完成編譯, 編譯完成後
add.o, sub.o和testelf.o是可重定位的對象文件(Relocatable file)
libtestelf.so是可被共享的對象文件(Shared object file)
testelf_normal, testelf_dynamic和testelf_static是可執行的對象文件(Executable file)
如下圖所示
ELF文件由各個部分組成。
為了方便和高效,ELF文件內容有兩個平行的視角:一個是程序連接角度,另一個是程序運行角度
首先圖的左邊部分,它是以鏈接視圖來看待elf文件的, 從左邊可以看出,包含了一個ELF頭部,它描繪了整個文件的組織結構。它還包括很多節區(section)。這些節有的是系統定義好的,有些是用戶在文件在通過.section命令自定義的,鏈接器會將多個輸入目標文件中的相同的節合並。節區部分包含鏈接視圖的大量信息:指令、數據、符號表、重定位信息等等。除此之外,還包含程序頭部表(可選)和節區 頭部表,程序頭部表,告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。而節區頭部表(Section Heade Table)包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
需要注意地是:盡管圖中顯示的各個組成部分是有順序的,實際上除了 ELF 頭部表以外,其他節區和段都沒有規定的順序。
右半圖是以程序執行視圖來看待的,與左邊對應,多了一個段(segment)的概念,編譯器在生成目標文件時,通常使用從零開始的相對地址,而在鏈接過程中,鏈接器從一個指定的地址開始,根據輸入目標文件的順序,以段(segment)為單位將它們拼裝起來。其中每個段可以包括很多個節(section)。
elf頭部
除了用於標識ELF文件的幾個字節外, ELF頭還包含了有關文件類型和大小的有關信息, 以及文件加載後程序執行的入口點信息
程序頭表(program header table)
程序頭表向系統提供了可執行文件的數據在進程虛擬地址空間中組織文件的相關信息。他還表示了文件可能包含的段數據、段的位置和用途
段segment
各個段保存了與文件愛你相關的各種形式的數據, 例如,符號表、實際的二進制碼、固定值(如字符串)活程序使用的數值常數
節頭表section
包含了與各段相關的附加信息。
在具體介紹ELF的格式之前,我們先來了解在ELF文件中都有哪些數據類型的定義:
ELF數據編碼順序與機器相關,為了使數據結構更加通用, linux內核自定義了幾種通用的數據, 使得數據的表示與具體體系結構分離
但是由於32位程序和64位程序所使用的數據寬度不同, 同時64位機必須兼容的執行32位程序, 因此我們所有的數據都被定義為32bit和64bit兩個不同類型的數據
常規定義在include/uapi/linux中, 各個結構也可以按照需求重新定義
32位機器上的定義
64位機器上的定義*
elf頭部用Elfxx_Ehdr結構(被定義在linux/uapi/linux/elf.h來表示, Elf32_Ehdr(32bit)和Elf64_Ehdr(64bit)
內部成員, 如下
魔數
很多類型的文件,其起始的幾個字節的內容是固定的(或是有意填充,或是本就如此)。根據這幾個字節的內容就可以確定文件類型,因此這幾個字節的內容被稱為魔數 (magic number)。此外在一些程序代碼中,程序員常常將在代碼中出現但沒有解釋的數字常量或字符串稱為魔數 (magic number)或魔字符串。
ELF魔數 我們可以從前面readelf的輸出看到,最前面的”Magic”的16個字節剛好對應“Elf32_Ehdr”的e_ident這個成員。這16個字節被ELF標准規定用來標識ELF文件的平台屬性,比如這個ELF字長(32位/64位)、字節序、ELF文件版本
最開始的4個字節是所有ELF文件都必須相同的標識碼,分別為0x7F、0x45、0x4c、0x46
第一個字節對應ASCII字符裡面的DEL控制符,
後面3個字節剛好是ELF這3個字母的ASCII碼。這4個字節又被稱為ELF文件的魔數,幾乎所有的可執行文件格式的最開始的幾個字節都是魔數。
比如a.out格式最開始兩個字節為 0x01、0x07;
PE/COFF文件最開始兩個個字節為0x4d、0x5a,即ASCII字符MZ。
這種魔數用來確認文件的類型,操作系統在加載可執行文件的時候會確認魔數是否正確,如果不正確會拒絕加載。
接下來的一個字節是用來標識ELF的文件類的,0x01表示是32位的,0x02表示是64位的;第6個字是字節序,規定該ELF文件是大端的還是小端的(見附錄:字節序)。第7個字節規定ELF文件的主版本號,一般是1,因為ELF標准自1.2版以後就再也沒有更新了。後面的9個字節ELF標准沒有定義,一般填0,有些平台會使用這9個字節作為擴展標志。
各種魔數的由來
a.out格式的魔數為0x01、0x07,為什麼會規定這個魔數呢?
UNIX早年是在PDP小型機上誕生的,當時的系統在加載一個可執行文件後直接從文件的第一個字節開始執行,人們一般在文件的最開始放置一條跳轉(jump)指令,這條指令負責跳過接下來的7個機器字的文件頭到可執行文件的真正入口。而0x01 0x07這兩個字節剛好是當時PDP-11的機器的跳轉7個機器字的指令。為了跟以前的系統保持兼容性,這條跳轉指令被當作魔數一直被保留到了幾十年後的今天。
計算機系統中有很多怪異的設計背後有著很有趣的歷史和傳統,了解它們的由來可以讓我們了解到很多很有意思的事情。這讓我想起了經濟學裡面所謂的“路徑依賴”,其中一個很有意思的叫“馬屁股決定航天飛機”的故事在網上流傳很廣泛,有興趣的話你可以在google以“馬屁股”和“航天飛機”作為關鍵字搜索一下。
其中需要注意地是e_ident是一個16字節的數組,這個數組按位置從左到右都是有特定含義,每個數組元素的下標在標准中還存在別稱,如byte0的下標0別名為EI_MAG0,具體如下:
e_ident[EI_MAG0]~e_ident[EI_MAG3]即e_ident[0]~e_ident[3]被稱為魔數(Magic Number),其值一般為0x7f,’E’,’L’,’F’
e_ident[EI_CLASS](即e_ident[4])識別目標文件運行在目標機器的類別,取值可為三種值:
e_ident[EI_DATA](即e_ident[5]):給出處理器特定數據的數據編碼方式。即大端還是小端方式。取值可為3種:
e_type表示elf文件的類型
文件類型 e_type成員表示ELF文件類型,即前面提到過的3種ELF文件類型,每個文件類型對應一個常量。系統通過這個常量來判斷ELF的真正文件類型,而不是通過文件的擴展名。相關常量以“ET_”開頭
如下定義:
e_machine表示目標體系結構類型
這個用來區分ELF標准的各個修訂版本, 但是前面提到ELF最新版本就是1(1.2), 仍然是最新版, 因此目前不需要這個特性
另外ELF頭還包括了ELF文件的各個其他部分的長度和索引位置信息。因為這些部分的長度可能依程序而不同。所以在文件頭部必須提供相應的數據.
readelf -h add.o
文件類型是, 說明是可重定位文件, 其代碼可以移動至任何位置.
該文件沒有程序頭表, 對需要進行鏈接的對象而言, 程序頭表是不必要的, 為此所有長度都設置為0
readelf -h testelf_dynamic
readelf -h libtestelf.so
以程序運行的角度看ELF文件, 就需要程序頭表,即要運行這個elf文件,需要將哪些東西載入到內存鏡像。而節區頭部表是以elf資源的角度來看待elf文件的,即這個elf文件到底存在哪些資源,以及這些資源之間的關聯關系,
程序頭部是一個表,它的起始地址在elf頭部結構中的e_phoff成員指定,數量由e_phnum表示,每個程序頭部表項的大小由e_phentsize指出。
可執行文件或者共享目標文件的程序頭部是一個結構數組,每個結構描述了一個段或者系統准備程序執行所必需的其它信息。目標文件的”段”包含一個或者多個”節區”,也就是”
段內容(Segment Contents)”。程序頭部僅對於可執行文件和共享目標文件有意義。
下面來看程序頭號部表項的數據結構
在程序頭表之後, 列出了6個段, 這些組成了最終在內存中執行的程序.
其還提供了各段在虛擬地址空間和物理空間的大小, 位置, 標志, 訪問權限和對齊方面的信息. 還指定了yui個類型來更精確的描述段.
各段的語義如下
readelf -l add.o
可重定向文件, 是一個需要鏈接的對象, 程序頭表對其而言不是必要的, 因此這類文件一般沒有程序頭表
readelf -l testelf_dynamic
readelf -l libtestelf.so
虛擬地址空間中的各個段, 填充了來自ELF文件中特定的段的數據. 因而readelf輸出的第二部分指定了那些節載入到哪些段(節段映射).
物理地址信息講被忽略, 因為該信息是由內盒根據物理頁幀到虛擬地址空間中相應位置的映射情況動態分配的.只有在沒有MMU(因而沒有虛擬內存)的系統上該信息才是由意義的
節區中包含目標文件中的所有信息
除了:ELF 頭部、程序頭部表格、節區頭部表格。
節區滿足以下條件:
目標文件中的每個節區都有對應的節區頭部描述它,反過來,有節區頭部不意味著有節區。
每個節區占用文件中一個連續字節區域(這個區域可能長度為 0)。
文件中的節區不能重疊,不允許一個字節存在於兩個節區中的情況發生。
目標文件中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容未指定。
ELF文件在描述各段的內容時, 是指定了哪些節的數據映射到段中. 因此需要一個結構來管理各個節的內容, 即節頭表
節區頭部表是以elf資源的角度來看待elf文件的,即這個elf文件到底存在哪些資源,以及這些資源之間的關聯關系,而前面提到的程序頭部表,則以程序運行來看elf文件的,即要運行這個elf文件,需要將哪些東西載入到內存鏡像。
ELF 頭部中,
e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數;
e_shnum給出表格中條目數目;
e_shentsize 給出每個項目的字節數。
從這些信息中可以確切地定位節區的具體位置、長度。
從之前的描述中可知,每一項節區在節區頭部表格中都存在著一項元素與它對應,因此可知,這個節區頭部表格為一連續的空間,每一項元素為一結構體
那麼這個節區頭部由elfxx_shdr(定義在include/uapi/linux/elf.h), 32位elf32_shdr, 64位elf64_shdr
結構體的成員如下
sh_type的取值如下:
sh_flag標志著此節區是否可以修改,是否可以執行,如下定義:
sh_link和sh_info字段的具體含義依賴於sh_type的值
有些節區是系統預訂的,一般以點開頭號,因此,我們有必要了解一些常用到的系統節區。
readelf -S add.o
可重定向文件, 是一個需要鏈接的對象, 程序頭表對其而言不是必要的, 因此這類文件一般沒有程序頭表
readelf -S testelf_dynamic
readelf -S libtestelf.so
首先要知道,字符串表它本身就是一個節區,從第二章描述中可知,每一個節區都存在一個節區頭部表項與之對應,所以字符串表這個節區也存在一個節區頭部表項對應,而在elf文件頭部結構中存在一個成員e_shstrndx給出這個節區頭部表項的索引位置。因此可以通過
shstrab = (rt_uint8_t *)module_ptr +shdr[elf_module->e_shstrndx].sh_offset;
來得到字符串表的起始位置。
字符串表節區包含以 NULL(ASCII 碼 0)結尾的字符序列,通常稱為字符串。ELF目標文件通常使用字符串來表示符號和節區名稱。對字符串的引用通常以字符串在字符
串表中的下標給出。
一般,第一個字節(索引為 0)定義為一個空字符串。類似的,字符串表的最後一個字節也定義為 NULL,以確保所有的字符串都以 NULL 結尾。索引為 0 的字符串在
不同的上下文中可以表示無名或者名字為 NULL 的字符串。
允許存在空的字符串表節區,其節區頭部的 sh_size 成員應該為 0。對空的字符串表而言,非 0 的索引值是非法的。
例如:對於各個節區而言,節區頭部的 sh_name 成員包含其對應的節區頭部字符串表節區的索引,此節區由 ELF 頭的 e_shstrndx 成員給出。下圖給出了包含 25 個字節的一個字符串表,以及與不同索引相關的字符串。
那麼上面字符串表包含以下字符串:
首先,符號表同樣本身是一節區,也存在一對應節區頭部表項。
目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。
符號表索引是對此數組的索引。索引0表示表中的第一表項,同時也作為未定義符號的索引。
符號表是由一個個符號元素組成,用elfxx_sym來結構來表示, 定義在include/uapi/linux/elf.h, 同樣32位為elf32_sym, 64位對應elf64_sym
每個元素的數據結構如下定義:
st_info 中包含符號類型和綁定信息,操縱方式如:
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
st_info 的高四位(ELF32_ST_BIND(i))
表示符號綁定,用於確定鏈接可見性和行為。具體的綁定類型如:
全局符號與弱符號之間的區別主要有兩點:
當鏈接編輯器組合若干可重定 位 的 目 標 文 件 時 , 不 允 許 對 同 名 的STB_GLOBAL 符號給出多個定義。另一方面如果一個已定義的全局符號已經存在,出現一個同名的弱符號並不會產生錯誤。鏈接編輯器盡關心全局符號,忽略弱符號。類似地,如果一個公共符號(符號的 st_shndx 中包含 SHN_COMMON),那麼具有相同名稱的弱符號出現也不會導致錯誤。鏈接編輯器會采納公共定義,而忽略弱定義。
當鏈接編輯器搜索歸檔庫(archive libraries)時,會提取那些包含未定義全局符號的檔案成員。成員的定義可以是全局符號,也可以是弱符號。連接編輯器不會提取檔案成員來滿足未定義的弱符號。未能解析的弱符號取值為 0。
在每個符號表中,所有具有 STB_LOCAL 綁定的符號都優先於弱符號和全局符號。符號表節區中的 sh_info 頭部成員包含第一個非局部符號的符號表索引。
st_info的低四位ELF32_ST_TYPE(i)
定義如下
在共享目標文件中的函數符號(類型為 STT_FUNC)具有特別的重要性。當其他目標文件引用了來自某個共享目標中的函數時,鏈接編輯器自動為所引用的符號創建過
程鏈接表項。類型不是 STT_FUNC 的共享目標符號不會自動通過過程鏈接表進行引用。
如果一個符號的取值引用了某個節區中的特定位置,那麼它的節區索引成員(st_shndx)包含了其在節區頭部表中的索引。當節區在重定位過程中被移動時,符號的取值也會隨之變化,對符號的引用始終會“指向”程序中的相同位置。
如前面所述,st_shndx給出相關的節區頭部表索引。但其值也存在一些特殊值,具有某些特殊的含義:
不同的目標文件類型中符號表項對 st_value 成員具有不同的解釋:
在可重定位文件中,st_value 中遵從了節區索引為 SHN_COMMON 的符號的對齊約束。
在可重定位的文件中,st_value 中包含已定義符號的節區偏移。就是說,st_value 是從 st_shndx 所標識的節區頭部開始計算,到符號位置的偏移。
在可執行和共享目標文件中,st_value 包含一個虛地址。為了使得這些文件的符號對動態鏈接器更有用,節區偏移(針對文 件的解釋)讓位於虛擬地址(針對內存的解釋),因為這時與節區號無關。
盡管符號表取值在不同的目標文件中具有相似的含義,適當的程序可以采取高效的數據訪問方式。
nm *.o
可重定向文件, 是一個需要鏈接的對象, 程序頭表對其而言不是必要的, 因此這類文件一般沒有程序頭表
nm testelf_dynamic
nm libtestelf.so
重定位是將符號引用與符號定義進行連接的過程。例如,當程序調用了一個函數時,相關的調用指令必須把控制傳輸到適當的目標執行地址。
可重定位文件必須包含如何修改其節區內容的信息,從而允許可執行文件和共享目標文件保存進程的程序映像的正確信息。重定位表項就是這樣一些數據。
可重定位表項的數據結構如下定義:
Elf32_Rel
Elf32_Rela
重定位節區會引用兩個其它節區:符號表、要修改的節區。節區頭部的 sh_info 和sh_link 成員給出這些關系。不同目標文件的重定位表項對 r_offset 成員具有略微不同的解釋。
r_info通常分為高8位和低8位,分別表示不同的含義:
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))
高8位用作要進行重定位的符號表索引,通過它可以得出一個符號表項,而低8位表示將實施的重定位類型,它是和處理器相關的。
重定位表項描述如何修改後面的指令和數據字段。一般,共享目標文件在創建時,其基本虛擬地址是 0,不過執行地址將隨著動態加載而發生變化。
參考
六星經典CSAPP-筆記(7)加載與鏈接(上)ELF文件結構
ELF文件格式
elf文件格式分析
ELF (文件格式)
淺談Linux的可執行文件格式ELF(轉帖)
ELF文件的加載和動態鏈接過程
可執行文件(ELF)格式的理解
elf文件格式
elf文件格式總結
elf文件格式與動態鏈接庫(非常之好)—–不可不看
Executable and Linkable Format (ELF) (這專門介紹ELF文件格式的ABI的好文章,網絡版在 www.skyfree.org/linux/references/ELF_Format.pdf可以得到)