ELF文件(Executable Linkable Format)是一种文件存储格式。Linux下的目标文件和可执行文件都按照该格式进行存储,有必要做个总结。
概要本文主要记录总结32位的Intel x86平台下的ELF文件结构。ELF文件以Section的形式进行存储。代码编译后的指令放在代码段(Code Section),全局变量和局部静态变量放到数据段(Data Section)。文件以一个“文件头”开始,记录了整个文件的属性信息。
未链接的目标文件结构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命令查看目标文件的结构和内容。
ELF文件头可以用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结构文档。主要看下:
Entry point address:程序的入口地址,这是没有链接的目标文件所以值是0x00
Start of section headers:段表开始位置的首字节
Size of section headers:段表的长度(字节为单位)
Number of section headers:段表中项数,也就是有多少段
Start of program headers:程序头的其实位置(对于可执行文件重要,现在为0)
Size of program headers:程序头大小(对于可执行文件重要,现在为0)
Number of program headers:程序头中的项数,也就是多少Segment(和Section有区别,后面介绍)
Size of this header:当前ELF文件头的大小,这里是52字节
段表及段(Section) 段表ELF文件由各种各样的段组成,段表就是保存各个段信息的结构,以数组形式存放。段表的起始位置,长度,项数分别由ELF文件头中的Start of section headers,Size of section headers,Number of section headers指出。使用readelf -S SimpleSection.o查看SimpleSection.o的段表如下:
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段。
代码段.text段保存代码编译后的指令,可以用objdump -s -d SimpleSection.o查看SimpleSection.o代码段的内容。
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()的指令。
数据段和只读数据段