Golang 可执行可链接格式,ELF文件结构

Golang 投稿 70900 0 评论

Golang 可执行可链接格式,ELF文件结构

ELF文件结构

ELF文件的全称是Executable and Linkable Format,直译为“可执行可链接格式”,包括目标文件(.o)、可执行文件(可以直接运行)、静态链接库、动态链接库、核心转储文件(core dump)。ELF文件的定义可以在中找到,本文主要介绍ELF64,ELF文件通常由下列部分组成:

  • ELF头(ELF header):放在ELF文件开头,描述该文件信息。

  • 节头表(Section header table):包含对节(section)的描述,对于可重定位文件(relocatable files)是必须的,对于可装载文件(loadable files)是可选的。

  • 程序头表(Program header table):对于可装载文件(loadable files)是必须的,对于可重定位文件(relocatable files)是可选的。用来描述加载程序或动态链接库所需要的段(segments)和其他数据结构。

  • 节或段的内容,包括符号表等。

ELF头

typedef struct
{
  unsigned char e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half    e_type;			/* Object file type */
  Elf64_Half    e_machine;		/* Architecture */
  Elf64_Word    e_version;		/* Object file version */
  Elf64_Addr    e_entry;		/* Entry point virtual address */
  Elf64_Off     e_phoff;		/* Program header table file offset */
  Elf64_Off     e_shoff;		/* Section header table file offset */
  Elf64_Word    e_flags;		/* Processor-specific flags */
  Elf64_Half    e_ehsize;		/* ELF header size in bytes */
  Elf64_Half    e_phentsize;		/* Program header table entry size */
  Elf64_Half    e_phnum;		/* Program header table entry count */
  Elf64_Half    e_shentsize;		/* Section header table entry size */
  Elf64_Half    e_shnum;		/* Section header table entry count */
  Elf64_Half    e_shstrndx;		/* Section header string table index */
} Elf64_Ehdr;

Elf64_Ehdr中的数据结构含义如下:

数据结构名称大小(byte)对齐(byte)目标
Elf64_Addr88Unsigned program address
Elf64_Off88Unsigned file offset
Elf64_Half22Unsigned medium integer
Elf64_Word44Unsigned integer
Elf64_Sword44Signed integer
Elf64_Xword88Unsigned long integer
Elf64_Sxword88Signed long integer
unsigned char11Unsigned small integer

从源文件到可执行文件:源文件的预处理、编译、汇编、链接中生成的文件的ELF头(因为我机器上显示的结果是中文,所以接下来就按照中文来说明,比如ELF头中类别对应Class,类型对应Type)。

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:          864 (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:         14
  Section header string table index: 13

当文件被映射到内存中,可以通过魔术字符确定映射地址。 ELF头与Elf64_Ehdr存在对应关系:

Elf64_Ehdr成员ELF头含义
e_identMagic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
e_type类型: REL (可重定位文件)ELF文件类型(包括Relocatable/Executable/Shared/Core等类型)
e_machine系统架构: Advanced Micro Devices X86-64
e_version版本: 0x1版本号,通常为0x1
e_entry入口点地址: 0x0程序入口点的虚拟地址,操作系统在加载完程序后从该地址开始执行进程的指令。可重定位文件没有入口地址,所以为0。用readelf命令查看前文生成的可执行文件可以看到入口地址
e_phoff程序头起点:0 (bytes into file)程序头表偏移(单位:byte)
e_shoffStart of section headers: 864 (bytes into file)节头表偏移(单位:byte)
e_flags标志:0x0特定于处理器的标识
e_ehsizeSize of this header: 64 (bytes)ELF头本身的大小(单位:byte)
e_phentsizeSize of program headers: 0 (bytes)程序头大小(单位:byte)
e_phnumNumber of program headers: 0程序头个数
e_shentsizeSize of section headers: 64 (bytes)节头大小(单位:byte)
e_shnumNumber of section headers: 14节头个数
e_shstrndxSection header string table index: 13字符串表在节头表中索引

对于魔数字符,再展开介绍一下。Magic共16个字节(Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00),第0~3个字节标识文件;第4个字节标识文件类别(类别: ELF64),1表示32位,2标识64位;第5个字节标识文件数据编码(数据: 2 补码,小端序 (little endian)),1标识小端序,2表示大端序;第6个字节标识文件版本(Version: 1 (current)),值为1;第7个字节标识操作系统和ABI(OS/ABI: UNIX - System V),0表示System V ABI,1表示HP-UX operating system,255表示Standalone (embedded) application;第8个字节标识ABI版本(ABI 版本: 0),值为1;剩余字节被保留为将来使用,设置为0。

节头表

一个目标文件(包括Relocatable/Executable/Shared/Core等类型)中包含很多节,这些节的信息保存在节头表中,表的每一项都是一个Elf64_Shdr结构体(也称为节描述符),节点信息包括节名、节大小、在文件中的偏移、读写权限等,编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。中的Elf64_Shdr内容如下:

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Xword	sh_addralign;		/* Section alignment */
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;

通过命令查看hello.o文件的节头表。

There are 14 section headers, starting at offset 0x360:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000027  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000270
       0000000000000060  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000067
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000067
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000067
       0000000000000019  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  00000080
       000000000000002c  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000ac
       0000000000000000  0000000000000000           0     0     1
  [ 8] .note.gnu.propert NOTE             0000000000000000  000000b0
       0000000000000020  0000000000000000   A       0     0     8
  [ 9] .eh_frame         PROGBITS         0000000000000000  000000d0
       0000000000000038  0000000000000000   A       0     0     8
  [10] .rela.eh_frame    RELA             0000000000000000  000002d0
       0000000000000018  0000000000000018   I      11     9     8
  [11] .symtab           SYMTAB           0000000000000000  00000108
       0000000000000138  0000000000000018          12    10     8
  [12] .strtab           STRTAB           0000000000000000  00000240
       0000000000000029  0000000000000000           0     0     1
  [13] .shstrtab         STRTAB           0000000000000000  000002e8
       0000000000000074  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)

正如ELF头中内容所示,hello.o中包含14个节。节名(sh_name)是一个单位为字节的偏移量,表示相对于节名字符串表(section name string table,也就是.shstrtab)起点的偏移,节名实际存放在节名字符串表中,通过查表得到。节类型(sh_type)分为以下几类,readelf命令的结果省略了前缀SHT_。

节类型含义
SHT_NULL无效节
SHT_PROGBITS程序节。代码节、数据节都是这种类型
SHT_SYMTAB符号表
SHT_STRTAB字符串表
SHT_RELA重定位表
SHT_HASH符号表的哈希表
SHT_DYNAMIC动态链接信息
SHT_NOTE提示性信息
SHT_NOBITS表示该节在文件中没有内容,不占用空间
SHT_REL重定位信息
SHT_SHLIB保留
SHT_DYNSYM动态链接的符号表
SHT_LOOS/SHT_HIOS特定环境使用
SHT_LOPROC/SHT_HIPROC特定处理器使用

节标志位(sh_flags)表示该节在进程虚拟地址空间中的属性,1表示可写,2表示该节在进程空间中需要分配空间,有些包含指示或者控制信息的节不需要在进程分配空间,就没有这个标志,4表示该节在进程空间中可以被执行。

节链接信息(sh_link、sh_info),如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表等,则sh_link、sh_info两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。

sh_typesh_linksh_info
SHT_DYNAMIC该节所使用的字符串表在节头表中的下标0
SHT_HASH该节所使用的符号表在节头表中的下标0
SHT_REL该节所使用的相应符号表在节头表中的下标该重定位表所作用的节在节头表中的下标
SHT_RELA该节所使用的相应符号表在节头表中的下标该重定位表所作用的节在节头表中的下标
SHT_SYMTAB操作系统相关操作系统相关
SHT_DYNSYM操作系统相关操作系统相关
otherSHN_UNDEF0

重要的节

  • .text节
    .text节是保存了程序代码指令的代码节。一段可执行程序,如果存在Phdr,则.text节就会存在于text段中。由于.text节保存了程序代码,所以节类型为SHT_PROGBITS。

  • .rodata节
    rodata节保存了只读的数据,如一行C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能在text段(不是data段)中找到.rodata节。由于.rodata节是只读的,所以节类型为SHT_PROGBITS。

  • .plt节(过程链接表)
    .plt节也称为过程链接表(Procedure Linkage Table),其包含了动态链接器调用从共享库导入的函数所必需的相关代码。由于.plt节保存了代码,所以节类型为SHT_PROGBITS。

  • .data节
    .data节存在于data段中,其保存了初始化的全局变量和局部静态变量等数据。由于.data节保存了程序的变量数据,所以节类型为SHT_PROGBITS。

  • .bss节
    .bss节存在于data段中,占用空间不超过4字节,仅表示这个节本身的空间。.bss节保存了未进行初始化的全局数据和局部静态变量,程序加载时数据被初始化为0,在程序执行期间可以进行赋值。由于.bss节未保存实际的数据,所以节类型为SHT_NOBITS。

  • .got.plt节(全局偏移表-过程链接表)
    .got节保存了全局偏移表。.got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。由于.got.plt节与程序执行有关,所以节类型为SHT_PROGBITS。

  • .dynsym节(动态链接符号表)
    .dynsym节保存在text段中。其保存了从共享库导入的动态符号表。节类型为SHT_DYNSYM。

  • .dynstr节(动态链接字符串表)
    .dynstr保存了动态链接字符串表,表中存放了一系列字符串,这些字符串代表了符号名称,以空字符作为终止符。

  • .rel.*节(重定位表)
    重定位表保存了重定位相关的信息,这些信息描述了如何在链接或运行时,对ELF目标文件的某部分或者进程镜像进行补充或修改。由于重定位表保存了重定位相关的数据,所以节类型为SHT_REL。

  • .hash节
    .hash节也称为.gnu.hash,其保存了一个用于查找符号的散列表。

  • .symtab节(符号表)
    .symtab节是一个ElfN_Sym的数组,保存了符号信息。节类型为SHT_SYMTAB。

  • .strtab节(字符串表)
    .strtab节保存的是符号字符串表,表中的内容会被.symtab的ElfN_Sym结构中的st_name引用。节类型为SHT_STRTAB。

  • .ctors节和.dtors节
    .ctors(构造器)节和.dtors(析构器)节分别保存了指向构造函数和析构函数的函数指针,构造函数是在main函数执行之前需要执行的代码;析构函数是在main函数之后需要执行的代码。

符号表包括和,前者是后者的子集。保存了引用自外部文件的符号,只能在运行时被解析(flag为Alloc),而还保存了本地符号,用于调试和链接,不会被装载到内存中。

程序头表

在,程序头表的结构如下:

typedef struct
{
  Elf64_Word	p_type;			/* Segment type */
  Elf64_Word	p_flags;		/* Segment flags */
  Elf64_Off	p_offset;		/* Segment file offset */
  Elf64_Addr	p_vaddr;		/* Segment virtual address */
  Elf64_Addr	p_paddr;		/* Segment physical address */
  Elf64_Xword	p_filesz;		/* Segment size in file */
  Elf64_Xword	p_memsz;		/* Segment size in memory */
  Elf64_Xword	p_align;		/* Segment alignment */
} Elf64_Phdr;

由于hello.o没有程序头表,所以通过来读取可执行文件hello的程序头表,如下所示。

Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 13 program headers, starting at offset 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000005f8 0x00000000000005f8  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x00000000000001f5 0x00000000000001f5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000170 0x0000000000000170  R      0x1000
  LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x0000000000000258 0x0000000000000260  RW     0x1000
  DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x0000000000002020 0x0000000000002020 0x0000000000002020
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x0000000000000248 0x0000000000000248  R      0x1

 Section to Segment mapping:
  段节...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got

与节类似,段也有几种不同的类型(p_type),如下:

段类型含义
PT_NULL无效段
PT_LOAD可加载段
PT_DYNAMIC动态链接表
PT_INTERP程序解释器路径名
PT_NOTE信息段
PT_SHLIB保留
PT_PHDR程序头表
PT_LOOS / PT_HIOS特定环境使用
PT_LOPROC / PT_HIPROC特定处理机使用

段标志位(p_flags)表示权限,X表示执行允许,W表示写允许,R表示读允许。

最后再说一下节(section)和(segment)的关系。每个段包括一个或多个节,因为系统不关心这些节的具体内容,只关心这些节的权限(读、写、执行),将具有相同权限的节放到同一个段中。节是链接视角下的ELF文件,段是运行视角下的ELF文件。

编程笔记 » Golang 可执行可链接格式,ELF文件结构

赞同 (116) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽