ELF 可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

ELF 可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 
0 自我小結
 
==================虛擬存儲器管理================================
linux就像是一個大的加工生產企業, 它接了很多客戶的很多任務, 各任務的原材料等都放在倉庫中, linux的內存就像是他的加工生產區,這個區被分爲很多的塊,一塊就是一個加工生產車間,因任務衆多,加工生產車間有限,需要合理利用生產車間; linux根據倉庫中生產計劃指令,在這個時刻去加工某個客戶任務的某部分資料,如果這部分資料已在某個生產車間內,則繼續加工,如果不在,則找一個生產車間,將待加工材料裝進來,加工,該車間加工完這個任務當前的計劃後,很可能被放入倉庫中,生產車間被用來加工生產另一任務的計劃,類似這樣...
 
可執行文件的各個段 被映射到虛擬地址空間中, 同時虛擬地址空間中還包含有其他的各個代碼部分(如庫 堆 棧等)【因爲虛擬空間32位,有4G,而實際物理存儲器,內存很小,所以很有可能可執行文件的某個虛擬空間(頁)中的內容是在CPU真正執行到了那裏後,才被調到內存對應的某個頁中,再被CPU執行的,所以很可能並不是可執行文件的所有段都一開始就全部加載到物理內存中】, cpu要訪問某個虛擬地址單元,先檢查該單元是否已經映射到了物理內存中,如果已在,就直接處理,否則就要先把這個地址對應的可執行文件的虛擬頁且存放在磁盤上內容先加載到某個物理頁中,再執行...
 
虛擬存儲器(VM)被組織爲一個由存放在磁盤上的N個連續的字節大小的單元組成的數組。(概念)
VM系統將虛擬存儲器分割爲 虛擬頁(VP)
物理存儲器被分割爲 物理頁(PP); 虛擬頁與物理頁的大小一致;
虛擬頁中的數據或代碼要被執行時,需要將該虛擬頁的內容緩存(映射)到某物理頁中;
 
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 

存儲器映射

虛擬存儲器區域 與 一個磁盤上的對象關聯起來,用這個關聯對象來初始化這個虛擬存儲器區域的內容,這個過程叫做存儲器映射【存儲器強調的是對虛擬存儲器區域的初始化】;

虛擬存儲器區域與普通的文件映射,比如與可執行文件的映射,這時可執行文件中的程序頭已經包含了可執行文件與虛擬空間地址的映射關係,這個程序頭中指明瞭可執行文件的各個段映射到虛擬存儲器空間的虛擬地址 以及 讀寫權限等信息。這就是ELF中的映射信息。虛擬存儲器只不過根據塊的大小將這些規劃好的段(區域)再映射一下而已。

虛擬存儲器區域還可以映射到一個全0的匿名文件(使用該虛擬區域時,用這個全0的匿名文件初始化);

其實不論如何映射(映射只是在使用到該分配且未緩存的虛擬區域時,用對應的映射區域進行初始化對應的物理頁而已,一旦一個虛擬頁被初始化了,他就在一個由內核維護的專門的交換文件(swap file)之間進行換來換去【這裏強調的是虛擬頁對應的物理頁初始化後,換進換出的過程】。這個交換文件就是交換空間(交換區域)。因此交換空間限制了當前運行着的(所有)進程能夠分配的虛擬頁面的總是。

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

共享對象

由於很多的進程包含有公共的只讀文件區域等(如C庫中printf等),如果每個進程都在屋裏存儲器重包含這樣的常用代碼,那將是一種浪費。

存儲器映射提供了一種機制,用來控制多個進程如何共享對象;

我們可以將一個對象映射到虛擬存取器區域中去,且這個對象要麼是共享對象要麼是私有對象;

共享對象:多個進程將這個共享對象映射到各自的虛擬空間,且各進程共享同一個共享對象的物理空間,某個進程進程對該共享對象的寫操作,其他的進程是可見的,且寫操作會反映在磁盤原始文件中;每個對象有唯一的文件名,內核可迅速判定進程1已映射該對象,且進程2的頁表條目指向相應的物理頁面,關鍵是即使對象被映射到多個進程的共享區域,物理存儲器中也只存放共享對象的一個拷貝
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

私有對象:私有對象使用寫時拷貝的方法,物理存儲器只保持私有對象的一份拷貝,這個各個進程共享這個對象的一個物理拷貝

Fork函數:

一個現有進程可以調用fork函數創建一個新進程。由fork創建的新進程被稱爲子進程(child process)。fork函數被調用一次但返回兩次。兩次返回的唯一區別是子進程中返回0值而父進程中返回子進程ID。子進程是父進程的副本,它將獲得父進程數據空間、堆、棧等資源的副本。注意,子進程持有的是上述存儲空間的“副本”,這意味着父子進程間不共享這些存儲空間。

當fork函數被當前進程調用時,內核爲新進程創建各種數據結構,並分配給它一個唯一的PID。爲了給這個新進程創建虛擬存儲器,它創建了當前進程的mm_struct、區域結構和頁表的原樣拷貝。它將兩個進程中的每個頁面都爲標記只讀,並將兩個進程中的每個區域結構都標記爲私有的寫時拷貝。

當fork在新進程中返回時,新進程現在的虛擬存儲器剛好和調用fork時存在的虛擬存儲器相同。當這兩個進程中的任一個後來進行寫操作時,寫時拷貝機制就會創建新頁面,因此,也就爲每個進程保持了私有地址空間的抽象概念。

execve函數:

execve("a.out",NULL,NULL) ;

execve函數在當前進程中加載並運行包含在可執行目標文件a.out中的程序,用a.out程序有效地替代了當前程序。加載並運行a.out需要以下幾個步驟:

刪除已存在的用戶區域。刪除當前進程虛擬地址用戶部分中的已存在的區域結構。

映射私有區域。爲新程序的文本、數據、bss和棧區域創建新的區域結構。所有這些新的區域都是私有的、寫時拷貝的。文本和數據區域被映射爲a.out文件中的文本和數據區。bss區域是請求二進制零的,映射到匿名文件,其大小包含在a.out中。棧和堆區域也是請求二進制零的。

映射共享區域。如果a.out程序與共享對象(或目標)鏈接,比如標準C庫libc.so,那麼這些對象都是動態鏈接到這個程序的,然後再映射到用戶虛擬地址空間中的共享區域內。

設置程序計數器(PC)。execve做的最後一件事情就是設置當前進程上下文中的程序計數器,使之指向文本區域的入口點。

下一次調度這個進程時,它將從這個入口點開始執行。Linux將根據需要換入代碼和數據頁面。
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 

使用mmap函數的用戶級存儲器映射:

 

[cpp] view plaincopy

#include 

#include 

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset) ; 

//返回:若成功時則爲指向映射區域的指針,若出錯則爲MAP_FAILED(-1) 

 

mmap函數要求內核創建一個新的虛擬存儲器區域是,最好是從地址start開始的一個區域,並將文件描述符fd指定的對象的一個連續的片(chunk)映射到這個新區域。連續的對象片大小爲length字節,從距文件開始處偏移量爲offset字節的地方開始。start地址僅僅是一個暗示,通常被定義爲NULL。

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

munmap函數刪除虛擬存儲器的區域:

#include 

#include 

int  munmap( void *start,   size_t length);

Munmap函數刪除從虛擬地址start開始的,由接下來的length字節組成的區域。接下來對已刪除區域的引用會導致段錯誤。

 

動態存儲器分配

雖可通過低級的mmap、munmap函數來創建與刪除虛擬存取器的區域,但是用動態存儲器分配器更方便,更易於移植;

 

動態存儲器分配器維護着一個進程的虛擬存儲器區域—堆heap; 堆緊接着未初始化的bss區域後面,並向上生長,對每個進程,內核維護着一個變量brk,他指向堆的頂部;


ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載

分配器的分類:

顯式分配器:要求應用顯式地釋放任何已分配的塊。C程序通過調用malloc函數來分配一個塊,並通過調用free函數來釋放一個塊。

隱式分配器:要求適配器檢測一個已分配塊何時不再被程序所使用,那麼就釋放這個塊。隱式分配器也叫垃圾收集器(java),而自動釋放未使用的已分配的塊的過程叫做垃收集。

C語音用malloc free等來從堆中分配虛擬空間;

ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 

 

 

 
linux的虛擬存儲器系統:linux是如何組成虛擬存儲器的?
  linux將虛擬存儲器組織成一些區域(段)的集合,一個區域就是已經存在的(已分配的)虛擬存儲器的連續片(chunk),這些頁是以某種方式相關聯的。例如代碼中的 代碼段  數據段 堆 共享庫段 用戶棧 都是不同的區域。每個存在的虛擬頁面都保存在某個區域中,而不屬於某個區域的虛擬頁是不存在的,並且不能被進程引用。因爲虛擬地址空間有間隙,即內核不用記錄那些不存在的虛擬頁(未分配的),而這樣的頁也不佔用存儲器、磁盤或內核本身的任何額外資源。
內核爲系統中的每個進程維護一個單獨的任務結構(task_struct),任務結構中的元素包含或者指向內核運行該進程所需要的所有信息(如 PID、指向用戶棧的指針、可執行目標文件的名字以及程序計數器)
task_struct 中的一個條目指向mm_struct,他描述了虛擬存儲器的當前狀態,mm_struct中包含有pgd 和 mmap,pgd指向第一級頁表(頁全局目錄)的基址,mmap指向一個vm_area_structs的鏈接,該鏈表中的每一個vm_area_structs都描述了當前虛擬地址空間的一個區域(也就是一個段),當內核運行這個進程時,他就將pgd存放在CR3控制寄存器中;
vm_start:指向這個區域(段)的起始處 
vm_end:指向這個區域(段)的結束處 
vm_prot:指向這個區域(段)內包含的所有頁的讀寫許可權限
vm_flags:描述該區域的頁面是與其他進程共享的還是這個進程私有的
vm_next:指向下一個
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 
存儲在磁盤上的虛擬頁(VP)   DRAM中的物理頁
                    /media/note/2012/03/29/virtual-memory/fig6.png

Linux進程的虛擬存儲器

 

虛擬頁(VP)可以分爲 未分配的虛擬頁  緩存的虛擬頁 未緩存的虛擬頁
未分配的虛擬頁:VM系統還沒有分配(創建)(或使用)的虛擬頁,未分配的虛擬頁的塊沒有任何的數據和他們               相關聯。
緩存的虛擬頁:當前緩存在物理存儲器中的已分配頁
未緩存的虛擬頁:還沒有緩存到物理存儲器中的已分配頁
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
虛擬存儲器系統必須有某種方法來判定一個虛擬頁是否存放在DRAM中的某個地方,如果是,還必須知道這個虛擬頁存放在哪個物理頁中,如果否(沒有命中),系統必須知道這個虛擬頁存放在磁盤的哪個位置,在物理存儲器中選擇一個犧牲頁,並將虛擬頁從磁盤拷貝到DRAM中,替換這個犧牲頁;
 
頁表:存放在物理存儲器中頁表(page table)的數據結構,頁表將虛擬頁映射到物理頁;每次地址翻譯硬件將一個虛擬地址轉換爲物理地址時都會讀取頁表,頁表由操作系統來維護;如下圖中的左圖就是一個頁表,頁表就是一個頁表條目(PTE page table entry)的數組,虛擬地址空間中的每個頁在頁表中一個固定偏移量處都對應有一個PTE。下圖中左部分通過有效位 1簡化表示 表右部的物理頁號或磁盤地址有效,即該虛擬頁已緩存映射到了這個地址對應的物理頁中, 0&null 表示 未分配的虛擬頁
頁命中:如果代碼中 去讀 VP1 VP2 VP7 VP4的字時, 由於這些虛擬頁都已經緩存到DRAM中,故直接可以命中,虛擬地址直接轉換爲物理地址,進行操作;
缺頁:如果代碼中 去讀 VP3的字時 , 由於VP3 沒有緩存到DRAM中,所以會產生一個缺頁異常,這時內核選擇一個犧牲頁如PP3,再從磁盤中拷貝VP3到存儲PP3,更新PTE3。
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 
儘管在整個運行過程中,程序引用的不同頁面的總數可能超出物理存儲器的總的大小,但是局部性原則保證了再任意時刻,程序將往往在一個較小的活動頁面(active page)集合上工作,這個集合叫做工作集(working set) 或者駐集(resident set),在初始開銷,也就是將工作集頁面調度到存儲器中之後,接下來對這個工作集的引用將導致命中,而不會產生額外的磁盤流量。如果程序沒有良好的局部性,頁面不停的換進換出,程序將出現一種不幸的現象,叫做顛簸(thrashing),程序運行很卡的時候就一般這樣了,可以用getrusage函數來監測缺頁的數量
 
Linux缺頁異常處理
MMU在翻譯某個虛擬地址A時,觸發一個缺頁,這個異常導致控制轉移到內核的缺頁處理程序,處理程序隨後就執行下面的步驟:
1:虛擬地址A是合法的嗎?A在某個區域(段)結構定義的區域內嗎?缺頁處理程序搜索區域結構的鏈表,把A和每個區域結構中的vm_start vm_end比較,如果非法,那麼缺頁處理程序就觸發一個段錯誤,從而終止進程;其實進程可以通過mmap函數創建任意數量的新虛擬存儲器區域,所以搜索區域結構鏈表花銷很大,一般linux在鏈表中構建了一棵樹,在這棵樹上搜索。
2:試圖進行存儲器的訪問是否合法? 進程是否有讀寫執行這個區域內頁面的權限,訪問不合法,缺頁處理觸發一個保護異常,進程終止
3:內核知道了這個缺頁是由合法地址引起的,內核會先選擇一個犧牲頁,如果犧牲頁被修改過,那麼先將他交換出去,換入新的頁面並更新頁表,當缺頁處理程序返回時,CPU重新啓動引起缺頁的指令,這條指令將再次發生A到MMU,這次MMU就能正常翻譯A地址了
Linux存儲器映射(就是將虛擬存儲空間 與 磁盤上的對象【這個對象可是普通文件(虛擬存儲空間直接與可執行文件進行映射) 或 匿名文件】 進行映射)
通過將虛擬存存儲器區域(段) 與 一個磁盤上的對象關聯起來,以初始化這個虛擬存儲器區域(段)的內容,叫存儲器的映射。虛擬存儲器區域可以映射到兩種類型的對象中的一種:
(1)Unix文件上的普通文件:
一個區域可以映射到一個普通磁盤文件的連續部分,例如一個可執行目標文件。文件區(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始化內容。因爲按需進行頁面調度,所以這些虛擬頁面沒有實際交換進入物理存儲器,直到CPU第一次引用到頁面(即發射一個虛擬地址,落在地址空間這個頁面的範圍之內)。如果區域比文件區要大,那麼就用零來填充這個區域的餘下部分。
(2)匿名文件:
一個區域也可以映射到一個匿名文件,匿名文件是由內核創建的,包含的全是二進制零。CPU第一次引用這樣一個區域內的虛擬頁面時,內核就在物理存儲器中找到一個合適的犧牲頁面,如果該頁面被修改過,就將這個頁面換出來,用二進制零覆蓋犧牲頁面並更新頁表,將這個頁面標記爲是駐留在存儲器中的。注意在磁盤和存儲器之間沒有實際的數據傳送。因爲這個原因,映射到匿名文件的區域中的頁面有時也叫做請求二進制零的頁(demand-zero page)。
無論在哪種情況下,一旦一個虛擬頁面被初始化了, 它就在一個由內核維護的專門的交換文件(swap file)之間換來換去。交換文件也叫做交換空間(swap space)或者交換區域(swap area)。需要意識到的很重要的一點,在任何時刻,交換空間都限制着當前運行着的進程能夠分配的虛擬頁面的總數。
===================================================
 
【參加 深入理解計算機系統 第7章】
gcc -v查看編譯的過程
文件編譯過程: 語言預處理器-->xxx.i-->編譯器-->xxx.s-->彙編器-->xxx.o-->鏈接器-->可執行文件
 
目標文件分爲三類:可重定位目標文件、共享目標文件、可執行目標文件
編譯器、彙編器 可生產可重定位的目標文件(包括共享目標文件);鏈接器生成可執行目標文件
 
什麼是符號解析?在代碼中,變量 函數等符號 有 定義 和引用之分,因此符號解析的目的就是將每個符號引用與一個符號定義聯繫起來; 鏈接器來做符號解析,鏈接器將每個符號的引用 與 此時輸入的可重定位目標文件中的符號表中的一個符號定義 聯繫起來。
 
什麼是重定位?編譯器和彙編器  生成從0地址開始的代碼和數據節,鏈接器通過吧每個符號定義與一個存儲器位置聯繫起來(簡單理解爲符號定義的位置),然後修改所有對這些符號的引用,使得他們指向這個存儲器位置,從而重定位這些節。鏈接器在完成符號解析後(即把一個符號的引用與一個符號的定義聯繫起來)。接下來,鏈接器準備對各目標文件中的確定長度的相關節進行重定位了,合併輸入模塊,併爲每個符號分配運行時的地址
 
重定位分爲兩步:
1:重定位節和符號定義: 鏈接器將多個目標文件中所有相同類型的節合併爲同一類型的新的聚合節。如,各輸入模塊的.data節在最終可執行目標文件中被合併爲一個.data節 ,然後,鏈接器將運行時存儲器地址賦給新的聚合節(裏面輸入各模塊的各個節 以及 各個符號),這步完成後,程序中的每個指令和全局變量都有唯一的運行時存儲地址了。
2:重定位節中的符號引用
在這一步,鏈接器修改代碼節和數據節中對每個符號的引用,使得他們指向正確的運行時地址。
 
==============================================================
可以將各個.o目標文件打包成一個庫來使用
 
ar:創建靜態庫(各個xx.o),插入 刪除 列出 和 提前成員
strings:列出一個目標文件中所有可打印的字符串
strip:從目標文件中刪除符號表信息
nm:列出一個目標文件的符號表中定義的符號
size:列出目標文件中節的名字和大小
readelf:顯示一個目標文件的完整結構,包括ELF頭中編碼的所有信息(包含size和nm的功能)
objdump:所有二進制工具之母,能夠顯示一個目標文件中的所有的信息,他最大的作用是反彙編.text節中的二進制指令
ldd:列出一個可執行文件在運行時所需要的共享庫
===================可重定位目標文件============================
可重定位目標文件 與 可執行目標文件 的組織結構大致是相同的;有些小差別;
ELF頭:
.text:
.rodata:
.bss:
.symbol: 符號表【符號表由彙編器構造的】,存放的是程序中定義和引用的函數 和全局變量 或 靜態變量(靜態變量在有些書中統稱爲全局變量)的信息;因爲局部變量是存放在堆棧中,所以符號表中是不包含局部變量條目的符號
.rel.text:.text中需要重定位的位置,比如引用另一個目標文件的函數等;可執行文件中 並不需要重定位信息,因此通常省略,除非用戶顯示地指示鏈接器包含這些信息;
.rel.data:
.debug:一個調試符號表,其條目是程序中定義局部變量 和類型定義(-g選項)
.line:原始C源程序中的行號與.text節中機器指令之間的對應關係
.strtab:一個字符串表,其內容包括.symbol和.debug節中的符號表 以及節頭部中的節名字,字符串表 就是以null結尾的字符串序列         
 
===================可執行目標文件加載============================
先看下面的可執行目標文件的分析,再看這裏的加載吧,排版問題
 
     Program Headers\\這個就是ELF Header中描述的程序頭描述表的起始位置,他告訴系統如何創建進程映                \\像用來構造進程映像的目標文件必須具有程序頭部表
                  \\他描述了進程映像的目標文件的“段”包含對應的一個或者多個“節區”,也就                      \\是“段內容(Segment Contents)”
                 \\該程序頭描述表(數組)來描述了內存中的可執行的進程映射是如何構成的信息
                \\Type  Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
                \\如 LOAD 0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
               \\上面描述的是一個加載段,加載目標文件中的對應的內容到內存中對應的位置
 
proc/進程PID/maps  查看某進程的虛擬地址空間是如何分配利用的。
 
cat /proc/1/statm
487 185 133 31 0 67 0
很簡單地返回7組數字,每一個的單位都是一頁 (常見的是4KB)
分別是
size:任務虛擬地址空間大小
Resident:正在使用的物理內存大小
Shared:共享頁數
Trs:程序所擁有的可執行虛擬內存大小
Lrs:被映像倒任務的虛擬內存空間的庫的大小
Drs:程序數據段和用戶態的棧的大小
dt:髒頁數量
 
===================可執行目標文件============================
ELF可執行文件(a.out)或類似編譯器編譯出的文件(.bin), 都是由各個節(section:如.text .bss .data等)組成;運行時,各個節被加載到內存中,此時的內存中以段(Segment:如代碼段 數據段)的形式組織相關的節,以便程序運行;
 
ELF可執行文件(a.out): 在linux下,可使用readelf  nm objdump 以及gdb 來查看與調試可運行的文件;
ELF可執行文件的組成形式如下:
 
 readelf -a a.out: 讀取顯示執行文件中的所有的頭表信息
 objdump -s a.out: 顯示可節的具體內容
 nm a.out:列出目標文件中的所有符號信息
 gdb :調試
 
一般C語言的編譯後執行語句都編譯成機器代碼,保存在.text段;已初始化的全局變量和局部靜態變量都保存在. data段;未初始化的全局變量和局部靜態變量一般放在一個叫."bss"的段裏。我們知道未初始化的全局變量和局部靜態變量默認值都爲0,本來它們也可以被放在.data段的,但是因爲它們都是0,所以爲它們在.data段分配空間並且存放數據0是沒有必要的。程序運行的時候它們的確是要佔內存空間的,並且可執行文件必須記錄所有未初始化的全局變量和局部靜態變量的大小總和,記爲.bss段。所以.bss段只是爲未初始化的全局變量和局部靜態變量預留位置而已,它並沒有內容,所以它在文件中也不佔據空間。
【函數中的局部變量將放在棧中,既不在.data 也不在.bss中】
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
一個進程的內存映像,從低地址開始分爲五部分
正文段
初始化數據段
未初始化數據段
堆區
棧區
【棧由該區域的最高地址向低地址增長,而堆由該區域的低地址向高地址增長】
【當一程序啓動運行的初期,並沒有把該程序所需要的所有的物理空間分配給它,而是隻分配了滿足當時可以使之運行的幾個頁面。程序繼續運行(虛擬地址)需讀取新的頁面時,發現該頁面不在內存中,就要用一定的算法爲該進程對應的虛擬地址空間分配一內存頁面】
 

簡單來講,程序的裝入到運行的主要包含以下幾個步驟:

1:讀入可執行文件的頭部信息以確定其文件格式及地址空間的大小;

2:以段的形式劃分地址空間;

3:將可執行程序讀入地址空間中的各個段,建立虛實地址間的映射關係;

4:將bbs段清零;

5:創建堆棧段;

6:建立程序參數、環境變量等程序運行過程中所需的信息;

7:啓動運行。

 
//程序頭表中 描述可執行文件 到 虛擬內存空間的映射關係
Program Headers:
  Type          Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR          0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP        0x000154 0x08048154 0x08048154 0x00013 0x00013 R  0x1
     [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD          0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
  LOAD          0x000f08 0x08049f08 0x08049f08 0x00240 0x00324 RW  0x1000
  DYNAMIC       0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE          0x000168 0x08048168 0x08048168 0x00044 0x00044 R  0x4
  GNU_EH_FRAME   0x000690 0x08048690 0x08048690 0x0002c 0x0002c R  0x4
  GNU_STACK     0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO     0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R   0x1
//程序頭表中 描述段 到 節 的映射關係
 Section to Segment mapping:
  Segment Sections...
   00    
   01    .interp 
   02    .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03    .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04    .dynamic 
   05    .note.ABI-tag .note.gnu.build-id 
   06    .eh_frame_hdr 
   07    
   08    .init_array .fini_array .jcr .dynamic .got 
 
    ELF Header\\用來描述整個文件的組織,文件類型,機器類型,程序入口地址,
              \\Start of program headers程序頭(描述表)起始位置,這是一個數組起始地址
              \\Number of program headers告訴我們這個數組包含了多少個程序段描述子成員
              \\Start of section headers節區頭(描述表)起始位置,這是一個數組起始地址
              \\Number of section headers告訴我們這個數組包含了多少個節區描述子成員           
     Program Headers\\這個就是ELF Header中描述的程序頭描述表的起始位置,他告訴系統如何創建進程映                \\像用來構造進程映像的目標文件必須具有程序頭部表
                  \\他描述了進程映像的目標文件的“段”包含對應的一個或者多個“節區”,也就                       \\是“段內容(Segment Contents)”
                  \\該程序頭描述表(數組)來描述了內存中的可執行的進程映射是如何構成的信息
               \\Type  Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
               \\如 LOAD 0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
              \\上面描述的是一個加載段,加載目標文件中的對應的內容到內存中對應的位置
     【接下來就是各個節的數據,各個節的偏移地址與長度 在Section Headers中做描述】 
  【各個節區有各自對應的讀寫屬性標誌,MMU通過這個標誌來控制各節區的可讀寫特性,.rodata等】
    【gcc 編譯時 加入-g 選項,目標文件中將包含調試信息】 
     .interp \\ 此節區包含程序解釋器的路徑名      
     .note.ABI-tag \\   
     .note.gnu.build-i \\ 
     .gnu.hash \\   
     .dynsym  \\  
     .dynstr \\    
     .gnu.version \\   
     .gnu.version_r \\   
     .rel.dyn \\     
     .rel.plt \\    
     .init  \\ 此節區包含了可執行指令,是進程初始化代碼的一部分。當程序開始執行時,系統要在開始調            \\用主程序入口之前(通常指 C 語言的 main 函數)執行這些代碼。
     .plt  \\    
     .text \\  包含程序的可執行指令   
     .fini \\ 此節區包含了可執行的指令,是進程終止代碼的一部分。程序正常退出時,系統將安排執行這           \\裏的代碼。  
     .rodata \\  包含只讀數據
     .eh_frame_hdr \\ 
     .eh_frame  \\   
     .init_array\\   
     .fini_array  \\ 
     .jcr  \\      
     .dynamic  \\ 
     .got    \\  
     .got.plt  \\  
     .data    \\ 初始化了的數據  
     .bss     \\ 未初始化了的數據
     .comment  \\ 
     .shstrtab  \\ 
     .symtab  \\此節區包含一個符號表
     .strtab \\ 
     Section Headers\\這個就是ELF Header中描述的節區頭描述表的起始位置,文件包含了多少個節,這個                  \\數組就有多少個成員,每個成員描述了對應一個節區的名字,偏移地址,虛擬地址,                   \\可讀寫信息等
 
readelf  -a  a.out :可查看ELF文件的各個頭信息(這些信息描述了整個可執行文件的組織信息)
     如:
 
1 簡介
    可執行鏈接格式(Executable and Linking Format)最初是由UNIX系統實驗室(UNIX System Laboratories,USL)開發併發布的,作爲應用程序二進制接口(Application Binary Interface,ABI)的一部分。工具接口標準(Tool Interface Standards,TIS)委員會將還在發展的ELF標準選作爲一種可移植的目標文件格式,可以在32位Intel體系結構上的很多操作系統中使用[1, 2]。
     ELF標準的目的是爲軟件開發人員提供一組二進制接口定義,這些接口可以延伸到多種操作環境,從而減少重新編碼、重新編譯程序的需要。接口的內容包括目標模塊格式、可執行文件格式以及調試記錄信息與格式等。
    TIS給出的Portable Formats Specification 1.1版本中主要針對三種不同類型的目標文件作了規定,並規定了程序加載與動態鏈接相關過程細節,給出了標準ANSI C和libc例程必須提供的符號[1]。在該組織隨後發佈的Executable and Linking Format(ELF) Specification 1.2版本中則分如下三個部分,主要的不同點是對與操作系統相關的部分進行了重新組織:
􀂄 Book I: Executable and Linking Format,描述ELF目標文件格式;
􀂄 Book II: Processor Specific(Intel Architecture),描述ELF中與硬件相關的信息;
􀂄 Book III: Operating System Specific,描述ELF中與操作系統相關的部分,例如System V Release 4信息等。
 
2 相關標準
2.0 System V

      Unix System V,是Unix操作系統衆多版本中的一支。它最初由AT&T開發,在1983年第一次發佈,因此也被稱爲AT&T System V。一共發行了4個System V的主要版本:版本1、2、3和4。System V Release 4,或者稱爲SVR4,是最成功的版本,成爲一些UNIX共同特性的源頭,例如SysV 初始化腳本(/etc/init.d),用來控制系統啓動和關閉System V Interface Definition (SVID)是一個System V如何工作的標準定義。

     AT&T出售運行System V的專有硬件,但許多(或許是大多數)客戶在其上運行一個轉售的版本,這個版本基於AT&T的實現說明。流行的SysV派生版本包括Dell SVR4和Bull SVR4。當今廣泛使用的System V版本是SCO OpenServer,基於System V Release 3,以及SUN Solaris和SCOUnixWare,都基於System V Release 4。

     System V是AT&T的第一個商業UNIX版本(UNIX System III)的加強。傳統上,System V被看作是兩種UNIX"風味"之一(另一個是BSD然而,隨着一些並不基於這兩者代碼的UNIX實現的出現,例如LinuxQNX, 這一歸納不再準確,但不論如何,像POSIX這樣的標準化努力一直在試圖減少各種實現之間的不同。

2.1 System V ABI
    System VApplication Binary Interface(ABI)爲已編譯的應用程序定義了系統接口,同時也爲安裝腳本支持提供了一個最小環境。其目的是爲應用程序提供一個標準的二進制接口,使得這些程序能夠運行在符合X/Open Commen Application Environment Specification, Issue 4.2和System V Interface Definition, Fourth Edition的操作系統上二進制要包含與不同處理器體系結構相關的信息,所以ABI並不是只有一個規範,而是一個規範體系。System V ABI由兩個部分組成:一個通用的部分,描述System V在所有硬件平臺上都一致的接口[3];一個處理器相關的部分,描述特定於某個處理器體系結構的具體實現[4]。
System V ABI的主要參考標準包括:
􀂄 目標系統處理器的體系結構手冊
􀂄 System V Interface Definition(接口定義), 第4版
􀂄 IEEE POSIX 1003.1-1990標準操作系統規範
􀂄 X/Open Common Application Environment Specification(CAE), Issue 4.2
􀂄 International Standard, ISO/IEC 9899:1990(E), Programming Languages – C, 12/15/90.
􀂄 X Window System™, X Version 11, Release 5, 圖形用戶界面規範
 
2.2 LSB
  由於我們所關心的主要是Linux平臺上目標文件的格式,所以Linux標準LSB (Linux Standard Base)也是重要的參考資料。LSB的目標是增強不同的Linux發佈版本之間的兼容性,與ABI類似,也由兩個部分組成:
􀂄 gLSB(Generic LSB) 適用於所有體系結構
􀂄 archLSB(Architecture Specific LSB) 特定某種體系結構的LSB
  目前,LSB由SourceForge開放源碼項目社區提供支持。
 
3 ELF文件格式
     一個程序要想在內存中運行,除了編譯之外還要經過鏈接裝入這兩個步驟。

    鏈接器和裝入器的基本工作原理

     一個程序要想在內存中運行,除了編譯之外還要經過鏈接和裝入這兩個步驟。從程序員的角度來看,引入這兩個步驟帶來的好處就是可以直接在程序中使用printf和errno這種有意義的函數名和變量名,而不用明確指明printf和errno在標準C庫中的地址。當然,爲了將程序員從早期直接使用地址編程的夢魘中解救出來,編譯器和彙編器在這當中做出了革命性的貢獻。編譯器和彙編器的出現使得程序員可以在程序中使用更具意義的符號來爲函數和變量命名,這樣使得程序在正確性和可讀性等方面都得到了極大的提高。但是隨着C語言這種支持分別編譯的程序設計語言的流行,一個完整的程序往往被分割爲若干個獨立的部分並行開發,而各個模塊間通過函數接口或全局變量進行通訊。這就帶來了一個問題,編譯器只能在一個模塊內部完成符號名到地址的轉換工作,不同模塊間的符號解析由誰來做呢?比如前面所舉的例子,調用printf的用戶程序和實現了printf的標準C庫顯然就是兩個不同的模塊。實際上,這個工作是由鏈接器來完成的。

爲了解決不同模塊間的鏈接問題,鏈接器主要有兩個工作要做――符號解析和重定位:

符號解析:當一個模塊使用了在該模塊中沒有定義過的函數或全局變量時,編譯器生成的符號表會標記出所有這樣的函數或全局變量,而鏈接器的責任就是要到別的模塊中去查找它們的定義,如果沒有找到合適的定義或者找到的合適的定義不唯一,符號解析都無法正常完成。

重定位:編譯器在編譯生成目標文件時,通常都使用從零開始的相對地址。然而,在鏈接過程中,鏈接器將從一個指定的地址開始,根據輸入的目標文件的順序以段爲單位將它們一個接一個的拼裝起來。除了目標文件的拼裝之外,在重定位的過程中還完成了兩個任務:一是生成最終的符號表;二是對代碼段中的某些位置進行修改,所有需要修改的位置都由編譯器生成的重定位表指出。

舉個簡單的例子,上面的概念對讀者來說就一目瞭然了。假如我們有一個程序由兩部分構成,m.c中的main函數調用f.c中實現的函數sum:

 
int i = 1;
int j = 2;
extern int sum();
void main()
{
       int s;
       s = sum(i, j);
}
int sum(int i, int j)
{
       return i + j;
}
在Linux用gcc分別將兩段源程序編譯成目標文件:
$ gcc -c m.c
$ gcc -c f.c
我們通過objdump來看看在編譯過程中生成的符號表和重定位表:
$ objdump -x m.o
……
SYMBOL TABLE:
……
00000000 g O .data  00000004 i
00000004 g O .data  00000004 j
00000000 g F .text  00000021 main
00000000        *UND*  00000000 sum
RELOCATION RECORDS FOR [.text]:
OFFSET  TYPE            VALUE
00000007 R_386_32         j
0000000d R_386_32         i
00000013 R_386_PC32       sum
 
首先,我們注意到符號表裏面的sum被標記爲UND(undefined),也就是在m.o中沒有定義,所以將來要通過ld(Linux下的鏈接器)的符號解析功能到別的模塊中去查找是否存在函數sum的定義。另外,在重定位表中有三條記錄,指出了在重定位過程中代碼段中三處需要修改的位置,分別位於7、d和13。下面以一種更加直觀的方式來看一下這三個位置:
$ objdump -dx m.o
Disassembly of section .text:
00000000 :
  0:   55 push  �p
  1:   89 e5 mov    %esp,�p
  3:   83 ec 04 sub    $0x4,%esp
  6:   a1 00 00 00 00 mov    0x0,�x
7: R_386_32     j
  b:   50 push  �x
  c:   a1 00 00 00 00 mov    0x0,�x
d: R_386_32     i
  11:   50 push  �x
  12:   e8 fc ff ff ff call  13
13: R_386_PC32  sum
  17:   83 c4 08 add   $0x8,%esp
  1a:   89 c0 mov   �x,�x
  1c:   89 45 fc mov   �x,0xfffffffc(�p)
  1f:   c9 leave
  20:   c3 ret
以sum爲例,對函數sum的調用是通過call指令實現的,使用IP相對尋址方式。可以看到,在目標文件m.o中,call指令位於從零開始的相對地址12的位置,這裏存放的e8是call的操作碼,而從13開始的4個字節存放着sum相對call的下一條指令add的偏移。顯然,在鏈接之前這個偏移量是不知道的,所以將來要來修改13這裏的代碼。那現在這裏爲什麼存放着0xfffffffc(注意Intel的CPU使用little endian的編址方式)呢?這大概是出於安全的考慮,因爲0xfffffffc正是-4的補碼錶示(讀者可以在gdb中使用p /x -4查看),而call指令本身佔用了5個字節,因此無論如何call指令中的偏移量不可能是-4。我們再看看重定位之後call指令中的這個偏移量被修改成了什麼:
$ gcc m.o f.o
$ objdump -dj .text a.out | less
Disassembly of section .text:
……
080482c4 :
……
80482d6:      e8 0d 00 00 00 call   80482e8
80482db:      83 c4 08 add    $0x8,%esp
……
080482e8 :
……
 

可以看到經過重定位之後,call指令中的偏移量修改成0x0000000d了,簡單的計算告訴我們:0x080482e8-0x80482db=0xd。這樣,經過重定位之後最終的可執行程序就生成了。

可執行程序生成後,下一步就是將其裝入內存運行。Linux下的編譯器(C語言)是cc1,彙編器是as,鏈接器是ld,但是並沒有一個實際的程序對應裝入器這個概念。實際上,將可執行程序裝入內存運行的功能是由execve(2)這一系統調用實現的。

簡單來講,程序的裝入主要包含以下幾個步驟:

1:讀入可執行文件的頭部信息以確定其文件格式及地址空間的大小;

2:以段的形式劃分地址空間;

3:將可執行程序讀入地址空間中的各個段,建立虛實地址間的映射關係;

4:將bbs段清零;

5:創建堆棧段;

6:建立程序參數、環境變量等程序運行過程中所需的信息;

7:啓動運行。

 
3.1 簡介
目標文件有三種類型
􀂄 可重定位文件(Relocatable File) 包含適合於與其他目標文件鏈接來創建可執行文件或者共享目標文件的代碼和數據。
􀂄 可執行文件(Executable File) 包含適合於執行的一個程序,此文件規定了 exec() 如何創建一個程序的進程映像。
􀂄 共享目標文件(Shared Object File) 包含可在兩種上下文中鏈接的代碼和數據。首先鏈接編輯器可以將它和其它可重定位文件和共享目標文件一起處理,生成另外一個目標文件。其次,動態鏈接器(Dynamic Linker)可能將它與某個可執行文件以及其它共享目標一起組合,創建進程映像。
  目標文件全部是程序的二進制表示,目的是直接在某種處理器上直接執行。
 
3.1.1 目標文件中的數據表示
   目標文件格式支持 8 位字節/32位體系結構。不過這種格式是可以擴展的,目標文件因此以某些機器獨立的格式表達某些控制數據,使得能夠以一種公共的方式來識別和解釋其內容。目標文件中的其它數據使用目標處理器的編碼結構,而不管文件在何種機器上創建。
  ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
   目標文件中的所有數據結構都遵從“自然”大小和對齊規則。如果必要,數據結構可以包含顯式的補齊,例如爲了確保4字節對象按4字節邊界對齊。數據對齊同樣適用於文件內部。
  
3.2 目標文件格式
    目標文件既要參與程序鏈接又要參與程序執行。出於方便性和效率考慮,目標文件格式提供了兩種並行視圖,分別反映了這些活動的不同需求。下面 列出目標文件 在鏈接時  與 執行時 的格式視圖。 兩個過程的目標文件 在形式上 經過調整是有較大差別的。  
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
    文件開始處是一個ELF 頭部(ELF Header),用來描述整個文件的組織。節區部分包含鏈接視圖的大量信息:指令、數據、符號表、重定位信息等等。
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 //7f 45[E] 4c[L] 46[F]
  Class:                        ELF32
  Data:                         2's complement, little endian
  Version:                       1 (current)
  OS/ABI:                       UNIX - System V
  ABI Version:                   0
  Type:                         EXEC (Executable file)
  Machine:                       Intel 80386
  Version:                       0x1
  Entry point address:            0x8048320
  Start of program headers:         52 (bytes into file)
  Start of section headers:         4772 (bytes into file)
  Flags:                        0x0
  Size of this header:             52 (bytes)
  Size of program headers:         32 (bytes)
  Number of program headers:        9
  Size of section headers:         40 (bytes)
  Number of section headers:        30
  Section header string table index: 27
 
    程序頭部表(Program Header Table),如果存在的話,告訴系統如何創建進程映像用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。
Elf file type is EXEC (Executable file)
Entry point 0x8048320
There are 9 program headers, starting at offset 52
Program Headers:
  Type          Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR          0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP        0x000154 0x08048154 0x08048154 0x00013 0x00013 R  0x1
     [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD          0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
  LOAD          0x000f08 0x08049f08 0x08049f08 0x00240 0x00324 RW  0x1000
  DYNAMIC       0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE          0x000168 0x08048168 0x08048168 0x00044 0x00044 R  0x4
  GNU_EH_FRAME   0x000690 0x08048690 0x08048690 0x0002c 0x0002c R  0x4
  GNU_STACK     0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO     0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R   0x1
 Section to Segment mapping: 
//目標文件的“段”包含一個或者多個“節區”,也就是“段內容(Segment Contents)”
  Segment Sections...
   00    
   01    .interp 
   02    .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03    .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04    .dynamic 
   05    .note.ABI-tag .note.gnu.build-id 
   06    .eh_frame_hdr 
   07    
   08    .init_array .fini_array .jcr .dynamic .got
 
    節區頭部表(Section Heade Table)包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
Section Headers:
  [Nr] Name            Type           Addr     Off   Size   ES Flg Lk Inf Al
  [ 0]                NULL          00000000 000000 000000 00     0   0  0
  [ 1] .interp         PROGBITS       08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag    NOTE          08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE           08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash        GNU_HASH       080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym         DYNSYM         080481cc 0001cc 000050 10   A  6   1  4
  [ 6] .dynstr         STRTAB         0804821c 00021c 00004c 00   A  0   0  1
  [ 7] .gnu.version     VERSYM        08048268 000268 00000a 02   A  5   0  2
  [ 8] .gnu.version_r   VERNEED        08048274 000274 000020 00   A  6   1  4
  [ 9] .rel.dyn        REL           08048294 000294 000008 08   A  5   0  4
  [10] .rel.plt        REL           0804829c 00029c 000018 08   A  5  12  4
  [11] .init           PROGBITS       080482b4 0002b4 000023 00  AX  0   0  4
  [12] .plt            PROGBITS       080482e0 0002e0 000040 04  AX  0   0 16
  [13] .text           PROGBITS       08048320 000320 000262 00  AX  0   0 16
  [14] .fini           PROGBITS       08048584 000584 000014 00  AX  0   0  4
  [15] .rodata         PROGBITS       08048598 000598 0000f8 00   A  0   0  4
  [16] .eh_frame_hdr    PROGBITS       08048690 000690 00002c 00   A  0   0  4
  [17] .eh_frame        PROGBITS       080486bc 0006bc 0000b0 00   A  0   0  4
  [18] .init_array      INIT_ARRAY     08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array      FINI_ARRAY     08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr            PROGBITS       08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic        DYNAMIC        08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got            PROGBITS       08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt        PROGBITS       0804a000 001000 000018 04  WA  0   0  4
  [24] .data           PROGBITS       0804a020 001020 000128 00  WA  0   0 32
  [25] .bss            NOBITS         0804a160 001148 0000cc 00  WA  0   0 32
  [26] .comment        PROGBITS       00000000 001148 000055 01  MS  0   0  1
  [27] .shstrtab        STRTAB         00000000 00119d 000106 00      0   0  1
  [28] .symtab         SYMTAB         00000000 001754 0004c0 10     29  47  4
  [29] .strtab         STRTAB         00000000 001c14 0002e5 00     0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
    注意:儘管圖中顯示的各個組成部分是有順序的,實際上除了 ELF 頭部表以外,其他節區和段都沒有規定的順 序
 
3.3 ELF Header部分
  文件的最開始幾個字節給出如何解釋文件的提示信息。這些信息獨立於處理器,也獨立於文件中的其餘內容。ELF Header部分可以用下圖中的數據結構表示:
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
其中,e_ident數組給出了ELF的一些標識信息,這個數組中不同下標的含義如表 2所示:
           ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
 
注意:在 32 位 Intel 體系結構上要求:
1、標誌
    位置               取值
    e_ident[EI_CLASS]     ELFCLASS32
    e_ident[EI_DATA]     ELFDATA2LSB
2、處理器標識(e_machine)成員必須是 EM_386。
 
ELF Header中各個字段的說明如表 4:
 
e_ident   目標文件標識
 
e_type    目標文件類型:
         ET_NONE   0  未知目標文件格式 
         ET_REL   1  可重定位文件
        ET_EXEC 2 可執行文件
        ET_DYN  3 共享目標文件
        ET_CORE 4 Core 文件(轉儲格式)
        ET_LOPROC  0xff00  特定處理器文件
        ET_HIPROC  0xffff  特定處理器文件
        ET_LOPROC 和 ET_HIPROC 之間的取值用來標識與處理器相關的文件格式。
e_machine 給出文件的目標體系結構類型
         EM_NONE  0  未指定
         EM_M32   1  AT&T WE 32100
        EM_SPARC  2  SPARC
         EM_386  3  Intel 80386
         EM_68K  4  Motorola 68000
         EM_88K  5  Motorola 88000
         EM_860  7  Intel 80860
         EM_MIPS  8  MIPS RS3000
        其它值都是保留的。特定處理器的 ELF 名稱會使用機器名來進行區分。
 
e_version 目標文件版本  EV_NONE  0  非法版本; EV_CURRENT  1  當前版本
e_entry   程序入口的虛擬地址。如果目標文件沒有程序入口,可以爲 0。
e_phoff   程序頭部表格(Program Header Table)的偏移量(按字節計算)。如果文件沒有程序頭部表格,可          以爲 0。
e_shoff   節區頭部表格(Section Header Table)的偏移量(按字節計算)。如果文件沒有節區頭部表格,可          以爲 0
e_flags  保存與文件相關的,特定於處理器的標誌。標誌名稱採用EF_machine_flag 的格式
e_ehsize  ELF 頭部的大小(以字節計算)。
e_phentsize   程序頭部表格的表項大小(按字節計算)。
e_phnum     程序頭部表格的表項數目。可以爲 0 [該目標文件中一共有多少個段Segment]
===========Program Header Table===================
  Segment Sections...
  00    
  01    .interp 
  02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
  03    .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
  04    .dynamic 
  05    .note.ABI-tag .note.gnu.build-id 
  06    .eh_frame_hdr 
  07    
  08    .init_array .fini_array .jcr .dynamic .got
==================================================
e_shentsize   節區頭部表格的表項大小(按字節計算)。
e_shnum    節區頭部表格的表項數目。可以爲 0。[該目標文件中一共有多少個節區Section]
================Section Header Table=====================================
Section Headers:
  [Nr] Name            Type          Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                 NULL           00000000 000000 000000 00     0   0  0
  [ 1] .interp          PROGBITS       08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE           08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE          08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash        GNU_HASH       080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym          DYNSYM        080481cc 0001cc 000050 10   A  6   1  4
  [ 6] .dynstr          STRTAB        0804821c 00021c 00004c 00   A  0   0  1
  [ 7] .gnu.version     VERSYM         08048268 000268 00000a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED        08048274 000274 000020 00   A  6   1  4
  [ 9] .rel.dyn         REL           08048294 000294 000008 08   A  5   0  4
  [10] .rel.plt         REL           0804829c 00029c 000018 08   A  5  12  4
  [11] .init            PROGBITS       080482b4 0002b4 000023 00  AX  0   0  4
  [12] .plt            PROGBITS       080482e0 0002e0 000040 04  AX  0   0 16
  [13] .text            PROGBITS       08048320 000320 000262 00  AX  0   0 16
  [14] .fini            PROGBITS       08048584 000584 000014 00  AX  0   0  4
  [15] .rodata          PROGBITS       08048598 000598 0000f8 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS       08048690 000690 00002c 00   A  0   0  4
  [17] .eh_frame        PROGBITS       080486bc 0006bc 0000b0 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr            PROGBITS       08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic         DYNAMIC        08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got            PROGBITS       08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt         PROGBITS       0804a000 001000 000018 04  WA  0   0  4
  [24] .data            PROGBITS       0804a020 001020 000128 00  WA  0   0 32
  [25] .bss            NOBITS         0804a160 001148 0000cc 00  WA  0   0 32
  [26] .comment         PROGBITS       00000000 001148 000055 01  MS  0   0  1
  [27] .shstrtab        STRTAB         00000000 00119d 000106 00     0   0  1
  [28] .symtab          SYMTAB        00000000 001754 0004c0 10    29  47  4
  [29] .strtab          STRTAB        00000000 001c14 0002e5 00     0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
======================================================
e_shstrndx   節區頭部表格中與節區名稱字符串表相關的表項的索引。如果文件沒有節區名稱字符串表,此參              數可以爲SHN_UNDEF。
 
3.4 節區(Sections)
節區中包含目標文件中的所有信息,除了:ELF 頭部、程序頭部表格、節區頭部表格。節區滿足以下條件:
(1). 目標文件中的每個(每類)節區都有對應的節區頭部描述它(在Section Header Table 節區頭部表格中對包含的每個節區都有描述,且每個節區還有各自的節目頭部【不是節區頭部表格,節區頭表格只有一個,對各個節目集中描述】),反過來,有節區頭部不意味着有節區。
(2). 每個節區佔用文件中一個連續字節區域(這個區域可能長度爲 0)。
(3). 文件中的節區不能重疊,不允許一個字節存在於兩個節區中的情況發生。
(4). 目標文件中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容未指定。
 
如下面的描述
Relocation section '.rel.dyn' at offset 0x294 contains 1 entries:
 Offset    Info    Type          Sym.Value  Sym. Name
08049ffc  00000206 R_386_GLOB_DAT    00000000  __gmon_start__
Relocation section '.rel.plt' at offset 0x29c contains 3 entries:
 Offset    Info    Type          Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   printf
0804a010  00000207 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a014  00000307 R_386_JUMP_SLOT   00000000  __libc_start_main
 
3.4.1 節區頭部表格【目標文件中只有一個節區頭部表格, 這個表格集中描述各個節區的位置等信息,每個節目又有自己的節目頭部,來描述本節目的信息】
   ELF 頭部中,e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數;e_shnum給出表格中條目數目;e_shentsize 給出每個項目的字節數。從這些信息中可以確切地定位節區的具體位置、長度。
節區頭部表格中比較特殊的幾個下標如下:
 
節區頭部表格中的特殊下標
SHN_UNDEF  0  標記未定義的、缺失的、不相關的,或者沒有含義的節區引用
SHN_LORESERVE  OXFF00  保留索引的下界
SHN_LOPROC  0XFF00  保留給處理器特殊的語義
SHN_HIPROC  0XFF1F  保留給處理器特殊的語義
SHN_ABS     OXFFF1   包含對應引用量的絕對取值。這些值不會被重定位所影響
SHN_COMMON OXFFF2 相對於此節區定義的符號是公共符號。如 FORTRAN 中 COMMON 或者未分配的 C 外部變量。
SHN_HIRESERVE  0XFFFF保留索引的上界
介於 SHN_LORESERVE 和 SHN_HIRESERVE 之間的表項不會出現在節區頭部表中。
 
3.4.2 節區頭部
每個節區頭部可以用如下數據結構描述:
typedef struct{
    Elf32_Word sh_name;
   Elf32_Word sh_type;
   Elf32_Word sh_flags;
   Elf32_Addr sh_addr;
   Elf32_Off sh_offset;
   Elf32_Word sh_size;
   Elf32_Word sh_link;
   Elf32_Word sh_info;
   Elf32_Word sh_addralign;
   Elf32_Word sh_entsize;
}Elf32_Shdr;
對其中各個字段的解釋如下:
sh_name   給出節區名稱。是節區頭部字符串表節區(Section Header String Table Section)的索引。名字          是一個 NULL  結尾的字符串。
sh_type  爲節區的內容和語義進行分類。參見節區類型。
sh_flags  節區支持 1 位形式的標誌,這些標誌描述了多種屬性。
sh_addr  如果節區將出現在進程的內存映像中,此成員給出節區的第一個字節應處的位置。否則此字段爲 0。
sh_offset  此成員的取值給出節區的第一個字節與文件頭之間的偏移。不過,SHT_NOBITS 類型的節區不佔用文           件的空間,因此其 sh_offset 成員給出的是其概念性的偏移。
sh_size   此成員給出節區的長度(字節數)。除非節區的類型是SHT_NOBITS,否則節區佔用文件中的                sh_size 字節。類型爲SHT_NOBITS 的節區長度可能非零,不過卻不佔用文件中的空間
sh_link   此成員給出節區頭部表索引鏈接。其具體的解釋依賴於節區類型。
sh_info   此成員給出附加信息,其解釋依賴於節區類型。
sh_addralign  某些節區帶有地址對齊約束。例如,如果一個節區保存一個doubleword,那麼系統必須保證整             個節區能夠按雙字對齊。sh_addr 對 sh_addralign 取模,結果必須爲 0。目前僅允許取值爲               0 和 2 的冪次數。數值 0 和 1 表示節區沒有對齊約束。
sh_entsize    某些節區中包含固定大小的項目,如符號表。對於這類節區,此成員給出每個表項的長度字節             數。如果節區中並不包含固定長度表項的表格,此成員取值爲 0。
 
索引爲零(SHN_UNDEF)的節區頭部也是存在的,儘管此索引標記的是未定義的節區引用。這個節區的內容固定如下:
表 7 SHN_UNDEF(0)節區的內容
    字段名稱  取值  說明
    sh_name  0  無名稱
   sh_type  SHT_NULL  非活動
   sh_flags  0  無標誌
    sh_addr  0  無地址
   sh_offset  0  無文件偏移
    sh_size  0  無尺寸大小
    sh_link  SHN_UNDEF  無鏈接信息
    sh_info  0  無輔助信息
   sh_addralign  0  無對齊要求
   sh_entsize  0  無表項
3.4.2.1 節區類型—sh_type字段
SHT_NULL     0  此值標誌節區頭部是非活動的,沒有對應的節區。此節區頭部中的其他成員取值無意義。
SHT_PROGBITS  1  此節區包含程序定義的信息,其格式和含義都由程序來解釋。
SHT_SYMTAB    2   此節區包含一個符號表。目前目標文件對該種類型的節區都只能包含一個,不過這個             限制將來可能發生變化。一般,SHT_SYMTAB 節區提供用於鏈接編輯(指 ld 而言)的             符號,儘管也可用來實現動態鏈接。
SHT_STRTAB   3  此節區包含字符串表。目標文件可能包含多個字符串表節區。
SHT_RELA     4   此節區包含重定位表項,其中可能會有補齊內容(addend),例如 32 位目標文件中的                   Elf32_Rela 類型。目標文件可能擁有多個重定位節區。
SHT_HASH     5   此節區包含符號哈希表。所有參與動態鏈接的目標都必須包含一個符號哈希表。目前,一              個目標文件只能包含一個哈希表,不過此限制將來可能會解除。
SHT_DYNAMIC  6  此節區包含動態鏈接的信息。目前一個目標文件中只能包含一個動態節區,將來可能會取消這               一限制。
SHT_NOTE     7   此節區包含以某種方式來標記文件的信息。
SHT_NOBITS   8  這種類型的節區不佔用文件中的空間,其他方面和SHT_PROGBITS 相似。儘管此節區不包             含任何字節,成員sh_offset 中還是會包含概念性的文件偏移
SHT_REL     9  此節區包含重定位表項,其中沒有補齊(addends),例如 32 位目標文件中的 Elf32_rel                 類型。目標文件中可以擁有多個重定位節區。
SHT_SHLIB    10   此節區被保留,不過其語義是未規定的。包含此類型節區的程序與 ABI 不兼容。
SHT_DYNSYM   11  作爲一個完整的符號表,它可能包含很多對動態鏈接而言不必要的符號。因此,目標文件也                 可以包含一個 SHT_DYNSYM 節區,其中保存動態鏈接符號的一個最小集合,以節省空間。
SHT_LOPROC   0X70000000  這一段(包括兩個邊界),是保留給處理器專用語義的。
SHT_HIPROC   OX7FFFFFFF  這一段(包括兩個邊界),是保留給處理器專用語義的。
SHT_LOUSER   0X80000000  此值給出保留給應用程序的索引下界。
SHT_HIUSER   0X8FFFFFFF  此值給出保留給應用程序的索引上界
其它的節區類型是保留的
 
3.4.2.2 sh_flags 字段
   sh_flags字段定義了一個節區中包含的內容是否可以修改、是否可以執行等信息。如果一個標誌位被設置,則該位取值爲1。未定義的各位都設置爲0。
 
表 9 節區頭部的 sh_flags字段取值
名稱          取值
SHF_WRITE     0x1
SHF_ALLOC     0x2
SHF_EXECINSTR  0x4
SHF_MASKPROC   0xF0000000
 
其中已經定義了的各位含義如下:
SHF_WRITE: 節區包含進程執行過程中將可寫的數據。
SHF_ALLOC: 此節區在進程執行過程中佔用內存。某些控制節區並不出現於目標文件的內存映像中,對於那些節區,此位應設置爲 0。
SHF_EXECINSTR: 節區包含可執行的機器指令。
SHF_MASKPROC: 所有包含於此掩碼中的四位都用於處理器專用的語義。
 
3.4.2.3 sh_link和sh_info字段
 
根據節區類型的不同,sh_link 和 sh_info 的具體含義也有所不同:
sh_link和sh_info字段解釋
sh_type             sh_link            sh_info
SHT_DYNAMIC   此節區中條目所用到的字符串表格的節區頭部索引    0
SHT_HASH     此哈希表所適用的符號表的節區頭部索引          0
SHT_REL      相關符號表的節區頭部索引      重定位所適用的節區的節區頭部索引
SHT_RELA     相關符號表的節區頭部索引     重定位所適用的節區的節區頭部索引     
SHT_SYMTAB   相關聯的字符串表的節區頭部索引  相關聯的字符串表的節區頭部索引最後一個局部符號(綁定                                       STB_LOCAL)的符號表索引值加一
SHT_DYNSYM   相關聯的字符串表的節區頭部索引  相關聯的字符串表的節區頭部索引最後一個局部符號(綁定                                       STB_LOCAL)的符號表索引值加一
其它   SHN_UNDEF   0
 
3.4.3 特殊節區【講述各個節區的內容等情況】
 
很多節區中包含了程序和控制信息。下面的表中給出了系統使用的節區,以及它們的類型和屬性
 
 常見特殊節區
名稱    類型       屬性        含義
.bss   SHT_NOBITS   SHF_ALLOC + SHF_WRITE   包含將出現在程序的內存映像中的未初始化數據。根據定                                         義,當程序開始執行,系統將把這些數據初始化爲 0此                                   節區不佔用文件空間
.comment  SHT_PROGBITS  (無)                    包含版本控制信息。
.data     SHT_PROGBITS    SHF_ALLOC + SHF_WRITE    這些節區包含初始化了的數據,將出現在程序的內存                                       映像中。
.data1    SHT_PROGBITS    SHF_ALLOC + SHF_WRITE     同上
.debug    SHT_PROGBITS  (無)               此節區包含用於符號調試的信息
.dynamic  SHT_DYNAMIC                     此節區包含動態鏈接信息。節區的屬性將包含 SHF_ALLOC                                         位。是否 SHF_WRITE 位被設置取決於處理器。
.dynstr   SHT_STRTAB   SHF_ALLOC           此節區包含用於動態鏈接的字符串,大多數情況下這些字                                       符串代表了與符號表項相關的名稱。
.dynsym   SHT_DYNSYM  SHF_ALLOC            此節區包含了動態鏈接符號表
 
.fini    SHT_PROGBITS  SHF_ALLOC + SHF_EXECINSTR   此節區包含了可執行的指令,是進程終止代碼的                                 一部分。程序正常退出時,系統將安排執行這裏的代碼
.got     SHT_PROGBITS                  此節區包含全局偏移表
.hash     SHT_HASH    SHF_ALLOC           此節區包含了一個符號哈希表。
 
.init    SHT_PROGBITS   SHF_ALLOC + SHF_EXECINSTR   此節區包含了可執行指令,是進程初始化代碼的                             一部分當程序開始執行時,系統要在開始調用主程序入口之前                              (通常指 C 語言的 main 函數)執行這些代碼。
.interp   SHT_PROGBITS   此節區包含程序解釋器的路徑名。如果程序包含一個可加載的段,段中包含此節                       區,那麼節區的屬性將包含SHF_ALLOC 位,否則該位爲 0。
.line     SHT_PROGBITS    (無)   此節區包含符號調試的行號信息,其中描述了源程序與機器指令之間的對                              應關係。其內容是未定義的。
.note     SHT_NOTE       (無)   此節區中包含註釋信息,有獨立的格式。
.plt     SHT_PROGBITS          此節區包含過程鏈接表(procedure linkage table)
 
.relname   SHT_REL    這些節區中包含了重定位信息。如果文件中包含可加載的段,段中有重定位內容,節                    區的屬性將包含 SHF_ALLOC 位,否則該位置 0。傳統上 name 根據重定位所適用的節                     區給定。例如 .text 節區的重定位節區名字將是:.rel.text 或者 .rela.text。
 
.relaname   SHT_RELA  同上
.rodata    SHT_PROGBITS  SHF_ALLOC  這些節區包含只讀數據,這些數據通常參與進程映像的不可寫段
.rodata1   SHT_PROGBITS  SHF_ALLOC  同上
.shstrtab   SHT_STRTAB  此節區包含節區名稱
.strtab     SHT_STRTAB   此節區包含字符串,通常是代表與符號表項相關的名稱。如果文件擁有一個可加載                      的段,段中包含符號串表,節區的屬性將包含SHF_ALLOC 位,否則該位爲 0。
.symtab    SHT_SYMTAB    此節區包含一個符號表。如果文件中包含一個可加載的段,並且該段中包含符號                        表,那麼節區的屬性中包含SHF_ALLOC 位,否則該位置爲 0。
.text    SHT_PROGBITS    SHF_ALLOC + SHF_EXECINSTR    此節區包含程序的可執行指令
在分析這些節區的時候,需要注意如下事項:
1:以“.”開頭的節區名稱是系統保留的。應用程序可以使用沒有前綴的節區名稱,以避免與系統節區衝突。
2:目標文件格式允許人們定義不在上述列表中的節區。
3:目標文件中也可以包含多個名字相同的節區。
4:保留給處理器體系結構的節區名稱一般構成爲:處理器體系結構名稱簡寫 + 節區名稱。
5:處理器名稱應該與 e_machine 中使用的名稱相同。例如 .FOO.psect 街區是由FOO 體系結構定義的 psect 節區。
另外,有些編譯器對如上節區進行了擴展,這些已存在的擴展都使用約定俗成的名稱,如:
􀂄 .sdata
􀂄 .tdesc
􀂄 .sbss
􀂄 .lit4
􀂄 .lit8
􀂄 .reginfo
􀂄 .gptab
􀂄 .liblist
􀂄 .conflict
􀂄 …
3.5 字符串表(String Table)
字符串表節區包含以NULL(ASCII碼0)結尾的字符序列,通常稱爲字符串。ELF目標文件通常使用字符串來表示符號和節區名稱。對字符串的引用通常以字符串在字符串表中的下標給出。
一般,第一個字節(索引爲 0)定義爲一個空字符串。類似的,字符串表的最後一個字節也定義爲 NULL,以確保所有的字符串都以NULL結尾。索引爲0的字符串在不同的上下文中可以表示無名或者名字爲 NULL的字符串。
允許存在空的字符串表節區,其節區頭部的sh_size成員應該爲0。對空的字符串表而言,非0的索引值是非法的。
例如:對於各個節區而言,節區頭部的sh_name成員包含其對應的節區頭部字符串表節區的索引,此節區由ELF 頭的e_shstrndx 成員給出。下圖給出了包含 25 個字節的一個字符串表,以及與不同索引相關的字符串。
ELF <wbr>可執行鏈接文件分析(a.out)、虛擬存儲、虛擬內存管理、程序加載
在使用、分析字符串表時,要注意以下幾點:
􀂄 字符串表索引可以引用節區中任意字節。
􀂄 字符串可以出現多次
􀂄 可以存在對子字符串的引用
􀂄 同一個字符串可以被引用多次。
􀂄 字符串表中也可以存在未引用的字符串。
 
3.6 符號表(Symbol Table)
目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。符號表索引是對此數組的索引。索引0表示表中的第一表項,同時也作爲未定義符號的索引。
符號表項的格式如下:
typedef struct {
    Elf32_Word st_name;
   Elf32_Addr st_value;
   Elf32_Word st_size;
   unsigned char st_info;
   unsigned char st_other;
   Elf32_Half st_shndx;
} Elf32_sym;
其中各個字段的含義說明如表 13:
st_name   包含目標文件符號字符串表的索引,其中包含符號名的字符串表示。如果該值非 0,則它表示了給出          符號名的字符串表索引,否則符號表項沒有名稱。
         注:外部 C 符號在 C 語言和目標文件的符號表中具有相同的名稱。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
分享:
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章