目標代碼(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)執行視圖——可執行目標文件
與可重定位文件的不同
- ELF頭中字段e_entry給出執行程序時第一條指令的地址,而在可重定位文件中,此字段為0
- 多一個程序頭表,也稱段頭表(segment header table) ,是一個結構數組
- 多一個.init節,用於定義_init函數,該函數用來進行可執行目標文件開始執行時的初始化工作
- 少兩個.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節在文件中不占用磁盤空間,但在存儲器中需要給它分配相應大小的空間。
