ELF文件(Executable Linkable Format)是一種文件存儲格式。Linux下的目標文件和可執行文件都按照該格式進行存儲,有必要作個總結。程序員
本文主要記錄總結32位的Intel x86平臺下的ELF文件結構。ELF文件以Section的形式進行存儲。代碼編譯後的指令放在代碼段(Code Section),全局變量和局部靜態變量放到數據段(Data Section)。文件以一個「文件頭」開始,記錄了整個文件的屬性信息。c#
SimpleSection.c數組
int printf(const char* format, ...); int global_init_var = 84; int global_uniit_var; void func1(int i) { printf("%d\n", i); } int main(void) { static int static_var = 85; static int static_var2; int a = 1; int b; func1(static_var + static_var2 + a + b); return a; }
對於上面的一段c代碼將其編譯可是不連接。gcc -c -m32 SimpleSection.c
( -c表示只編譯不連接,-m32表示生成32位的彙編)獲得SimpleSection.o。能夠用objdump或readelf命令查看目標文件的結構和內容。sass
能夠用readelf -h
查看文件頭信息。執行readelf -h SimpleSection.o
後:函數
root@DESKTOP-2A432QS:~/c# readelf -h SimpleSection.o ELF Header: Magic: 7f 45 4c 46 01 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: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 832 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 13 Section header string table index: 10
程序頭包含了不少重要的信息,每一個字段的含義可參考ELF結構文檔。主要看下:ui
ELF文件由各類各樣的段組成,段表就是保存各個段信息的結構,以數組形式存放。段表的起始位置,長度,項數分別由ELF文件頭中的Start of section headers,Size of section headers,Number of section headers指出。使用readelf -S SimpleSection.o
查看SimpleSection.o的段表以下:this
There are 13 section headers, starting at offset 0x340: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 000034 000062 00 AX 0 0 1 [ 2] .rel.text REL 00000000 0002a8 000028 08 I 11 1 4 [ 3] .data PROGBITS 00000000 000098 000008 00 WA 0 0 4 [ 4] .bss NOBITS 00000000 0000a0 000004 00 WA 0 0 4 [ 5] .rodata PROGBITS 00000000 0000a0 000004 00 A 0 0 1 [ 6] .comment PROGBITS 00000000 0000a4 000036 01 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 00000000 0000da 000000 00 0 0 1 [ 8] .eh_frame PROGBITS 00000000 0000dc 000064 00 A 0 0 4 [ 9] .rel.eh_frame REL 00000000 0002d0 000010 08 I 11 8 4 [10] .shstrtab STRTAB 00000000 0002e0 00005f 00 0 0 1 [11] .symtab SYMTAB 00000000 000140 000100 10 12 11 4 [12] .strtab STRTAB 00000000 000240 000065 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
總共有13個Section,重點關注.text, .data, .rodata, .symtab, .rel.text段。code
.text段保存代碼編譯後的指令,能夠用objdump -s -d SimpleSection.o
查看SimpleSection.o代碼段的內容。orm
SimpleSection.o: file format elf32-i386 Contents of section .text: 0000 5589e583 ec0883ec 08ff7508 68000000 U.........u.h... 0010 00e8fcff ffff83c4 1090c9c3 8d4c2404 .............L$. 0020 83e4f0ff 71fc5589 e55183ec 14c745f0 ....q.U..Q....E. 0030 01000000 8b150400 0000a100 00000001 ................ 0040 c28b45f0 01c28b45 f401d083 ec0c50e8 ..E....E......P. 0050 fcffffff 83c4108b 45f08b4d fcc98d61 ........E..M...a 0060 fcc3 .. ...省略 Disassembly of section .text: 00000000 <func1>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 ec 08 sub $0x8,%esp 9: ff 75 08 pushl 0x8(%ebp) c: 68 00 00 00 00 push $0x0 11: e8 fc ff ff ff call 12 <func1+0x12> 16: 83 c4 10 add $0x10,%esp 19: 90 nop 1a: c9 leave 1b: c3 ret 0000001c <main>: 1c: 8d 4c 24 04 lea 0x4(%esp),%ecx 20: 83 e4 f0 and $0xfffffff0,%esp 23: ff 71 fc pushl -0x4(%ecx) 26: 55 push %ebp 27: 89 e5 mov %esp,%ebp 29: 51 push %ecx 2a: 83 ec 14 sub $0x14,%esp 2d: c7 45 f0 01 00 00 00 movl $0x1,-0x10(%ebp) 34: 8b 15 04 00 00 00 mov 0x4,%edx 3a: a1 00 00 00 00 mov 0x0,%eax 3f: 01 c2 add %eax,%edx 41: 8b 45 f0 mov -0x10(%ebp),%eax 44: 01 c2 add %eax,%edx 46: 8b 45 f4 mov -0xc(%ebp),%eax 49: 01 d0 add %edx,%eax 4b: 83 ec 0c sub $0xc,%esp 4e: 50 push %eax 4f: e8 fc ff ff ff call 50 <main+0x34> 54: 83 c4 10 add $0x10,%esp 57: 8b 45 f0 mov -0x10(%ebp),%eax 5a: 8b 4d fc mov -0x4(%ebp),%ecx 5d: c9 leave 5e: 8d 61 fc lea -0x4(%ecx),%esp 61: c3 ret
能夠看到.text段裏保存的正是func1()和main()的指令。blog
.data段保存的是已經初始化了的全局靜態變量和局部靜態變量。前面SimpleSection.c中的global_init_varabal和static_var正是這樣的變量。使用objdump -x -s -d SimpleSection.o
查看:
Contents of section .data: 0000 54000000 55000000 T...U... Contents of section .rodata: 0000 25640a00 %d..
最左邊的0000是偏移,不用看,後面跟着的0x00000054和0x00000055正是global_init_varabal和static_var的初始值。
.rodata段存放的是隻讀數據,包括只讀變量(const修飾的變量和字符串常量),這個例子中保存了"%d\n"正是調用printf的時候使用的字符常量。
符號表段通常叫作.symtab,以數組結構保存符號信息(函數和變量),對於函數和變量符號值就是它們的地址。主要關注兩類符號:
能夠用readelf -s SimpleSection.o
查看SimpleSection.o的符號:
Symbol table '.symtab' contains 16 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c 2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.1488 7: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1489 8: 00000000 0 SECTION LOCAL DEFAULT 7 9: 00000000 0 SECTION LOCAL DEFAULT 8 10: 00000000 0 SECTION LOCAL DEFAULT 6 11: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var 12: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uniit_var 13: 00000000 28 FUNC GLOBAL DEFAULT 1 func1 14: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf 15: 0000001c 70 FUNC GLOBAL DEFAULT 1 main
能夠看到:
Contents of section .data: 0000 54000000 55000000 T...U...
數據段0x00000000和0x00000004偏移處保存的正是global_init_var和static_var這兩個變量。
重定位表也是一個段,用於描述在重定位時連接器如何修改相應段裏的內容。對於.text段,對應的重定位表是.rel.text表。使用objdump -r SimpleSection.o
查看重定位表。
SimpleSection.o: file format elf32-i386 RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 0000000d R_386_32 .rodata 00000012 R_386_PC32 printf 00000036 R_386_32 .data 0000003b R_386_32 .bss 00000050 R_386_PC32 func1
printf對應的那行的OFFSET爲0x00000012,代表.text段的0x00000012偏移處須要修改。咱們objdump -s -d SimpleSection.o
查看代碼段的0x00000012偏移,發現是」fc ff ff ff「是call指令的操做數。
00000000 <func1>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 ec 08 sub $0x8,%esp 9: ff 75 08 pushl 0x8(%ebp) c: 68 00 00 00 00 push $0x0 11: e8 fc ff ff ff call 12 <func1+0x12> 16: 83 c4 10 add $0x10,%esp 19: 90 nop 1a: c9 leave 1b: c3 ret
也就是說,在沒有重定位前call指令的操做」fc ff ff ff「是無效的,須要在重定位過程當中進行修正。func1那行也同理。
ELF文件結構能夠用下面的圖表示:
和未連接的ELF文件結構同樣,只不過引入了Segment的概念(注意和Section進行區分)。Segment本質上是從裝載的角度從新劃分了ELF的各個段。目標文件連接成可執行文件時,連接器會盡量把相同權限屬性的段(Section)分配到同一Segment。Segment結構的起始位置,項數,大小分別由ELF頭中的Size of program headers,Number of program headers, Size of this header字段指定。
參考資料: