其實是想搞嵌入式的,但是總是要補補這裏的知識補點那裏的知識
1.ELF的分類
現在PC平臺流行的可執行文件格式(Executable)主要是Windows下的PE和Linux的ELF,他們都是COFF格式的變種
ELF文件標準裏面把系統中採用ELF格式的文件歸爲以下4類
ELF文件類型 | 說明 | 實例 |
---|---|---|
可重定位文件(Relocatable) | 這類文件包含了代碼和數據,可以用來鏈接成可執行文件或共享目標文件,靜態鏈接庫也屬於這一類 | Linux的.o Windows的.obj |
可執行文件(Executable) | 這類文件包含了可以直接執行的程序,它的代表就是ELF可執行文件,一般沒有擴展名 | 比如/bin/bash文件 Windows的.exe |
共享目標文件(Shared Object File) | 這種文件包含了代碼和數據 ,可以在以下兩種情況下使用。一種是鏈接器可以使用這種文件跟其他的可重定位文件和共享目標文件鏈接,產生新的目標文件。第二種是動態連接器可以將幾個這種共享目標文件與可執行文件結合,作爲進程映像的一部分 | Linux的.so windows的DLL |
核心轉儲文件(Core dump file) | 當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些其他信息轉儲到核心轉儲文件 | linux下的Core dump |
在linux下可以使用file命令來查看
2.ELF 文件的總體結構大概是這樣的:
ELF 文件頭 位於最前端,它包含了整個文件的基本屬性,如文件版本,目標機器型號,程序入口等等。
.text 爲代碼段,也是反彙編處理的部分,他們是以機器碼的形式存儲,沒有反彙編的過程基本不會有人讀懂這些二進制代碼的。
.data 數據段,保存的那些已經初始化了的全局靜態變量和局部靜態變量。
.bss 段, 存放的是未初始化的全局變量和局部靜態變量,這個很容易理解,因爲在未初始化的情況下,我們單獨用一個段來保存,可以不在一開始就分配空間,而是在最終連接成可執行文件的時候,再在.bss 段分配空間。
其他段, 還有一些可選的段,比如.rodata 表示這裏存儲只讀數據, .debug 表示調試信息等等,具體遇到可以查看相關文檔。
自定義段,這一塊是爲了實現用戶特殊功能而存在的段,方便擴展,比如我們使用全局變量或者函數之前加上 attribute(section(‘name’)) 就可以吧變量或者函數放到以name 作爲段名的段中。
段表,Section Header Table ,是一個重要的部分,它描述了ELF 文件包含的所有段的信息,比如每個段的段名,段長度,在文件中的偏移,讀寫權限和一些段的其他屬性。
ELF目標文件格式的最前端是ELF文件頭,包含了描述整個文件的基本屬性,比如ELF文件版本、目標機器型號、程序入口地址。緊接着的就是ELF文件各個段。其中ELF文件中與段有關的重要結構就是段表。段表描述的是ELF文件包含的所有段的信息,比如每個段的段名、段的長度、在文件中的偏移、讀寫權限以及段的其他屬性。
分段的好處??
1.其實數據和指令分段的好處有很多,一方面是當程序被裝載後,數據和指令被分別映射到兩個虛存。由於數據區域對於進程來說是可讀寫的,而指令區域對於進程來說是隻讀的,所以這兩個虛存區域的權限可以被分別設置成可讀寫和只讀。這樣可以防止程序的指令被有意或無意的改寫。
2.對於現代cpu來說,它們有着極爲強大的緩存體系,現代CPU的緩存一般都被設計成數據緩存和指令緩存分離,所以程序的指令和數據被分開存放對CPU的緩存命中率的提高有好處。
3.當系統中運行多個該程序的副本的時候,由於指令都是一樣的,所以內存中只要保存一份該程序的指令。比如說很多程序的帶的圖標、文本、資源都可以共享。在現代操作系統中,尤其是動態鏈接的系統中,節省了大量的內存。
2.1文件頭
以64位版本的文件頭結構Elf64_Ehdr爲例
85 typedef struct
86 {
87 unsigned char e_ident[EI_NIDENT]; /* 有6個值,分別代表不同的含義*/
88 Elf64_Half e_type; /* ELF文件類型,REL */
89 Elf64_Half e_machine; /* cpu平臺*/
90 Elf64_Word e_version; /* ELF版本號,一般爲1 */
91 Elf64_Addr e_entry; /*入口地址,規定ELF程序的入口虛擬地址,操作系統在加載完該程序後從這個地址開始執行進程的指令。可重定位文件一般沒有入口地址,則這個值爲0*/
92 Elf64_Off e_phoff; /* Program header table file offset */
93 Elf64_Off e_shoff; /* 段表在文件中的偏移,從下一個字節開始 */
94 Elf64_Word e_flags; /*ELF標誌位,用來標識一些ELF文件平臺相關屬性,相關常量的格式一般爲EF_machine_flag*/
95 Elf64_Half e_ehsize; /* ELF文件頭大小 */
96 Elf64_Half e_phentsize; /* Program header table entry size */
97 Elf64_Half e_phnum; /* Program header table entry count */
98 Elf64_Half e_shentsize; /*段表描述符大小,等於sizeof(Elf64_Shdr) */
99 Elf64_Half e_shnum; /* 段表描述符數量,這個值等於ELF文件中擁有的段的數量 */
100 Elf64_Half e_shstrndx; /* 段表字符串所在的段在段表中的下標 */
101 } Elf64_Ehdr;
要注意的是e_ident數組包含了6個成員,Magic、類別、數據、版本、OS/ABI、ABI版本
Magic中的16個字節被ELF標準規定用來標識ELF文件的平臺屬性,比如ELF字長(32位/64位)、字節序、ELF文件版本
最開始的4個字節 是所有ELF文件都必須相同的標識碼,分別爲0x7f 0x45 0x4c 0x46,第一個字節對應ASCII字符裏面的DEL控制符、後面3個字節 是ELF這三個字母的ASCII碼,這4個字節被稱爲魔數。第5個字節 標識ELF的文件類,0x01表示32位,0x02表示64位,第6位, 規定ELF文件是大端的還是小端的。第7個字節 規定了ELF文件的主版本號一般是1
2.2 段表
段表就是保存段的一些基本屬性,描述了段的段名、段的長度、在文件中的偏移、讀寫權限以及其他屬性。ELF文件的段結構是由段表決定的,編譯器、鏈接器和裝載器都是依靠段表來定位和訪問各個段的屬性的。段表在文件中的位置由ELF文件頭的e_shoff成員決定。
下面是段描述符結構
287 typedef struct
288 {
289 Elf64_Word sh_name; /* 段名*/
290 Elf64_Word sh_type; /* 段類型 */
291 Elf64_Xword sh_flags; /* 段標誌位*/
292 Elf64_Addr sh_addr; /* 段虛擬地址 */
293 Elf64_Off sh_offset; /* 段偏移 */
294 Elf64_Xword sh_size; /* 段的長度 */
295 Elf64_Word sh_link; /* 段的鏈接信息 */
296 Elf64_Word sh_info; /* 段的鏈接信息*/
297 Elf64_Xword sh_addralign; /* 段地址對齊 */
298 Elf64_Xword sh_entsize; /* 項長度 */
299 } Elf64_Shdr;
後記:段的名字對於編譯器,鏈接器來說是有意義的,但是對於操作系統來說並沒有實質的意義,對於操作系統來說,一個段如何處理取決於它的屬性和權限,也就是段的類型和段的標誌位這兩個成員決定。
test.o段表結構
將test的所有段的位置和長度信息分析如下:空白處代表字節對齊,因爲Section Table的長度爲0x340,也就是832個字節,它包含了13個段描述符,每個段描述符爲64個字節。
整個文件的結尾是Section Table,總長度爲0x730,也就是1840個字節,也剛好是test.o的文件長度。
通過驗證也確實是1840字節。
2.3 相關參數介紹
在段的結構體中:
段的類型(sh_type)
段的名字只是在鏈接和編譯過程中有意義,但是不能真正地表示段的類型。對於編譯器和鏈接器來說,主要決定段的屬性的是段的類型(sh_type)和段的標誌位(sh_flags),列舉如下表
段的標誌位(sh_flag)
段的標誌位表示該段在進程虛擬地址空間中的屬性,比如是否可寫、是否可執行
對於系統中的保留段,下表也列舉了段的類型和段的標誌位屬性,可以通過readelf -S test.o命令得到的屬性進行對照
段的鏈接信息(sh_link、sh_info)
如果段的類型是與鏈接相關的(不論是動態鏈接還是靜態鏈接),比如重定位表、符號表等,那麼sh_link和sh_info這兩個成員所包含的意義如下表所示,對於其他類型的段,這兩個成員,沒有意義
3.重定位表
這個也就是test.o中有一個叫做.rela.text的段和.rela.eh_frame的段,他們的類型都是SHT_RELA(其他的段類型含義可以到 /usr/include/elf.h 中查看),意思是帶有添加項的重定位項,也就是說它是一個重定位表。也就是鏈接器在處理目標文件的時候,要對一些目標文件中某些部位進行重定位。關於重定位可以看hello.程序的編譯過程
關於這裏我想說的是關於eh_frame段和rela.eh_frame段查了很多資料目前還不知道存了什麼,是什麼意思。
一個重定位表同時也是一個ELF段,那麼這個段的類型就是(sh_type)就是SHT_RELA類型,它的sh_link表示符號表的下表,它的sh_info表示它作用於哪個段。比如.rela.text作用於.text段,而.text段的下標爲1,那麼.rela.text的sh_info爲1。
ELF和bin的區別
Gcc 編譯出來的是ELF文件。通常gcc –o test test.c,生成的test文件就是ELF格式的,在linuxshell下輸入 ./test就可以執行。
Bin 文件是經過壓縮的可執行文件,去掉ELF格式的東西。是直接的內存映像的表示。在系統沒有加載操作系統的時候可以執行。
elf(executable and link format)文件裏面包含了符號表,彙編等。
BIN文件是將elf文件中的代碼段,數據段,還有一些自定義的段抽取出來做成的一個內存的鏡像。
在Embedded中,如果上電開始運行,沒有OS系統,如果將ELF格式的文件燒寫進去,包含一些ELF格式的東西,arm運行碰到這些指令,就會導致失敗,如果用arm-softfloat-linux-gnu-objcopy生成純粹的彙編 bin文件,程序就可以一步一步運行。