ELF文件格式


目標代碼(Object Code)指編譯器和匯編器處理源代碼后所生成的機器語言目標代碼
目標文件(Object File)指包含目標代碼的文件
最早的目標文件格式是自有格式,非標准的。標准的幾種目標文件格式:
– DOS操作系統(最簡單) :COM格式,文件中僅包含代碼和數據,且被加載到固定位置
– System V UNIX早期版本:COFF格式,文件中不僅包含代碼和數據,還包含重定位信息、調試信息、符號表等其他信息,由一組嚴格定義的數據結構序列組成
– Windows:PE格式(COFF的變種),稱為可移植可執行(Portable Executable,簡稱PE)
– Linux等類UNIX:ELF格式(COFF的變種),稱為可執行可鏈接(Executable and Linkable Format,簡稱ELF)
 
ELF文件格式提供了兩種視圖,分別是:
鏈接視圖(被鏈接):可重定位目標文件 (Relocatable object files) 
執行視圖(被執行):可執行目標文件(Executable object files)
(1)鏈接視圖——可重定位目標文件
擴展名為.o(相當於Windows中的 .obj文件),包含代碼、數據、定位信息(指出哪些符號引用處需要重定位)等
可被鏈接合並,生成可執行文件或共享目標文件(.so文件);若干個可重定位目標文件組成靜態鏈接庫文件(.a文件)。
節(section)是ELF 文件中具有相同特征的最小可處理單位:
  • ELF 頭:
分32位系統對應結構和64位系統對應結構(32位版本、64位版本)。32位系統中:
ELF頭位於ELF文件開始,共52字節,包括16字節標識信息、文件類型 (.o, exec, .so)、機器類型(如 IA-32)、節頭表的偏移、節頭表的表項大小以及表項個數等
#define EI_NIDENT 16
typedef struct{
    unsigned char e_ident[EI_NIDENT]; //16個字節的數組,定義了一些標識信息。最開頭4B稱為魔數,標識目標文件的類型或者格式。加載或讀取文件時,可用魔數確認文件類型是否正確。再后面12字節,標識32位還是64位、大端還是小端、ELF頭的版本號。
    Elf32_Half e_type;     //目標文件類型,是可重定位文件、可執行文件、共享庫文件還是其它
    Elf32_Half e_machine;  //機器結構類型,是IA32、AMD64、SPARC V9還是其它
    Elf32_Word e_version; //目標文件版本
    Elf32_Addr e_entry;  //程序執行的入口地址,可重定位目標文件應該為0,且沒有程序頭表(段頭表)等執行視圖
    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;  //字符串表在節頭表中的索引,即節頭表中第多少項是字符串表(String Table)。
}Elf32_Ehdr;
ELF頭是二進制存儲的,需要使用命令查看內容
$ readelf -h main.o
Magic:   7f 45 4c 4601 01 01 00 00 00 00 00 00 00 00 00    //ELF文件的魔數是7f 45 4c 46。
Class:    ELF32   
Data:     2's complement, little endian    //負數用2進制補碼形式表示、小端方式存放
Version: 1 (current)
OS/ABI:  UNIX - System V  
ABI Version:   0
Type:    REL (Relocatable file) 
Machine:   Intel 80386 
Version:    0x1
Entry point address:  0x0     //可重定位文件程序執行入口地址為0
Start of program headers:  0 (bytes into file)    //沒有程序頭表
Start of section headers:   516 (bytes into file) //節頭表起始位置
Flags:    0x0
Size of this header:   52 (bytes)          
Size of section headers:    40 (bytes)  //節頭表每項40B
Number of section headers:  15       //節頭表共15項
Section header string table index: 12        //.strtab在節頭表中的索引
  • .text 節
編譯后的代碼部分
  • .rodata節
只讀數據,如 printf 格式串、switch跳轉表等
  • .data 節
已初始化全局變量和靜態成員變量,存放具體的初始值,需要占磁盤空間。
區分初始化和非初始化是為了空間效率
  • .bss 節
未初始化全局變量和局部靜態變量,默認初始值為0,.bss節中無需存放初始值,只要說明.bss中的每個變量將來在執行時占用幾個字節即可,因此,.bss節實際上不占用磁盤空間,
通過專門的節頭表(Section header table)來說明應該為.bss節預留多大的空間
  • .symtab 節
存放函數和全局變量的 (符號表)信息 ,它不包括局部變量
  • .rel.text 節
.text節的重定位信息,用於重新修改代碼段的指令中的地址信息
  • .rel.data 節
.data節的重定位信息,用於對被模塊使用或定義的全局變量進行重定位的信息
  • .debug 節
調試用符號表
  • strtab 節
字符串表,包括.symtab節和.debug節中的符號以及節頭表中的節名。
字符串表就是以null結尾的字符串序列。
  • Section header table(節頭表)
節頭表的起始位置、表項數目、長度在ELF頭中給出。
以下是32位系統對應的節頭表數據結構(每個表項占40B),說明了每個節的節名、在文件中的偏移、大小、訪問屬性、對齊方式等
typedef struct {
    Elf32_Word sh_name;   //節名字符串在.strtab節(字符串表)中的偏移
    Elf32_Word sh_type;   //節類型:無效/代碼或數據/符號/字符串/...
    Elf32_Word sh_flags;  //節標志:該節在虛擬空間中的訪問屬性
    Elf32_Addr sh_addr;   //虛擬地址:若可被加載,則對應虛擬地址
    Elf32_Off  sh_offset; //在文件中的偏移地址,對.bss節而言則無意義
    Elf32_Word sh_size;   //節在文件中所占的長度
    Elf32_Word sh_link;   //sh_link和sh_info用於與鏈接相關的節(如 .rel.text節、.rel.data節、.symtab節等)
    Elf32_Word sh_info;
    Elf32_Word sh_addralign; //節的對齊要求
    Elf32_Word sh_entsize;   //節中每個表項的長度,0表示無固定長度表項
} Elf32_Shdr;
使用readelf命令命令查看節頭表內容
$ readelf -S test.o
可重定位目標文件中,每個可裝入節的起始地址總是0
.bss節應占00000c大小,但只有裝入內存時才會分配。
這11個節在裝入內存時,只有.text節、.data節、.bss節和.rodata節會分配存儲空間
 
(2)執行視圖——可執行目標文件
與可重定位文件的不同
  1. ELF頭中字段e_entry給出執行程序時第一條指令的地址,而在可重定位文件中,此字段為0
  2. 多一個程序頭表,也稱段頭表(segment header table) ,是一個結構數組
  3. 多一個.init節,用於定義_init函數,該函數用來進行可執行目標文件開始執行時的初始化工作
  4. 少兩個.rel節(無需重定位)
使用readelf命令查看ELF頭的內容:
$ readelf -h main.o
Magic:   7f 45 4c 4601 01 01 00 00 00 00 00 00 00 00 00    
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:  x8048580
Start of program headers:  52 (bytes into file)   //程序頭表在ELF頭后
Start of section headers:  3232 (bytes into file)
Flags:    0x0
Size of this header:   52 (bytes)  
Size of program headers:    32 (bytes)   //程序頭表每項32B
Number of program headers:   8          //程序頭表共8項
Size of section headers:    40 (bytes)       
Number of section headers:  29       
Section header string table index: 26        //.strtab在節頭表中的索引
裝入內存時,ELF頭、程序頭表、.init節、.rodata節會被裝入只讀代碼段
.data節和.bss節會被裝入讀寫數據段
描述可執行文件中的節與虛擬空間中的存儲段之間的映射關系。
一個表項32B,說明虛擬地址空間中一個連續的段或一個特殊的節。
以下是32位系統對應的程序頭表數據結構:
typedef struct {
    Elf32_Word p_type;   //此數組元素描述的段的類型,或者如何解釋此數組元素的信息。
    Elf32_Off p_offset;  //此成員給出從文件頭到該段第一個字節的偏移
    Elf32_Addr p_vaddr;  //此成員給出段的第一個字節將被放到內存中的虛擬地址
    Elf32_Addr p_paddr;  //此成員僅用於與物理地址相關的系統中。System V忽略所有應用程序的物理地址信息。
    Elf32_Word p_filesz; //此成員給出段在文件映像中所占的字節數。可以為0。
    Elf32_Word p_memsz;  //此成員給出段在內存映像中占用的字節數。可以為0。
    Elf32_Word p_flags;  //此成員給出與段相關的標志。
    Elf32_Word p_align;  //此成員給出段在文件中和內存中如何對齊。
} Elf32_phdr;
使用readelf命令某可執行目標文件的程序頭表
$ readelf –l main
程序頭表信息有8個表項,其中兩個為可裝入段(即Type=LOAD):
第一可裝入段:第0x00000~0x004d3的長度為0x4d4字節的ELF頭、程序頭表、.init、.text和.rodata節,映射到虛擬地址0x8048000開始長度為0x4d4字節的區域 ,按0x1000=2^12=4KB對齊,具有只讀/執行權限(Flg=RE),是只讀代碼段。
第二可裝入段:第0x00f0c~0x01014的長度為0x108字節的.data節和磁盤中不占存儲空間的.bss節,映射到虛擬地址0x8049f0c開始長度為0x110字節的存儲區域,在0x110=272B存儲區中,前0x108=264B用.data節內容初始化,后面272-264=8B對應.bss節,初始化為0 ,按0x1000=4KB對齊,具有可讀可寫權限(Flg=RW),是可讀寫數據段。
由此看出.bss節在文件中不占用磁盤空間,但在存儲器中需要給它分配相應大小的空間。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM