ELF 文件、鏡像(Image)文件、可執行文件、對象文件詳解
作者:ZC·Shou
https://itexp.blog.csdn.net/article/details/100048461
ELF 文件規範
ELF(Executable and Linking Format)是一個二進制文件規範。用於定義不同類型的對象文件(Object files)中都放了什麼東西、以及都以什麼樣的格式去放這些東西。
現在流行的二進制可執行文件格式 (Executable File Format),主要是 Windows 下的 PE(Portable Executable)和 Linux 的 ELF(Executable and Linking Format)可執行和鏈接格式)。他們都是 COFF(Common Object File Format)的變種。ARM 體系中採用的也是 ELF 文件格式。
COFF 是在 Unix System V Release 3 時由 UNIX 系統實驗室(UNIX System Laboratories, USL)首先提出並且使用的文件規範,後來微軟公司基於 COFF 格式,制定了 PE 格式標準,並將其用於當時的 Windows NT 系統。在 System V Release 4 時,UNIX 系統實驗室在 COFF 的基礎上,開發和發佈了 ELF 格式,作爲應用程序二進制接口 (Application Binary Interface,ABI)。
此後,工具接口標準委員會(Tool Interface Standard Committee,TISC)選擇了正在發展中的 ELF 標準作爲工作在 32 位 INTEL 體系上不同操作系統之間可移植的二進制文件格式。可以找到詳細的標準文檔 [1]。如下圖:
TISC 共出過兩個版本(v1.1 和 v1.2)的標準文檔。兩個版本內容上差不多,但 v1.2 版本重新組織了原本在 v1.1 版本中的內容。可讀性更高。兩個版本的目錄如下所示:
由於 TISC 的 v1.2 比較老舊,且後續沒有再更新,尤其是在 64 位出現之後,原來的 ELF v1.2 已經不再試用,因此,System V 對 ELF v1.2 進行了擴展,這個擴展就是 System V Application Binary Interface AMD64 Architecture Processor Supplement,實際了 Unix 系統與 類 Unix 系統都使用 System V 擴展的這個版本。
在 ELF 文件規範中,把系統中採用 ELF 格式的文件(規範中稱爲對象文件(Object File))歸類爲以下三種:
-
可重定位文件(Relocatable File ): 這類文件包含代碼和數據,可用來連接成可執行文件或共享對象文件(Object File),靜態鏈接庫歸爲此類,對應於 Linux 中的 .o ;Windows 的 .obj.
-
可執行文件(Executable File ): 這類文件包含了可以直接執行的程序,它的代表就是 ELF 可執行文件。Linux 下,他們一般沒有擴展名,比如 /bin/bash;Windows 下的 .exe
-
共享對象文件(Object File)(Shared Object File ): 這種文件包含代碼和數據,鏈接器可以使用這種文件跟其他可重定位文件的共享對象文件(Object File)鏈接,產生新的對象文件(Object File)。對應於 Linux 中的 .so,Windows 中的 DLL
另外是動態鏈接器可以將幾個這種共享對象文件(Object File)與可執行文件結合,作爲進程鏡像文件來運行。
在 Linux 系統中,還有一類文件,被稱爲核心轉儲文件(Core Dump File) ,當進程意外終止,系統可以將該進程地址空間的內容及終止時的一些信息轉存到核心轉儲文件。 對應 Linux 下的 core dump。
對象文件參與程序鏈接(構建程序)和程序執行(運行程序)。 爲了方便和高效,對象文件(Object File)格式提供文件內容的並行視圖,反映了這些活動的不同需求。 下圖顯示了對象文件(Object File)的組織。
其中,各部分的含義都是規範定義好的!
數據表示法
對象文件(Object File)格式支持具有 8 位字節和 32 位體系結構的各種處理器。 然而,它旨在可擴展到更大(或更小)的體系結構。 因此,對象文件(Object File)用一種與機器無關的格式表示一些控制數據,從而可以識別對象文件(Object File)並以通用方式解釋它們的內容。 目標處理器中的剩餘數據使用目標處理器的編碼,而不管創建文件的機器如何。出於可移植性的原因,ELF 不使用位字段。
對象文件格式定義的所有數據結構都遵循相關類的自然大小和對齊準則。如果需要,數據結構包含顯式填充,以確保 4 字節對象的 4 字節對齊,強制結構大小爲 4 的倍數,以此類推。數據從文件開始也有適當的對齊。因此,例如,包含 Elf32 Addr 成員的結構將在文件中的 4 字節邊界上對齊。
字符表示法
ELF 中對於符號的字符編碼也有一定的要求。當 ELF 接口文檔提到字符常量時,例如’/‘或’\ n’,它們的數值應遵循 7 位 ASCII 準則。 對於先前的字符常量,單字節值分別爲 47 和 10。
根據字符編碼,在 0 到 127 範圍之外的字符值可以佔用一個或多個字節。 應用程序可以根據需要使用不同語言的不同字符集擴展來控制自己的字符集。 儘管 TIS - 一致性 不限制字符集,但它們通常應遵循一些簡單的指導原則:
-
0 到 127 之間的字符值應對應於 7 位 ASCII 代碼。 也就是說,編碼大於 127 的字符集應包含 7 位 ASCII 碼作爲子集。
-
值大於 127 的多字節字符編碼應僅包含值在 0 到 127 範圍之外的字節。也就是說,每個字符使用多個字節的字符集不應嵌入類似於 7 位 ASCII 字符的字節。 一個多字節,非 ASCII 字符。
-
多字節字符應該是自我識別的。 例如,這允許在任何一對多字節字符之間插入任何多字節字符,而不改變字符的解釋。
關於 ELF 文件規範這裏就不多做詳細介紹了,感興趣的可以去 Linux 基金會的官方網站下載規範來看看!
ARM ELF 文件格式
ARM 體系中,所有文件均採用的 ELF 文件格式。我們可以在 ARM 的官網找到 ARM 關於 ARM ELF 文件格式的說明文檔。後文參考部分的下載中是目前可以從 ARM 官網找到的所有和 ARM ELF 相關的 PDF 文檔。
目前,我們可以找到的 ARM ELF 相關的文檔主要有 4 個:《ARM ELF File Format》、《ELF for the ARM® Architecture》、《ARM ELF》以及 ARM 的鏈接器手冊。其中,《ARM ELF File Format》是比較早期的文檔,針對於 ARM SDT 時代的 ELF 文件,有點過時了;後者三個則是最新的介紹文檔,《ELF for the ARM® Architecture》 僅僅是對 ARM ELF 取值的一些特殊說明,是在讀者先了解 ELF 文件規範的基礎上進行的說明。
ARM 中的各種源文件(包括彙編文件,C 語言程序及 C++ 程序等)經過 ARM 編譯器編譯後生成 ELF 格式的對象文件(Object File)(.o 文件)。這些對象文件(Object File)和相應的 C/C++ 運行時用到的庫經過 ARM 連接器處理後,生成 ELF 格式的鏡像文件(image),這種 ELF 格式的映像文件是一種可執行文件,可被寫入嵌入式設備的 ROM 中。
在 ARM 體系中,所有的二進制文件均被稱爲對象文件。其中,鏈接器最終生成的 ELF 格式的可執行文件又被稱爲鏡像文件(Image file)。ARM ELF 鏡像文件或者對象文件由輸入節(Input Sections)、輸出節(Output Sections)、域(Regions) 和 段(Segments) 組成,每個鏈接階段都有不同的鏡像視圖。如下圖所示:
-
ELF object file view (linker input): ELF 對象文件視圖由輸入節組成。 ELF 對象文件可以是:
-
一個可重定位文件,包含適合與其他對象文件(Object File)鏈接的代碼和數據,以創建可執行文件或共享對象文件。
-
包含代碼和數據的共享對象文件。
-
Linker view: 連接器視圖針對程序地址空間會有兩個視圖。並且這兩個視圖在存在重疊,位置無關和可重定位的程序片段(代碼或數據)時變得不同:
如果片段與位置無關或可重定位,則其執行地址在執行期間可能會有所不同。
-
程序片段的加載地址是鏈接器期望外部代理(例如程序加載器,動態鏈接器或調試器)從 ELF 文件複製片段的目標地址。 這可能不是片段執行的地址。
-
程序片段的執行地址是目標地址,其中鏈接器期望片段在參與程序的執行時駐留。
-
ELF image file view (linker output): ELF 鏡像文件視圖由程序段和輸出節組成:
-
RO section.
-
RW section.
-
XO section.
-
ZI section.
-
一個加載域對應於一個程序段。
-
一個執行域包含一個或多個以下輸出節:
-
一個或多個執行域組成一個加載域。
When describing a memory view:
The term root region means a region that has the same load and execution addresses. 術語根區域是指具有相同加載和執行地址的區域。
Load regions are equivalent to ELF segments. 加載區域等效於 ELF 段。
輸入節 Input section
一個輸入節就是是輸入對象文件中的一個獨立的部分。 它包含代碼,初始化數據,或着是描述未初始化或必須在鏡像文件執行前設置爲零的內存片段。 這些屬性由 RO,RW,XO 和 ZI 等屬性表示。 armlink
使用這些屬性將輸入節分組爲更大的構建塊,稱爲輸出節和域。
輸出節 Output section
一個輸出節就是一組輸入節的組合,它們具有相同的 RO,RW,XO 或 ZI 屬性,並且由鏈接器連續放置在存儲器中。 輸出節與組成它的輸入節具有相同的屬性。 在輸出節中,輸入節根據節放置規則進行排序。
域 Region
一個域最多包含四個輸出節,具體取決於內容和具有不同屬性的節的數量。 默認情況下,域中的輸出節根據其屬性進行排序。 首先是 XO 屬性的輸出節,然後是 RO 屬性的輸出節,再然後是 RW 屬性的輸出節,最後是 ZI 屬性的輸出節。 域通常會映射到物理存儲設備,例如 ROM,RAM 或外圍設備。 您可以使用分散加載文件來更改輸出節的順序。
程序段 Program segment
一個程序段對應於一個加載域,並且包含執行域。 程序段包含文本和數據等信息。
存在 XO( execute-only)節時的注意事項
您可以在同一執行域中混合 XO 和 非 XO 節。 但是,輸出的結果是一個 RO 節。
如果輸入文件具有一個或多個 XO 節,則鏈接器將生成單獨的 XO ELF 段。 在最終鏡像中,除非使用分散加載文件或
--xo-base
選項另有指定,否則 XO 段緊接在 RO 段之前。
鏡像的加載視圖和執行視圖
鏡像的域在加載時放置在系統存儲器映射中。 內存中域的位置可能會在執行期間發生變化。在執行鏡像之前,可能必須將鏡像的某些域移動到其執行地址並創建 ZI 輸出節。 例如,初始化的 RW 數據可能必須從其 ROM 中的加載地址複製到 RAM 中的執行地址。鏡像的內存映射具有以下不同視圖:
加載視圖 Load view
根據鏡像加載到內存中時所處的地址,即鏡像執行開始前的位置,描述每個鏡像的域和節。
執行視圖 Execution view
根據鏡像執行期間所在的地址描述每個鏡像的域和節。
下圖顯示了沒有(XO)節的鏡像的這些視圖:
下圖顯示了具有 XO 節的鏡像的加載和執行視圖:
Image entry points
鏡像中的入口點就是鏡像中的一個位置(地址),該位置(地址)會被加載到 PC 寄存器。 它是程序執行開始的位置。 雖然鏡像中可以有多個入口點,但在鏈接時只能指定一個入口點。並非每個 ELF 文件都必須有入口點。 不允許在單個 ELF 文件中存在多個入口點。
對於嵌入式 Cortex-M 核的程序,程序的執行是從復位向量所在的位置(地址)開始執行。復位向量會被加載到 PC 寄存器中,且復位向量的位置(地址)並不固定。 通常,復位向量指向 CMSIS Reset_Handler 函數。
有兩種不同類型的入口點:
-
初始化入口點:鏡像的初始入口點是存儲在 ELF 頭文件中的單個值。 對於那些需要由操作系統或引導加載程序加載到 RAM 中的程序,加載程序通過將控制轉移到鏡像中的初始入口點來啓動鏡像執行。一個鏡像只能有一個初始化入口點。初始入口點可以是 ENTRY 指令設置的入口點之一,但不是必需的。
-
ENTRY 指令指定的入口點:可以爲鏡像從多個可能的入口點中選擇其中一個。每個鏡像只能有一個入口點。您可以在彙編程序文件中使用 ENTRY 指令在對象中創建入口點。 在嵌入式系統中,該指令的典型用途是標記進入處理器異常向量(例如 RESET,IRQ 和 FIQ)的代碼。該指令使用 ENTRY 關鍵字標記輸出代碼部分,該關鍵字指示鏈接器在執行未使用的部分消除時不刪除該部分。對於 C/C++ 程序,C 庫 中的
__main
就是入口點。
如果加載程序要使用嵌入式的映像,則它必須在標頭中指定一個初始入口點。 使用--entry
命令行選項選擇入口點。
ARM ELF 文件實例
與標準的 ELF 文件相比,ARM ELF 的某些值比較特殊,下面以實際文件來說明一下每個部分。編譯工具如下圖:
編譯後,會在對應目錄下生成 .o 文件和 .axf 文件,爲了分析 ELF 文件,我們將使用 readelf 工具。在詳細解析之前,先用 Winhex 直接打開生成的 .o 文件,可以看到文件開頭有 ELF 字樣。表明它是一個 ELF 文件。如下:
注意:.o 不是 ARM 的可執行文件!axf 爲可執行文件。以下用兩種程序作對比。
一個簡單的可執行 ARM ELF 文件的概念佈局如下圖所示。請注意,文件中各部分的實際排序可能與下圖中的順序不同,因爲只有 ELF Header 在文件中具有固定位置。
注意,針對目前最新版本的 ARM ELF,上圖有點過時!
ELF Header
ELF Header 描述了體系結構和操作系統等基本信息,並指出 Section Header Table 和 Program Header Table 在文件中的什麼位置。實際文件中,只有 ELF Header 位置是絕對的,且只能在最開始,其他部分部分的位置順序並不一定完全相同。
Program Header Table 在彙編和鏈接過程中沒有用到,所以在重定位文件中可以沒有;Section Header Table 中保存了所有 Section 的描述信息,Section Header Table 在加載過程中沒有用到,對於可執行文件,可以沒有該部分。當然,對於某些類型的文件來說,可以同時擁有 Program header table 和 Section Header Table,這樣 load 完後還可以重定位。(例如:shared objects)
ELF Header 可以使用如下數據結構表示:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; // Magic
Elf32_Half e_type; // Type
Elf32_Half e_machine; // Machine
Elf32_Word e_version; // Version
Elf32_Addr e_entry; // Entry point address
Elf32_Off e_phoff; // Start of program headers
Elf32_Off e_shoff; // Start of section headers
Elf32_Word e_flags; // Flags
Elf32_Half e_ehsize; // Size of this header
Elf32_Half e_phentsize; // Size of program headers
Elf32_Half e_phnum; // Number of program headers
Elf32_Half e_shentsize; // Size of section headers
Elf32_Half e_shnum; // Number of section headers
Elf32_Half e_shstrndx; // Section header string table index
} Elf32_Ehdr;
下面兩幅圖分別顯示了不同文件的 ELF Header。以上數據結構中的註釋,即對應於下圖中的各部分字段。
.o 文件 ELF Header 如下圖所示:
.axf 文件 ELF Header 如下圖所示:
下面對以上兩幅圖中的內容做一下詳細介紹:
-
第 1 行 ELF Header:這是
readelf
工具的顯示,實際文件中不存在這個符號!直接從 Magic 開始! -
第 2 行 Magic:用來指名該文件是一個 ELF 對象文件(Object File),對應於 Elf32_Ehdr 數據結構中的
unsigned char e_ident[EI_NIDENT];
,使用以下宏值進行索引:
`e_ident[EI_MAG0] ~ e_ident[EI_MAG3]`:包含了 ELF 文件的魔數,依次固定是 0x7f 和 ‘E’、‘L’、‘F’。
`e_ident[EI_CLASS]`:取值如下
ARM ELF 文件應包含 ELFCLASS32。
`e_ident[EI_DATA]`:
選擇將由執行環境中的默認數據順序控制。 在以 BE8 模式運行的 Architecture v6 處理器上,所有的指令均爲小端格式。 適合在此模式下操作的可執行文件將在 e_flags 字段中設置 EF_ARM_BE8。
`e_ident[EI_VERSION]`:指定 ELF 頭部的版本,當前必須爲 1。
`e_ident[7]~e_ident[15]`:是填充符,通常是 0
-
第 3 行 Class:該值就是
e_ident[EI_CLASS]
。 -
第 4 行 Data:該值就是
e_ident[EI_DATA]
。 -
第 5 行 Version:該值就是
e_ident[EI_VERSION]
-
第 6 行 OS/ABI:該值應該是
e_ident
的擴展部分。操作系統類型,ABI 是 Application Binary Interface 的縮寫。除非文件使用具有 OS 特定含義的標誌(例如,使用 SHN_LOOS 通過 SHN_HIOS 的段索引),否則該字段應爲零。 目前,該字段有一個特定於處理器的值,如下。
-
第 7 行 ABI Version: 該值應該是 e_ident 的擴展部分。版本號,當前爲 0 。
-
第 8 行 Type:表示該對象文件(Object File)類型(上圖中的類型省略了 ET_)。
目前沒有特定於 ARM 的對象文件類型。 `ET_LOPROC` 和 `ET_HIPROC` 之間的所有值都保留給本規範的未來版本。
- 第 9 行 Machine:機器平臺類型。ARM 架構爲 EM_ARM(上圖中的類型省略了 ET_)。
- 第 10 行 Version:當前對象文件(Object File)的版本號。
-
第 11 行 Entry point address:程序的虛擬地址入口點。在 ARM 中:
-
在可執行 ELF 文件中,e_entry 是鏡像唯一入口點的虛擬地址,如果鏡像沒有唯一入口點,則爲 0。
-
在可重定位 ELF 文件中,e_entry 是被
SHF_ENTRYSECT
所標記的段的入口點的偏移量,若沒有入口點,則爲 0。 -
Bit[0] = 1,表示 Thumb 指令;Bit[0:1] = 00,表示 ARM 指令;Bit[0:1] = 10,保留;
平臺標準可以指定可執行文件總是具有入口點,在這種情況下,e_entry 指定入口點,即使爲零。 -
第 12 行 Start of program headers:程序頭的起始地址,.o 文件沒有 Program Headers 。
-
第 13 行 Start of section headers:節頭的起始地址。上圖中的 486388 是十進制,即:表示節頭是從地址偏移 0x76BF4 處開始。
-
第 14 行 Flags:是一個與處理器相關聯的標誌。
注意:以上部分與 ARM 早期文檔是有區別的,很多值已經不同
-
第 15 行 Size of this header:ELF 文件頭的字節數。
-
第 16 行 Size of program headers:Program Headers 大小。.o 文件大小爲 0。
-
第 17 行 Number of program headers:Program Headers 的數量(可以有多個)。
-
第 18 行 Size of section headers:sections header 的大小
-
第 19 行 Number of section headers:sections header 的數量。
-
第 20 行 Section header string table index:節頭部表格中與節名稱字符串表相關的表項的索引。如果文件沒有節名稱字符串表,此參數可以爲
SHN_UNDEF
。
注意:實際文件中,每一部分的位置順序並不一定完全相同,只有 ELF Header 位置是絕對的,且只能在最開始。
Section Header(節頭)
節頭表提供了對 ELF 文件中所有節的訪問。節中包含對象文件(Object File)中的所有信息,除了:ELF 頭部、程序頭部表格、節頭部 表格。節滿足以下條件:
-
對象文件(Object File)中的每個節都有對應的節頭部描述它,反過來,有節頭部不意 味着有節。
-
每個節佔用文件中一個連續字節域(這個區域可能長度爲 0)。
-
文件中的節不能重疊,不允許一個字節存在於兩個節中的情況發生。
-
對象文件(Object File)中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何 頭部和節,其內容未指定。
ELF 頭部中,e_shoff
成員給出從文件頭到節頭部表格的偏移字節數;e_shnum
給出表格中條目數目;e_shentsize
給出每個項目的字節數。從這些信息中可以確切地定位節的具體位置、長度。節頭部表格中比較特殊的幾個下標如下:
介於 SHN_LORESERVE
和 SHN_HIRESERVE
之間的表項不會出現在節頭部表中。
.o 文件 Section Header(部分)
.axf 文件 Section Header
上圖中的表頭可以用如下數據結構描述(對應關係見註釋):
typedef struct {
Elf32_Word sh_name; // name
Elf32_Word sh_type; // Type
Elf32_Word sh_flags; // Flg
Elf32_Addr sh_addr; // Addr
Elf32_Off sh_offset; // Off
Elf32_Word sh_size; // Size
Elf32_Word sh_link; // Lk
Elf32_Word sh_info; // Inf
Elf32_Word sh_addralign; // Al
Elf32_Word sh_entsize; // ES
} Elf32_Shdr;
-
sh_name:給出節名稱。是節頭部字符串表節(Section Header String Table Section)的索引。名字是一個 NULL 結尾的字符串。ELF 文件規定一些標準節的名字,例如
.text、.data、.bss
。此外,如上圖中,許多節名字都是 ARM 自己擴展的。 -
sh_type:爲節的內容和語義進行分類。ARM ELF 只使用了其中的一部分。參見下表(部分)。
除了以上標準節類型外,ARM 架構下,還有以下特殊的類型:
- sh_flags:字段定義了一個節中包含的內容是否可以修改、是否可以執行等信息。如果一個標誌比特位被設置,則該位取值爲 1。未定義的各位都設置爲 0。
ARM 中的特殊取值如下:
-
sh_addr:如果節將出現在進程的內存鏡像中,此成員給出節的第一個字節應處的位置。否則,此字段爲 0。
-
sh_link 和 sh_info:根據節類型的不同,sh_link 和 sh_info 的具體含義也有所不同。ARM 取值如下:
-
sh_addralign:節沒有最小對齊要求。 但是,包含 thumb 代碼的部分必須至少爲 16 位對齊,並且包含 ARM 代碼的部分必須至少爲 32 位對齊。具有
SHF_ALLOC
屬性的任何節必須滿足 sh_addralign >= 4。其他節可根據需要對齊。 例如,調試表通常沒有對齊要求。並且輸入到靜態鏈接器的數據段可以自然對齊。
平臺標準可能會限制他們可以保證的最大對齊(通常是頁面大小)。 -
sh_entsize:某些節中包含固定大小的項目,如符號表。對於這類節,此成員給出每個表項的長度字節數。如果節中並不包含固定長度表項的表格,此成員取值爲 0。
-
sh_size:此成員給出本節的長度(字節數)。除非節的類型是
SHT_NOBITS
,否則節佔用文件中的 sh_size 字節。類型爲SHT_NOBITS
的節長度可能非零,不過卻不佔用文件中的空間。 -
sh_offset:此成員的取值給出節的第一個字節與文件頭之間的偏移。不過,
SHT_NOBITS
類型的節不佔用文件的空間,因此其 sh_offset 成員給出的是其概念性的偏移。
注意:
-
保留給處理器體系結構的節名稱一般構成爲:處理器體系結構名稱簡寫 + 節名稱。且處理器名稱應該與 e_machine 中使用的名稱相同。例如:上圖最後的 .ARM.attributes
-
對象文件(Object File)中也可以包含多個名字相同的節。
-
上圖節名 ER_IROM1、RW_IRAM1、RW_IRAM 是由連接器的分散加載文件指定的名稱。可以根據需要自行修改。
ARM 節名稱是以下面列出的具有預定義含義的標準前綴之一開始的名稱,或者是包含美元($)字符的名稱。 在 ARM EABI 下沒有其他具有特殊意義的段名稱。
除了以上標準節外,ARM 架構下,還有以下特殊的節:
這裏需要注意一下 Debug Sections。Debug Sections 僅在調試時使用,稍微複雜一些。ARM 可執行 ELF 文件的調試節中包含多種類型的調試信息,ELF 可執行文件的使用者(如 armlink)可以通過檢查可執行文件的節表來區分這些種類型的調試信息。
ARM 系列的開發工具在不同的發展時期,採用的調試信息是有區別的,後來統一採用 DWARP。目前採用的應該是 3.0 版本。具體如下:
-
ASD debugging tables:
These provide backwards compatibility with ARM’s Symbolic Debugger. ASD debugging information is stored in a single Section in the executable named .asd. -
DWARP version 1.0
When DWARF 1.0 debugging information is included by the linker in the ELF executable, the file contains the following ELF Sections, each of which has a Section Header Table entry:
- DWARF version 2.0
When DWARF 2.0 debugging information is included by the linker in the ELF executable, the file contains the following ELF sections, each of which has a Section Header Table entry:
關於 DWARF 調試標準詳見:http://www.dwarfstd.org/。目前最新版本是 The DWARF Debugging Standard Version 5
Program Headers(程序頭)
可執行文件或者共享對象文件(Object File)的程序頭部是一個結構數組,每個結構描述了一個段或者系統準備程序執行所必需的其它信息。對象文件(Object File)的 "段" 包含一個或者多個 "節",也就是 "段內容(Segment Contents)"。程序頭部僅對於可執行文件和共享對象文件(Object File)有意義。
圖 7 Program Header
程序頭可以使用如下數據結構來表示(對應關係見註釋):
typedef struct {
Elf32_Word p_type; // Type
Elf32_Off p_offset; // Offset
Elf32_Addr p_vaddr; // VirtAddr
Elf32_Addr p_paddr; // PhyAddr
Elf32_Word p_filesz; // FileSiz
Elf32_Word p_memsz; // MemSiz
Elf32_Word p_flags; // Flg
Elf32_Word p_align; // Align
} Elf32_Phdr;
- p_type:這個成員告訴這個數組元素描述什麼樣的段,或者如何解釋數組元素的信息。 類型值及其含義如下圖所示。
-
p_offset:此成員給出從文件頭到該段第一個字節的偏移
-
p_vaddr:此成員給出段的第一個字節將被放到內存中的虛擬地址。
-
p_paddr:此成員僅用於與物理地址相關的系統中。因爲 System V 忽略所有應用程序的物理地址信息,此字段對與可執行文件和共享對象文件(Object File)而言,具體內容是未指定的。
-
p_filesz:此成員給出段在文件鏡像中所佔的字節數。可以爲 0。
-
p_memsz: 此成員給出段在內存鏡像中佔用的字節數。可以爲 0。
-
p_flags:此成員給出與段相關的標誌。
- p_align:可加載的進程段的 p_vaddr 和 p_offset 取值必須合適,相對於對頁面大小的取模而言。此成員給出段在文件中和內存中如何 對齊。數值 0 和 1 表示不需要對齊。否則 p_align 應該是個 正整數,並且是 2 的冪次數,p_vaddr 和 p_offset 對 p_align 取模後應該相等。
Symbol table(符號表)
一個對象文件的符號表保存了定位和重定位所在程序的符號定義和引用所需的信息。符號表以數組的下標進行索引。0 指定表中的第一個條目,並用作未定義的符號索引。ARM 結構中,符號表與標準的 ELF 文件沒有任何區別。
圖 12 .o 文件 Symbol table(部分)
在 C 語言中,符號表保存了程序實現或使用的所有全局變量和函數,如果程序引用一個自身代碼未定義的符號,則稱之爲未定義符號,這類引用必須在靜態鏈接期間用其他目標模塊或庫解決,或在加載時通過動態鏈接解決。
符號表可以使用以下數據結構表示:
typedef struct {
Elf32_Word st_name; // Name
Elf32_Addr st_value; // Value
Elf32_Word st_size; // Size
unsigned char st_info; //
unsigned char st_other;
Elf32_Half st_shndx; // Ndx
} Elf32_Sym;
-
st_name:該成員將對象文件(Object File)的符號字符串表中的索引保存在符號名稱的字符表示中
-
st_value:該成員給出相關聯的符號的值。 根據上下文,這可能是絕對值,地址等等; 不同對象文件(Object File)類型的符號表條目對 st_value 成員的解釋略有不同。
-
在可重定位文件中,st_value 保持其索引爲 SHN_COMMON 的符號的對齊約束。
-
在可重定位文件中,st_value 包含已定義符號的節偏移量。 也就是說,st_value 是 st_shndx 標識的部分開頭的偏移量。
-
在可執行文件和共享對象文件中,st_value 包含虛擬地址 1。 爲了使這些文件的符號對動態鏈接器更有用,段偏移(文件解釋)讓位於與段號無關的虛擬地址(存儲器解釋)。
-
st_size:許多符號具有相關尺寸。 例如,數據對象的大小是對象中包含的字節數。 如果符號沒有大小或未知的大小,該成員將保持 0。
-
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))
A symbol’s binding determines the linkage visibility and behavior.
In each symbol table, all symbols with STB_LOCAL binding precede the weak and global symbols. A symbol’s type provides a general classification for the associated entity. Figure 3-17, Symbol Types, ELF32_ST_TYPE
-
st_other:該成員目前只有 0,沒有定義。
-
st_shndx:每個符號表條目與某些部分有關 "定義"; 該成員保存相關部分標題表索引。 如上圖 3-7 和 3.3.1 節所述,一些段索引表示特殊含義。
The symbols in ELF object files convey specific information to the linker and loader. See section 4, ARM- and Thumb-Specific Definitions, for a description of the actual linking model used in the system.
-
SHN_ABS:The symbol has an absolute value that will not change because of relocation.
-
SHN_COMMON:The symbol labels a common block that has not yet been allocated. The symbol’s value gives alignment constraints, similar to a section’s sh_addralign member. That is, the link editor will allocate the storage for the symbol at an address that is a multiple of st_value. The symbol’s size tells how many bytes are required.
-
SHN_UNDEF:This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file’s references to the symbol will be linked to the actual definition.
As mentioned above, the symbol table entry for index 0 (STN_UNDEF) is reserved. It is shown in Figure 3-18. Figure 3-18, Symbol Table Entry: Index 0
String table(字符串表)
字符串表節包含以 NULL(ASCII 碼 0)結尾的字符序列,通常稱爲字符串。ELF 對象文件(Object File)通常使用字符串來表示符號和節名稱。對字符串的引用通常以字符串在字符 串表中的下標給出。ARM 結構中,字符串表與標準的 ELF 文件沒有任何區別。
axf 文件
axf 文件是 ARM 的調試文件,其格式符合上一節講的對象文件(Object File)格式(ELF)。其中除了包含了完整的 bin 文件外,還附加了其他的調試信息。在調試的時候,這些調試信息是不必下到 RAM 中去的,真正下到 RAM 中的信息僅僅是可執行代碼。下圖爲 axf 文件的頭部。
通過直接查看完整的 axf 文件可以看出,axf 中絕大多數都是和調試相關的內容。真正的 Bin 只是其中的一小部分。Bin 的結尾處在 axf 文件中也很容易找到,再次就不在贅述。
既然前面我們說了,axf 文件就是 ELF 文件格式,那麼我們可以使用 readelf 工具,具體查看一下 axf 文件。下圖是一個 axf 文件的節表
Bin 文件
bin 文件是 ARM 的可執行文件,是最純粹的二進制機器代碼。與 HEX 文件包括地址信息的不同,BIN 文件格式只包括了數據本身。在燒寫或下載 HEX 文件的時候,一般都不需要用戶指定地址,因爲 HEX 文件內部的信息已經包括了地址。而燒寫 BIN 文件的時候,用戶是一定需要指定地址信息的。
ARM 的 Bin 文件就是 axf 的精華部分(掐掉 ELF 頭,去掉 .symtab、.debug 和. symtab 區裏的信息)。下圖是筆者使用 Winhex 截取的 ARM 的 Bin 文件的開頭和結尾的示意圖。
hex 文件
首先,hex 文件最初由 Intel 提出。在 Intel HEX 文件中,每一行是一個 HEX 記錄,由十六進制數組成的機器碼或者數據常量,Intel HEX 文件經常被用於將程序或數據傳輸存儲到 ROM、EPROM,大多數編程器和模擬器使用 Intel HEX 文件。
hex 文件全部由可打印的 ASCII 字符組成。如下圖就是 ARM-MDK5.22 生成的一個 hex 文件(部分)
從上圖不難看出,hex 文件就是一個個的十六進制的字符串。實際上,一個 Intel HEX 文件可以包含任意多的十六進制記錄,每條記錄有五個域,每條記錄都由一個冒號 ":" 打頭。一個數據記錄以一個回車和一個換行結束。其格式如下:
:CCAAAARR[DD...]ZZ
其中:
-
CC:本條記錄中數據 (dd) 的字節數目
-
AAAA:本條記錄中的數據在存儲區中的起始地址
-
RR:記錄類型:
-
00 數據記錄 (data record)
-
01 文件結束記錄 (end record)
-
02 擴展段地址記錄 (paragraph record)
-
03 擴展線性地址記錄 (transfer address record)
-
DD… :數據域。表示一個字節的數據,一個記錄可能有多個數據字節,字節數目可以 查看 ll 域的說明
-
ZZ:效驗和域,表示記錄的效驗和,計算方法是將本條記錄冒號開始的所有字母對所表示的十六進制數字都加起來然後模除 256 得到的餘數最後求出餘數的補碼即是本效驗字節 cc。
舉例如下:
:10400000781A00203D420008034C0008D14B0008FC
-
10: 長度 16
-
4000: 起始地址
-
00: 表示數據記錄
-
78 ~ 08: 數據
-
FC:校驗和
參考
-
Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification Version 1.2
-
ARM® Compiler v5.06 for µVision® Version 5 armlink User Guide
-
ARM® Compiler v5.06 for µVision® Version 5 armcc User Guide
-
ARM ELF File Format ARM DUI 00101-A
-
ARM ELF Development Systems Business Unit Engineering Software Group
-
ELF for the ARM® Architecture
參考資料
[1] 標準文檔: https://refspecs.linuxbase.org/elf/elf.pdf
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/bLnaDOA0yindJQWUiRzSgw