目標(biāo)文件格式
前言:在講鏈接之前,我們先得說(shuō)說(shuō)痴怨,可重定位目標(biāo)文件格式和可執(zhí)行目標(biāo)文件格式
采用的是 ELF 標(biāo)準(zhǔn)二進(jìn)制文件格式進(jìn)行說(shuō)明
可重定位目標(biāo)文件格式
整個(gè)文件格式如下:
名稱(chēng) |
---|
ELF 頭 |
.text 節(jié) |
.rodata 節(jié) |
.data 節(jié) |
.bss 節(jié) |
.symtab 節(jié) |
.rel.text 節(jié) |
.rel.data 節(jié) |
.debug 節(jié) |
.line 節(jié) |
.strtab 節(jié) |
節(jié)頭表 |
- ELF 頭
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
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;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
一共 52 個(gè)字節(jié)屡穗。咋算的呢?Elf32_Half 開(kāi)頭的就是 16 位兩個(gè)字節(jié)
也就是:
16 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 6 * 2 = 52
每個(gè)的作用如下
成員 | 含義 |
---|---|
e_ident[EI_NIDENT] | 前 4 個(gè)字節(jié)成為魔數(shù)字,通常用來(lái)確定文件的類(lèi)型和格式 |
e_type | 文件類(lèi)型:可重定位咆瘟、可執(zhí)行、共享庫(kù) |
e_machine | 機(jī)器結(jié)構(gòu)類(lèi)型:如 IA-32 |
e_version | 文件版本 |
e_entry | 程序起始虛擬地址诽里,如可重定位文件就是 0 |
e_phoff | 程序頭表的偏移(在可執(zhí)行目標(biāo)文件中才有) |
e_shoff | 節(jié)頭表的偏移(字節(jié)為單位) |
e_flags | |
e_ehsize | ELF 頭大刑徊汀(字節(jié)為單位) |
e_phentsize | 程序頭表大小(在可執(zhí)行目標(biāo)文件中才有) |
e_phnum | 程序頭表項(xiàng)數(shù)(在可執(zhí)行目標(biāo)文件中才有) |
e_shentsize | 節(jié)頭表中一個(gè)表項(xiàng)的大邪啤(每個(gè)表項(xiàng)大小一致灸眼,字節(jié)為單位) |
e_shnum | 節(jié)頭表的項(xiàng)數(shù) |
e_shstrndx | .strlab 節(jié)在節(jié)頭表的索引 |
每種可執(zhí)行文件的格式的開(kāi)頭幾個(gè)字節(jié)都是很特殊的,特別是開(kāi)頭 4 個(gè)字節(jié)墓懂,通常被稱(chēng)為魔數(shù)(Magic Number)焰宣。通過(guò)對(duì)魔數(shù)的判斷可以確定文件的格式和類(lèi)型。如:ELF 的可執(zhí)行文件格式的頭 4 個(gè)字節(jié)為0x7F
拒贱、e
宛徊、l
、f
逻澳;Java的可執(zhí)行文件格式的頭 4 個(gè)字節(jié)為c
闸天、a
、f
斜做、e
苞氮;如果被執(zhí)行的是 Shell 腳本或 perl、python 等解釋型語(yǔ)言的腳本瓤逼,那么它的第一行往往是 #!/bin/sh
或 #!/usr/bin/perl
或 #!/usr/bin/python
笼吟,此時(shí)前兩個(gè)字節(jié) #
和 !
就構(gòu)成了魔數(shù),系統(tǒng)一旦判斷到這兩個(gè)字節(jié)霸旗,就對(duì)后面的字符串進(jìn)行解析贷帮,以確定具體的解釋程序路徑。
假設(shè)你有這樣一個(gè) main.o 的文件诱告,可以用 readelf 看它的 ELF 頭
readelf -h main.o
- 節(jié)
節(jié)是 ELF 文件中的主體信息撵枢,包含了鏈接過(guò)程中所用的目標(biāo)代碼信息,包括指令、數(shù)據(jù)锄禽、符號(hào)表和重定位信息潜必。
節(jié)名稱(chēng) | 用途 |
---|---|
.text 節(jié) | 目標(biāo)代碼部分(二進(jìn)制) |
.rodata 節(jié) | 只讀數(shù)據(jù),如 printf 中的格式串沃但、開(kāi)關(guān)語(yǔ)句的跳轉(zhuǎn)表 |
.data 節(jié) | 已經(jīng)初始化的全局變量 |
.bss 節(jié) | 未初始化的全局變量磁滚。由于是未初始化,所以無(wú)需在當(dāng)前目標(biāo)文件中分配用與保存值的空間宵晚。而對(duì)于局部變量來(lái)說(shuō)垂攘,運(yùn)行時(shí)被分配在棧中所以既不出現(xiàn)在 .bss 節(jié)中也不會(huì)出現(xiàn)在 .data 節(jié)中 |
.symtab 節(jié) | 符號(hào)表,程序中定義的函數(shù)名和全局靜態(tài)變量名都屬于符號(hào)坝疼,與這些符號(hào)相關(guān)的信息都保存在符號(hào)表中搜贤。每個(gè)可重定位目標(biāo)文件都有一個(gè) .symtab 節(jié) |
.rel.text 節(jié) | .text 節(jié)相關(guān)的重定位信息。通常钝凶,調(diào)用外部函數(shù)或者引用全局變量的指令中的地址字段需要修改 |
.rel.data 節(jié) | .data 節(jié)相關(guān)的可重定位信息仪芒。 |
.debug 節(jié) | 調(diào)試用符號(hào)表 |
.line 節(jié) | 源程序中的行號(hào)和 .text 節(jié)中的機(jī)器指令之間的映射 |
.strtab 節(jié) | 字符串表,包括 .symtab 和 .debug 節(jié)中的符號(hào)以及節(jié)頭表中的節(jié)名耕陷。 |
- 節(jié)頭表
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word shdebugging sym_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
大小 40B = 4 × 10B掂名。
命令:readelf -S main.o
可執(zhí)行目標(biāo)文件格式
鏈接器將相互關(guān)聯(lián)的可重定位目標(biāo)文件中的相同代碼和數(shù)據(jù)節(jié)(.text 節(jié),.rodata 節(jié)哟沫,.data 節(jié)饺蔑,.bss 節(jié))合并。因?yàn)楹喜⒑笫染鳎械奶摷俚刂范寄鼙挥?jì)算出來(lái)猾警。也就能算出每個(gè)符號(hào)的地址。
可執(zhí)行目標(biāo)文件格式包括:
- ELF 頭
- 程序頭表
- 節(jié)頭表
名稱(chēng) |
---|
ELF 頭 |
程序頭表 |
.init隆敢、.fini 節(jié) |
.text 節(jié) |
.rodata 節(jié) |
.data 節(jié) |
.bss 節(jié) |
.symtab 節(jié) |
.debug 節(jié) |
.line 節(jié) |
.strtab 節(jié) |
節(jié)頭表 |
與可重定義目標(biāo)文件格式類(lèi)似发皿,主要不同點(diǎn)有:
- ELF 頭中 e_entry 不再是 0。而是執(zhí)行代碼的第一條指令的地址
- 多了 .init 節(jié)和 .fini 節(jié)拂蝎,其中 .init 節(jié)中定義了一個(gè) _init 函數(shù)穴墅,用于可執(zhí)行目標(biāo)文件執(zhí)行時(shí)初始化工作。.fini 包含進(jìn)程終止時(shí)要執(zhí)行的指令代碼
- 少了 .rel.text 和 .rel.data 節(jié)等重定位信息節(jié)温自。
- 多了一個(gè)程序頭表也叫作段頭表
整個(gè)文件有兩個(gè)重要的段
- 只讀代碼段:(ELF 頭 + 程序頭表 + .init .fini 節(jié) + .text 節(jié) + .rodata 節(jié) )
- 可讀寫(xiě)數(shù)據(jù)段:(.data 節(jié) + .bss 節(jié))玄货,由于在執(zhí)行文件時(shí)這兩個(gè)段必須分配空間所以又可以叫做 可裝入段
下面隆重介紹程序頭表:
為了在可執(zhí)行文件執(zhí)行時(shí)能夠訪在內(nèi)存中訪問(wèn)到代碼和數(shù)據(jù),必須將可執(zhí)行文件中這些連續(xù)的悼泌,具有相同訪問(wèn)屬性的代碼和數(shù)據(jù)段映射到存儲(chǔ)空間(通常是虛擬地址)松捉。程序頭表就用于描述這種映射關(guān)系,一個(gè)表項(xiàng)對(duì)應(yīng)一個(gè)連續(xù)的存儲(chǔ)段或特殊節(jié)馆里。
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;
成員 | 含義 |
---|---|
p_type | 短的類(lèi)型或者節(jié)的類(lèi)型惩坑,列入是否為可裝入段 |
p_offset | 本段的首字節(jié)在文件中的偏移地址 |
p_vaddr | 本字段的虛擬地址 |
p_paddr | 本段首字節(jié)的物理地址 |
p_filesz | 本段所占字節(jié)數(shù) |
p_memsz | 在存儲(chǔ)器中所占字節(jié)數(shù) |
p_flags | 存取權(quán)限 |
p_align | 對(duì)齊方式 |
輸入 readelf -l main
程序頭:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00120 0x00120 R 0x4
INTERP 0x000154 0x00000154 0x00000154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x00000000 0x00000000 0x00730 0x00730 R E 0x1000
LOAD 0x000edc 0x00001edc 0x00001edc 0x0012c 0x00130 RW 0x1000
DYNAMIC 0x000ee4 0x00001ee4 0x00001ee4 0x000f8 0x000f8 RW 0x4
NOTE 0x000168 0x00000168 0x00000168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0005d0 0x000005d0 0x000005d0 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000edc 0x00001edc 0x00001edc 0x00124 0x00124 R 0x1
這里只是大概介紹了一些知識(shí)掉盅,具體怎么運(yùn)用要看接下來(lái)的文章。