ELF文件格式

ELF可执行可链接格式,最初由UNIX系统实验室作为应用程序二进制接口的一部分制定,是COFF(common file format)的变种,相关定义在/usr/include/elf.h文件中。

ELF 文件类型

说明:linux和windows识别文件格式不同,linux是根据文件头的字段,windows直接根据文件名后缀。

主要分为三类:

  • 可执行文件executable file:经过链接、可执行的目标文件,也被称为程序。
  • 可重定位文(relocatable file):由源文件编译但是没有链接的目标文件,通常以.o作为拓展名。用于与其他文件链接构成可执行文件或动态链接库,通常是一段位置独立的代码(Position Independent code, PIC)
  • 共享目标文件(shared object file):动态链接库文件,用于在链接的过程中与其他动态链接库或可重定位文件一起构建可执行文件。

除此之外,还存在 核心转储文件(core dump file)作为进程以外终止时地址空间的转储,也是ELF文件的一种。可以使用gdb工具读取辅助调试和查找程序崩溃的原因。

在内核中的定义(/include/uapi/linux/elf.h):

  #define  ET_REL     1
  #define  ET_EXEC   2
  #define  ET_DYN    3
  #define  ET_CORE  4

ELF文件格式

总体布局

ELF文件格式提供了两种视图,分别是链接视图和执行视图。链接视图是以节(section)为单位执行视图是以段(segment)为单位。接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。

img

可以使用readelf -l hello查看一个链接后的elf可执行文件,Section to Segment 的映射关系

Elf 文件类型为 EXEC (可执行文件)
Entry point 0x401cc0
There are 8 program headers, starting at offset 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000488 0x0000000000000488  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x000000000007fab1 0x000000000007fab1  R E    0x1000
  LOAD           0x0000000000081000 0x0000000000481000 0x0000000000481000
                 0x0000000000026e0b 0x0000000000026e0b  R      0x1000
  LOAD           0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
                 0x00000000000052f0 0x0000000000006b20  RW     0x1000
  NOTE           0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x0000000000000044 0x0000000000000044  R      0x4
  TLS            0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
                 0x0000000000000020 0x0000000000000060  R      0x8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x00000000000a8000 0x00000000004a9000 0x00000000004a9000
                 0x0000000000003000 0x0000000000003000  R      0x1

 Section to Segment mapping:
  段节...
   00     .note.gnu.build-id .note.ABI-tag .rela.plt 
   01     .init .plt .text __libc_freeres_fn .fini 
   02     .rodata .eh_frame .gcc_except_table 
   03     .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs 
   04     .note.gnu.build-id .note.ABI-tag 
   05     .tdata .tbss 
   06     
   07     .tdata .init_array .fini_array .data.rel.ro .got 

下面的段序号和上面程序头里的段一一对应。

文件结构

主要从链接的角度看elf文件。

elf头

描述文件的一些基本信息,文件头部存在魔术字符7f 45 4c 46即字符串"\177ELF",当文件被映射到内存时可以通过该字符串查找位置,dump内存。

readelf -h relocfile //查看文件头信息
ELF 头:
  Magic:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:              0x0
  程序头起点:              0 (bytes into file)
  Start of section headers:          816 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 12
typedef struct elf32_hdr
{
	  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
	  Elf32_Half	e_type;			    /* Object file type */
	  Elf32_Half	e_machine;		    /* Architecture */
	  Elf32_Word	e_version;		    /* Object file version */
	  Elf32_Addr	e_entry;		    /* Entry point virtual address */
	  Elf32_Off	e_phoff;		        /* Program header table file offset */
	  Elf32_Off	e_shoff;		        /* Section header table file offset */
	  Elf32_Word	e_flags;		    /* Processor-specific flags */
	  Elf32_Half	e_ehsize;		    /* ELF header size in bytes */
	  Elf32_Half	e_phentsize;		/* Program header table entry size */
	  Elf32_Half	e_phnum;		    /* Program header table entry count */
	  Elf32_Half	e_shentsize;		/* Section header table entry size */
	  Elf32_Half	e_shnum;		    /* Section header table entry count */
	  Elf32_Half	e_shstrndx;		    /* Section header string table index */
} Elf32_Ehdr;

程序头表(program header table)

列举了所有有效的段(segments)和他们的属性(执行视图),链接视图是可选的,一般没有。

程序头是一个结构的数组,每一个结构都表示一个段(segments)。在可执行文件或者共享链接库中所有的节(sections)都被分为不同的几个段(segments)。

typedef struct elf32_phdr{
	  Elf32_Word	p_type;    /* Magic number and other info */
	  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;

程序头的索引地址(e_phoff)、段数量(e_phnum)、表项大小(e_phentsize)都是通过 ELF头部信息获取的。

可通过readelf -l 读取ELF程序头信息:

节头表(section header table)

一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT)决定。每个section描述了这个段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其它属性。

typedef struct elf32_shdr{
	    Elf32_Word sh_name;   //节区名,名字是一个 NULL 结尾的字符串。
	    Elf32_Word sh_type;    //为节区类型
	    Elf32_Word sh_flags;    //节区标志
	    Elf32_Addr sh_addr;    //节区的第一个字节应处的位置。否则,此字段为 0。
	    Elf32_Off sh_offset;    //此成员的取值给出节区的第一个字节与文件头之间的偏移。
	    Elf32_Word sh_size;   //此 成 员 给 出 节 区 的 长 度 ( 字 节 数 )。
	    Elf32_Word sh_link;   //此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
	    Elf32_Word sh_info;       //此成员给出附加信息,其解释依赖于节区类型。
	    Elf32_Word sh_addralign;    //某些节区带有地址对齐约束.
	    Elf32_Word sh_entsize;    //给出每个表项的长度字节数。
}Elf32_Shdr;

节区名存储在.shstrtab字符串表中,sh_name是表中偏移。

可通过readelf -S 读取sections' header:

There are 29 section headers, starting at offset 0xbf0f8:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.bu[...] NOTE             0000000000400200  00000200
       0000000000000024  0000000000000000   A       0     0     4
  [ 2] .note.ABI-tag     NOTE             0000000000400224  00000224
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .rela.plt         RELA             0000000000400248  00000248
       0000000000000240  0000000000000018  AI       0    18     8
  [ 4] .init             PROGBITS         0000000000401000  00001000
       0000000000000017  0000000000000000  AX       0     0     4
  [ 5] .plt              PROGBITS         0000000000401018  00001018
       00000000000000c0  0000000000000000  AX       0     0     8
  [ 6] .text             PROGBITS         00000000004010e0  000010e0
       000000000007ef60  0000000000000000  AX       0     0     16
  [ 7] __libc_freeres_fn PROGBITS         0000000000480040  00080040
       0000000000000a66  0000000000000000  AX       0     0     16
  [ 8] .fini             PROGBITS         0000000000480aa8  00080aa8
       0000000000000009  0000000000000000  AX       0     0     4
  [ 9] .rodata           PROGBITS         0000000000481000  00081000
       000000000001c61c  0000000000000000   A       0     0     32
  [10] .eh_frame         PROGBITS         000000000049d620  0009d620
       000000000000a71c  0000000000000000   A       0     0     8
  [11] .gcc_except_table PROGBITS         00000000004a7d3c  000a7d3c
       00000000000000cf  0000000000000000   A       0     0     1
  [12] .tdata            PROGBITS         00000000004a9000  000a8000
       0000000000000020  0000000000000000 WAT       0     0     8
  [13] .tbss             NOBITS           00000000004a9020  000a8020
       0000000000000040  0000000000000000 WAT       0     0     8
  [14] .init_array       INIT_ARRAY       00000000004a9020  000a8020
       0000000000000010  0000000000000008  WA       0     0     8
  [15] .fini_array       FINI_ARRAY       00000000004a9030  000a8030
       0000000000000010  0000000000000008  WA       0     0     8
  [16] .data.rel.ro      PROGBITS         00000000004a9040  000a8040
       0000000000002ed4  0000000000000000  WA       0     0     32
  [17] .got              PROGBITS         00000000004abf18  000aaf18
       00000000000000e0  0000000000000000  WA       0     0     8
  [18] .got.plt          PROGBITS         00000000004ac000  000ab000
       00000000000000d8  0000000000000008  WA       0     0     8
  [19] .data             PROGBITS         00000000004ac0e0  000ab0e0
       0000000000001a50  0000000000000000  WA       0     0     32
  [20] __libc_subfreeres PROGBITS         00000000004adb30  000acb30
       0000000000000048  0000000000000000  WA       0     0     8
  [21] __libc_IO_vtables PROGBITS         00000000004adb80  000acb80
       0000000000000768  0000000000000000  WA       0     0     32
  [22] __libc_atexit     PROGBITS         00000000004ae2e8  000ad2e8
       0000000000000008  0000000000000000  WA       0     0     8
  [23] .bss              NOBITS           00000000004ae300  000ad2f0
       0000000000001800  0000000000000000  WA       0     0     32
  [24] __libc_freer[...] NOBITS           00000000004afb00  000ad2f0
       0000000000000020  0000000000000000  WA       0     0     8
  [25] .comment          PROGBITS         0000000000000000  000ad2f0
       0000000000000027  0000000000000001  MS       0     0     1
  [26] .symtab           SYMTAB           0000000000000000  000ad318
       000000000000b3e8  0000000000000018          27   765     8
  [27] .strtab           STRTAB           0000000000000000  000b8700
       00000000000068cf  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  000befcf
       0000000000000128  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
sh_name sh_type description
.text SHT_PROGBITS 代码段,包含程序的可执行指令
.data SHT_PROGBITS 包含初始化了的数据,将出现在程序的内存映像中
.bss SHT_NOBITS 未初始化数据,因为只有符号所以
.rodata SHT_PROGBITS 包含只读数据
.comment SHT_PROGBITS 包含版本控制信息
.eh_frame SHT_PROGBITS 它生成描述如何unwind 堆栈的表
.debug SHT_PROGBITS 此节区包含用于符号调试的信息
.dynsym SHT_DYNSYM 此节区包含了动态链接符号表
.shstrtab SHT_STRTAB 存放section名,字符串表。Section Header String Table
.strtab SHT_STRTAB 字符串表
.symtab SHT_SYMTAB 符号表
.got SHT_PROGBITS 全局偏移表
.plt SHT_PROGBITS 过程链接表
.relname SHT_REL 包含了重定位信息,例如 .text 节区的重定位节区名字将是:.rel.text

1).text 代码段

可以通过objdump -d 反汇编,查看ELF文件代码段内容。

2).strtab / .shstrtab 字符串表

在ELF文件中,会用到很多字符串,比如节名,变量名等。所以ELF将所有的字符串集中放到一个表里,每一个字符串以’\0’分隔,然后使用字符串在表中的偏移来引用字符串。这样在ELF中引用字符串只需要给出一个数组下标即可。字符串表在ELF也以段的形式保存, .shstrtab是专供section name的字符串表。

可以用一下命令查看:readelf -S xxx.o

Section Headers:  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align  [ 0]                   NULL             0000000000000000  00000000       0000000000000000  0000000000000000           0     0     0  [ 1] .text             PROGBITS         0000000000000000  00000040       0000000000000045  0000000000000000  AX       0     0     1  [ 2] .rela.text        RELA             0000000000000000  00000658       0000000000000048  0000000000000018          12     1     8  [ 3] .data             PROGBITS         0000000000000000  00000085       0000000000000000  0000000000000000  WA       0     0     1                         … …  [11] .shstrtab         STRTAB           0000000000000000  00000108       0000000000000074  0000000000000000           0     0     1  [12] .symtab           SYMTAB           0000000000000000  00000500       0000000000000138  0000000000000018          13    10     8  [13] .strtab           STRTAB           0000000000000000  00000638       000000000000001e  0000000000000000           0     0     1

3).symtab 符号表

在链接的过程中需要把多个不同的目标文件合并在一起,不同的目标文件相互之间会引用变量和函数。在链接过程中,我们将函数和变量统称为符号,函数名和变量名就是符号名。

每个定义的符号都有一个相应的值,叫做符号值(Symbol Value),对于变量和函数,符号值就是它们的地址。

可以使用下面命令查看:readelf -s xxx.o

img

4).eh_frame / .eh_frame_hdr

在调试程序的时候经常需要进行堆栈回溯,早期使用通用寄存器(ebp)来保存每层函数调用的栈帧地址,但局限性很大。后来现代Linux操作系统在LSB(Linux Standard Base)标准中定义了一个.eh_frame section,用来描述如何去unwind the stack。gcc编译器默认打开,如果不想把.eh_frame section编入elf文件,可以通过gcc选项 -fno-asynchronous-unwind-tables 去除。

GAS(GCC Assembler)汇编编译器定义了一组伪指令来协助eh_frame生成调用栈信息CFI(Call Frame Information)。具体原理在后续《栈回溯》章节分析,这里不再阐述。

5)重定位表(.relname)

链接器在处理目标文件时,需要对目标文件中的某些部位进行重定位,即代码段和数据中中那些绝对地址引用的位置。对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。比如”.rel.text”就是针对”.text”的重定位表,”.rel.data”就是针对”.data”的重定位表。

GOT是全局偏移表( Global Offset Table),用于存储外部符号地址;PLT是程序链接表(Procedure Link Table),用于存储记录定位信息的额外代码。关于linux动态链接重定位原理,可以参考海枫写的《Linux动态链接中的PLT和GOT》。

相关命令

readelf -S relocfile //查看节头表objdump -x -s -d relocfile //查看.text汇编readelf -x .strtab relocfile //查看字符串表readelf -r file //查看重定位表

可执行文件的装载

当运行一个可执行文件时,首先需要将该文件和动态链接库装载到进程空间,形成一个进程镜像。

每个进程都有独立的虚拟地址空间,空间的布局由程序头(program header)决定,通过readelf -l file可以看到节到段的映射,以及段的地址空间布局。每一个段都包含一个或多个节,因为随着节数量的增多,在进行内存映射时就会大量浪费空间和资源。系统并不关心节的实际内容,而是不同节的读写、执行的权限。

参考

ELF文件格式 - 知乎 (zhihu.com)

gcc 编译命令详解及最佳实践 - 知乎 (zhihu.com)

posted @ 2021-11-12 16:17  ddddd1234654732  阅读(382)  评论(0)    收藏  举报
Live2D