http://ccckmit.wikidot.com/lk:elf

目的檔ELF 格式(Executable and Linking Format) 是 UNIX/Linux 系統中較先進的目的檔格式。這種格式是 AT&T 公司在設計第五代UNIX (UNIX System V) 時所發展出來的。因此,ELF格式的主要文件被放在規格書 -『System V Application Binary Interface』的第四章的 Object Files當中 ,該文件詳細的介紹了 UNIX System V 中二進位檔案格式的存放方式。並且在第五章的 Program Loading and Dynamic Linking 當中,說明了動態連結與載入的設計方法。

雖然該規格書當中並沒有介紹與機器結構相關的部分,但由於各家CPU廠商都會自行撰寫與處理器有關的規格書,以補充該文件的不足之處,因此,若要查看與處理器相關的部分,可以查看各個廠商的補充文件 。

ELF可用來記錄目的檔 (object file)、執行檔 (executable file)、動態連結檔 (share object)、與核心傾印 (core dump) 檔等格式,並且支援較先進的動態連結與載入等功能。因此,ELF 格式在 UNIX/Linux 的設計上具有相當關鍵性的地位。

為了支援連結與執行等兩種時期的不同用途,ELF 格式可以分為兩種不同觀點,第一種是連結時期觀點 (Linking View),第二種是執行時期觀點 (Execution View)。圖 4 顯示了這兩種不同觀點的結構圖。在連結時期,是以分段 (Section) 為主的結構,如圖 4 (a) 所示,但在執行時期,則是以分區 (Segment) 為主的結構,如圖 4 (b) 所示。其中,一個區通常是數個分段的組合體,像是與程式有關的段落,包含程式段、程式重定位等,在執行時期會被組合為一個分區。

圖 4. 目的檔 ELF 的兩種不同觀點

因此,ELF 檔案有兩個不同用途的表頭,第一個是程式表頭 (Program Header Table),這個表頭記載了分區資訊,因此也可稱為分區表頭 (Segment Header Table)。程式表頭是執行時期的主要結構。而第二個表頭是分段表頭 (Section Header Table),記載了的分段資訊,是連結時期的主要結構。

程式片段 2 顯示了ELF 的檔頭結構 Elf32_Ehdr,其中的 e_phoff 指向程式表頭,而 e_shoff 指向分段表頭,透過這兩個欄位,我們可以取得兩種表頭資訊。

程式片段 2 : 目的檔 ELF 的檔頭結構 (Elf32_Ehdr)

typedef struct {
unsigned char e_ident[EI_NIDENT]; // ELF 辨識代號區
Elf32_Half e_type; // 檔案類型代號
Elf32_Half e_machine; // 機器平台代號
Elf32_Word e_version; // 版本資訊
Elf32_Addr e_entry; // 程式的起始位址
Elf32_Off e_phoff; // 程式表頭的位址
Elf32_Off e_shoff; // 分段表頭的位址
Elf32_Word e_flags; // 與處理器有關的旗標值
Elf32_Half e_ehsize; // ELF檔頭的長度
Elf32_Half e_phentsize; // 程式表頭的記錄長度
Elf32_Half e_phnum; // 程式表頭的記錄個數
Elf32_Half e_shentsize; // 分段表頭的記錄長度
Elf32_Half e_shnum; // 分段表頭的記錄個數
Elf32_Half e_shstrndx; // 分段字串表 .shstrtab 的分段代號
} Elf32_Ehdr;

在連結時期,連結器會以 ELF 的分段結構為主,利用分段表頭讀出各個分段。ELF 檔可支援任意數目的分段 (當然有上限,必須可以用16 位元整數表達)。而且,每個分段可以具有不同的結構,常見的分段有程式段 (.text) , 資料段 (.data), 唯讀資料段 (.rodata) , 未設定變數段 (.bss), 字串表 (.strtab), 符號表 (.symtab)等。但是,ELF為了支援較先進的連結載入方式,還包含了許多其他類型的段落,像是動態連結相關的區段等,表格 1 顯示了 ELF 中的常見分段名稱與其用途。

表格 1. 目的檔ELF 中的常見分段列表

分段名稱 說明
.text 程式段
.data  
.data1 資料段
.bss 未設初值的全域變數
.rodata  
.rodata1 唯讀資料段
.dynamic 動態連結資訊
.dynstr 動態連結用字串表
.dynsym 動態連結用符號表
.got 動態連結用的全域位移表 (Global Offset Table)
.plt 動態連結用的程序連結表 (Porcedure Linkage Table)
.interp 記錄程式解譯器的路徑 (program interpreter file)
.ctors 物件導向中的建構函數 (constructor) (C++可用)
.dtors 物件導向中的解構函數 (destructor) (C++可用)
.hash 雜湊表
.init 在主程式執行前會執行此段落
.fini 在主程式執行後會執行此段落
.rel<name>  
.rela<name> 重定位資訊,例如: rel.text 是程式段的重定位資訊,rel.data 則是資料段的重定位資訊。
.shstrtab 儲存分段 (Section) 名稱
.strtab 字串表
.symtab 符號表
.debug 除錯資訊 (保留給未來用)
.line 除錯時的行號資訊
.comment 版本控制訊息
.note 附註資訊

由於 ELF 的分段眾多,我們將不詳細介紹每的段落的資料結構,只針對較重要或常見的資料結構進行說明。圖 5 顯示了ELF 檔案的分段與對應的資料結構,其中,檔頭結構是 Elf32_Ehdr、程式表頭結構是 Elf32_Phdr、分段表頭結構是 Elf32_Shdr。而在分段中,符號記錄 (Elf32_Sym) 、重定位記錄 (Elf32_Rel、Elf32_Rela)、與動態連結記錄 (Elf32_Dyn),是較重要的結構。

圖 5. 目的檔ELF的資料結構

分段表頭記錄了各分段 (Section) 的基本資訊,包含分段起始位址等,因此可以透過分段表頭讀取各分段,圖 6 顯示了如何透過分段表頭讀取分段的方法。程式片段 3 則顯示了分段表頭的結構定義程式。

圖 6. 目的檔ELF的分段表頭

程式片段 3 : ELF 的分段表頭記錄

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;

程式表頭指向各個分區 (Segment) ,包含分區的起始位址,因此可以透過程式表頭取得各分區的詳細內容,
圖 7 顯示了如何透過程式表頭取得各分區的方法。 程式片段 4 則顯示了程式表頭的結構定義程式。

圖 7. 目的檔ELF的程式表頭

程式片段 4. 目的檔 ELF 的程式表頭結構

typedef struct {
Elf32_Word p_type; // 分區類型
Elf32_Off p_offset; // 分區位址 (在目的檔中)
Elf32_Addr p_vaddr; // 分區的虛擬記憶體位址
Elf32_Addr p_paddr; // 分區的實體記憶體位址
Elf32_Word p_filesz; // 分區在檔案中的大小
Elf32_Word p_memsz; // 分區在記憶體中的大小
Elf32_Word p_flags; // 分區旗標
Elf32_Word p_align; // 分區的對齊資訊
} Elf32_Phdr;

在靜態連結的情況之下,ELF的連結器同樣會合併 .text, .data, .bss 等段落,也會利用修改記錄 Elf32_Rel 與 Elf32_Rela,進行合併後的修正動作。而且,不同類型的分段會被組合成分區,像是.text, .rodata, .hash, .dynsym, .dynstr, .plt, .rel.got 等分段會被併入到內文區 (Text Segment) 當中。而 .data, .dynamic, .got, .bss 等分段則會被併入到資料區 (Data Segemnt) 當中。

Elf32_Sym 儲存了符號記錄,包含名稱 (st_name)、值 (st_value)、大小 (st_size)、資訊 (st_info)、其他 (st_other)、分段代號 (st_shndx) 等,其中 st_info 欄位又可細分為兩個子欄位,前四個位元是 bind 欄,用來記錄符號的屬性,後四個位元是 type欄,用來記錄符號的類型。

程式片段 5. 目的檔ELF的符號記錄

typedef struct
{
Elf32_Word st_name; // 符號名稱的代號
Elf32_Addr st_value; // 符號的值,通常是位址
Elf32_Word st_size; // 符號的大小,以 byte為單位
unsigned char st_info; // 細分為bind與type兩欄位
unsigned char st_other; // 目前為 0,保留未來使用
Elf32_Half st_shndx; // 符號所在的分段 (Section) 代號
} Elf32_Sym; #define ELF32_ST_BIND(i) ((i) >> 4) // 取出 st_info 中的bind欄位
#define ELF32_ST_TYPE(i) ((i)&0xf) // 取出 st_info 中的type欄位
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf)) // 將 bind 與 type 組成 info

Elf32_Rel 與 Elf32_Rela 是 ELF 檔的兩種重定位記錄,兩者均包含位址欄 (r_offset) 與資訊欄 (r_info),其中資訊欄又可分為兩個子欄位,前面的 byte 是符號代號,後面的 byte 記錄符號類型。另外,在 Elf32_Rela 中,多了一個外加的數值欄位 (r_addend),可用來儲存重定位的位移值。

程式片段 6. 目的檔ELF的重定位記錄

typedef struct
{
Elf32_Addr r_offset; // 符號的位址
Elf32_Word r_info; // r_info可分為sym與type兩欄
} Elf32_Rel; typedef struct
{
Elf32_Addr r_offset; // 符號的位址
Elf32_Word r_info; // r_info可分為sym與type兩欄
Elf32_Sword r_addend; // 外加的數值
} Elf32_Rela; #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))

重定位記錄 Elf32_rel 的 r_info 欄中的 sym 子欄位,會儲存符號表的索引值,因此,程式可以透過 sym 子欄位取得符號記錄。然後,在符號記錄 Elf32_Sym 中的 st_name 欄位,會儲存字串表中的索引值,因此,可以透過 st_name 取得符號的名稱。透過 sym 與 st_name 欄位,可將重定位表、符號表與字串表關連起來,圖 8 顯示了這三個表格的關連狀況圖。

圖 8. 目的檔ELF中的重定位表、符號表與字串表的關連性

雖然分段結構主要式為了連結時使用的,但是,如果不考慮動態連結的情況,載入器也可以利用分段結構直接進行載入。只要載入 .text, .data, .data2, .bss等區段,然後利用 .rel.text, .rel.data, .rel.data2, .rela.text, .rela.data, .rela.data2 等分段進行修改的動作,就能載入 ELF目的檔了。

但是,為了支援動態連結與載入 的技術,ELF 當中多了許多相關的分段,包含解譯段 (.interp)、動態連結段 (.dynamic)、全域位移表 (Global Offset Table : .got)、程序連結表 (Porcedure Linkage Table : .plt) 等,另外還有動態連結專用的字串表 (.dynstr) 、符號表 (.dynsym)、映射表 (.hash)、全域修改記錄 (rel.got) 等作為輔助。

執行ELF載入動作時,使用的是以區塊為主的執行時期觀點,常見的區塊包含程式表頭 (PHDR)、解譯區塊 (INTERP)、載入區塊(LOAD)、動態區塊 (DYNAMIC)、註解區塊 (NOTE)、共用函式庫區塊 (SHLIB) 等。其中,載入區塊通常有兩個以上,如此才能容納程式區塊 (TEXT) 與資料區塊 (DATA) 等不同屬性的區域。

表格 2 目的檔ELF 的常見區塊列表

Segment (區塊型態) Sections (分段) 說明
PT_PHDR Program Header 表頭段,用來計算基底位址 (base address)
PT_INTERP .interp 動態載入區段。
PT_LOAD .interp .note .hash .dynsym .dynstr .rel.dyn .rel.plt .init .plt .text .fini .rodata … 載入器將此區塊載入程式段。
PT_LOAD .data .dynamic .ctors .dtors .jcr .got .bss 載入器將此區塊載入資料段。
PT_DYNAMIC .dynamic 由動態載入器處理

在 Linux 當中,一般目的檔的附檔名是 .o (Object File),而動態連結函式庫的附檔名是 .so (Shared Object)。當程式被編譯為 .so 檔時,ELF目的檔中才會有INTERP 區塊,這個區塊中記錄了動態載入器的相關資訊,ELF載入器可透過這些資訊找到動態載入器 (ELF 文件中稱為Program Interpreter,但若稱為 Dynamic Loader 或許更恰當)。然後,當目的檔載入完成後,就可以開始執行,一但需要使用到動態函數時,才能利用動態載入器將動態函式庫載入。

通常,載入的動作是由作業系統的核心 (Kernel) 所負責的,載入器是作業系統的一部分。例如,Linux 作業系統的核心就會負責載入 ELF 格式的檔案,ELF 檔案的載入過程大致如下所示:

1. Kernel 將 ELF 檔案中的所有 PT_LOAD 型態的區塊載入到記憶體,這些區塊包含程式區塊與資料區塊。
2. Kernel 將載入的區塊映射到該行程的虛擬位址空間中 (例如使用 linux 的 mmap 系統呼叫)。
3. Kernel 找到 PT_INTERP 型態的區塊,並根據區塊內的資訊找到動態連結器 (Dynamic Linker ) 的ELF檔 。
4. Kernel 將動態連結器載入到記憶體,並將其映射到該行程的虛擬位址空間中,然後啟動『動態連結器』。
5. 目的程式開始執行,在呼叫動態函數時,『動態連結器』根據需要,決定出正確的連結順序,然後對該程式與動態函數進行重定位的動作,再將控制權轉移到動態函數中。

ELF 檔案的載入過程,會因 CPU 的結構不同而有差異,因此,在 ELF 文件中這些與 CPU 有關的主題都被分離出來,由各家 CPU 廠商自行撰寫。舉例而言,動態函數的呼叫就是一個與 CPU 有關的主題,不同的 CPU實作方法會有所不同。

程式片段 7. IA32 處理器中的靜態函數呼叫與動態函數呼叫方式

C語言程式                    靜態函數呼叫            動態函數呼叫
extern int var; pushl var movl var@GOT(%ebx)
extern int func(int); call func pushl (%eax)
call func@PLT
int call_func(void) {
return func(var);
}

程式片段 8. IA32 處理器中的動態連結函數區 (Stub) 的程式

.PLT0:  pushl 4(%ebx)
Jmp *8(%ebx)
nop
nop
.PLT1: jmp *name1@GOT(%ebx)
pushl $offset1
jmp .PLT0@PC
.PLT2 jmp *name2@GOT(%ebx)
pushl $offset2
jmp .PLT0@PC

程式片段 9 顯示了 ELF目的檔的動態連結記錄 Elf32_Dyn,這些記錄會被儲存在一個名為 _DYNAMIC[] 的陣列中,以便讓動態連結器使用。

程式片段 9. 目的檔ELF的動態連結 (重定位) 記錄

typedef struct {
Elf32_Sword d_tag; // 標記
union {
Elf32_Word d_val; // 值 (用途很多樣)
Elf32_Addr d_ptr; // 指標 (程式的虛擬位址)
} d_un;
} Elf32_Dyn; extern Elf32_Dyn _DYNAMIC[]; // 動態連結陣列

有關 ELF 目的檔的進一步資訊,有興趣的讀者可以參考規格書 System V Application Binary Interface 中的第四章與第五章 。

說明與參考文獻

  1. 讀者可於下列網址下載到System V 的二進位應用介面規格書『System V Application Binary Interface』, http://www.caldera.com/developers/devspecs/gabi41.pdf ,其中的第四章 (Chapter 4) Object Files 即是 ELF 檔案格式的規格書。
  2. 在維基百科的 ELF 主題中,列有 ELF 的詳細相關資訊,包含 System V 的規格書與各家處理器廠商的補充文件,讀者可參閱http://en.wikipedia.org/wiki/Executable_and_Linkable_Format,其中 IA32 處理器的補充文件位於http://www.caldera.com/developers/devspecs/abi386-4.pdf,而 ARM 的補充文件位於http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf
  3. 有關 Linux 的動態連結技術可以參考 Ulrich Drepper , How To Write Shared Libraries, Red Hat, Inc., August 20, 2006, 其網址為http://people.redhat.com/drepper/dsohowto.pdf
  4. Jollen’s Blog, Program Loading 觀念介紹, last update 2007/03/13, http://www.jollen.org/EmbeddedLinux/Program_Loading.html
  5. 動態連結器的英文名稱為Dynamic Linker,但在 ELF 文件中被稱為 Program Interpreter。
  6. 動態連結器 (Dynamic Linker ) 的ELF檔,在Linux 中通常被儲存的檔名為ld.so。
  7. a.out 執行文件格式 — http://www.cnscn.org/htm_data/56/0903/21669.html

Facebook

目的檔格式 (ELF)的更多相关文章

  1. 可执行文件格式elf和bin

    区别 常用的可执行文件包含两类:原始二进制文件(bin)和可加载执行的二进制文件,在linux中可加载执行的二进制文件为elf文件. BIN文件是直接的二进制文件,内部没有地址标记.bin文件内部数据 ...

  2. Linux可执行文件格式-ELF结构详解

    表1. ELF文件类型分类 ELF文件类型 说明 实例 Relocatable File 可重定位文件 未链接之前的ELF文件,可用于链接可执行文件或静态链接库 Linux下的".o&quo ...

  3. Linux ELF格式分析

    http://www.cnblogs.com/hzl6255/p/3312262.html ELF, Executable and Linking Format, 是一种用于可执行文件.目标文件.共享 ...

  4. ELF文件的格式和加载过程

    http://blog.csdn.net/lingfong_cool/article/details/7832896 (一) ELF 文件的格式       ELF 文件类型 (1) 可重定位文件(  ...

  5. 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析OpenHarmony源码 | v51.04

    百篇博客系列篇.本篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | ...

  6. 【VC++技术杂谈007】使用GDI+进行图片格式转换

    本文主要介绍如何使用GDI+对图片进行格式转换,可以转换的图片格式为bmp.jpg.png. 1.加载GDI+库 GDI+是GDI图形库的一个增强版本,提供了一系列Visual C++ API.为了使 ...

  7. linux实践之ELF文件分析

    linux实践之ELF文件分析 下面开始elf文件的分析. 我们首先编写一个简单的C代码. 编译链接生成可执行文件. 首先,查看scn15elf.o文件的详细信息. 以16进制形式查看scn15elf ...

  8. (转载)OC学习篇之---类目的概念和使用

    上一篇文章介绍了OC中的@class关键字的使用,这一篇我们介绍一下,OC中的一个特有的亮点:类目 首先我们来看一下场景,如果我们现在想对一个类进行功能的扩充,我们该怎么做? 对于面向对象编程的话,首 ...

  9. OC学习篇之---类目的概念和使用

    上一篇文章介绍了OC中的@class关键字的使用http://blog.csdn.net/jiangwei0910410003/article/details/41774747,这一篇我们介绍一下,O ...

随机推荐

  1. Wireshark抓取TCP包分析

    介绍 本篇文章是使用wireshrak对某个https请求的tcp包进行分析. 目的 通过抓包实际分析了解tcp包. 准备工作 在我自己机子上安装的是wireshark2.2.6版本,随机查找了某个T ...

  2. mysql 模拟一个自增序列

    文章出处:https://sdu0rj.axshare.com/%E4%BA%8C%E7%BA%A7%E5%AE%A2%E6%88%B7%E7%AE%A1%E7%90%86.html mysql没有像 ...

  3. fiddler中安装证书进行https协议的抓取

    Fiddler目前默认安装对http协议进行抓取但是对手机以及其他一些是https协议的通讯抓取需要配置. 电脑Fiddler设置: 1.首页我们要在fiddler中找到菜单栏的Tools > ...

  4. my___sublime Text配置

    sublime text 备份 插件下载 http://www.cnblogs.com/457220157-FTD/p/5546545.html https://www.jianshu.com/p/3 ...

  5. Express中404页面

    404页面是各大网站都需要的. 在做express项目时,应当注意,404页面在app.js中的判断是在最后的,使用这个中间件时,不需要next(),因为它是最后一个了. 它之前一般是router. ...

  6. Quartz使用(3) - Quartz核心接口Trigger

    Trigger最常用的有两种SimpleTrigger和CronTrigger,首先介绍Trigger的一些基础的信息,然后会详细描述这两种Trigger. 1. 通用Trigger属性 quartz ...

  7. 自定义Qt组件-通讯模块(P3)

    1.   半双工模式实时检测串口 ComHalfDuplex类是为了解决上位机发送控制指令和下位机发送数据会在半双工RS485总线中产生冲突引起乱码而引入的(v0.010版本引入). 解决冲突的原理主 ...

  8. java学习第十二天

    1:Scanner的使用(了解) (1)在JDK5以后出现的用于键盘录入数据的类. (2)构造方法: A:讲解了System.in这个东西. 它其实是标准的输入流,对应于键盘录入 B:构造方法 Inp ...

  9. node搭环境(三)之安装vue-devtools

    前面已经安装了bower gulp,马上要学vue了,今天安装vue及调试神器vue-devtools 安装步骤: 1.在GitHub上输入 github.com/vuejs/vue-devtool后 ...

  10. IIS报错试图加载格式不正确的程序集解决办法 64位WinServer系统兼容32位程序集

    问题描述:     将网站/WCF发布到IIS后运行报这个错. 原因:     64位系统和32系统不匹配导致. 解决办法:     方法一.注册32位IIS(必须卸载64位IIS)         ...