ELF文件类型

ELF主要包括三种类型文件

  • 可重定位文件(relocatable):编译器和汇编器产生的.o文件,尚未会被Linker所处理,符号尚未被全局确认地址,不存在程序段(segments)
  • 可执行文件(executable):通过Linker对所有的.o文件进行链接处理输出的文件,包括所有的符号,包括程序段(segments)和节区(sections)
  • 共享对象文件(shared object):动态库文件.so,包括程序段(segments)和节区(sections),会对本地符号进行链接处理
    可以通过 file 命令来查询类型,如下:
$ file ./cJSON.o
./cJSON.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), with debug_info, not stripped

$ file ./test.elf
./test.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped

ELF文件布局

ELF文件从概念上来说包括了5个部分:
ELF header:描述体系结构和操作系统等基本信息,指出Entry point address、section header table和program header table在文件的位置
section header table:节区表,这个保存了所有的section的信息,这是从编译和链接的角度来看ELF文件的,主要是Linker链接时使用
program header table:程序头表,这个是从运行的角度来看ELF文件的,主要给出了各个segment的信息,实际是多个sections合并后的结果
sections:就是各个节区
segments:就是在运行时的各个段
sections 和 segments 所在的位置是一样的,左边是链接视图,右边是加载视图,sections是程序员可见的,是给链接器使用的概念,而segments是程序员不可见的,是给加载器使用的概念,一般是一个segment包含多个sections。
2-gyyr.png

ELF Header

使用如下命令查询文件的 ELF header

> readelf -h app.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:                           ARM
  Version:                           0x1
  Entry point address:               0x1
  Start of program headers:          0 (bytes into file)
  Start of section headers:          2944 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  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:         11
  Section header string table index: 10

代码中的结构体如下所示,给出了程序段和节区信息等:

/* ELF32 Header */
#define EI_NIDENT   16
typedef struct elf32_hdr {
    /*ELF的一些标识信息,固定值*/
    unsigned char e_ident[EI_NIDENT];           /* ELF identification */
    /*目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件等*/
    Elf32_Half    e_type;                       /* object file type */
    /*文件的目标体系结构类型:ARM,X86-64*/
    Elf32_Half    e_machine;                    /* machine */
    /*目标文件版本:1-当前版本*/
    Elf32_Word    e_version;                    /* object file version */
    /*程序入口的虚拟地址,重定位文件中一定为0*/
    Elf32_Addr    e_entry;                      /* virtual entry point */
    /*程序头表(segment header table)的偏移量,没有可为0*/
    Elf32_Off     e_phoff;                      /* program header table offset */
    /*节区头表(section header table)的偏移量,没有可为0*/
    Elf32_Off     e_shoff;                      /* section header table offset */
    /*与文件相关的,特定于处理器的标志*/
    Elf32_Word    e_flags;                      /* processor-specific flags */
    /*ELF头部的大小,单位字节*/
    Elf32_Half    e_ehsize;                     /* ELF header size */
    /*程序头表每个表项的大小,单位字节*/
    Elf32_Half    e_phentsize;                  /* program header entry size */
    /*程序头表表项的个数*/
    Elf32_Half    e_phnum;                      /* number of program header entries */
    /*节区头表每个表项的大小,单位字节*/
    Elf32_Half    e_shentsize;                  /* section header entry size */
    /*节区头表表项的数目*/
    Elf32_Half    e_shnum;                      /* number of section header entries */
    /*.shstrtab 段在段表中的索引位置*/
    Elf32_Half    e_shstrndx;                   /* index of ".shstrtab" in segment table */
} Elf32_Ehdr;

ELF 头中包含了程序运行的关键信息,如下:

  • e_ident
    在数组 e_ident[0~3] 的前4个字节中存储了 ELF 的magic number,分别为 0x7F、E、L、F,主要是用来识别是否为ELF 格式的文件,只有检测到这个魔数,才会进行下一步解析,否则直接退出报错。
名称索引作用
EI_MAG00文件标识
EI_MAG11文件标识
EI_MAG22文件标识
EI_MAG33文件标识
EI_CLASS4文件类 [ 1: ELFCLASS32; 2: ELFCLASS64 ]
EI_DATA5数据编码 [ 1: ELFDATA2LSB; 2: ELFDATA2MSB]
EI_VERSION6文件版本
EI_PAD7补齐字节开始处
  • e_type
    在ELF头中可以通过 e_type 成员获取文件的类型:

    • 可重定位类型:ET_REL = 1,也就是我们常见到 .o 文件,只是经过编译后产生的文件
    • 可执行类型:ET_EXEC = 2,也就是我们常见的编译后的可以运行的文件,经过编译和完整的链接后产生的文件
    • 共享库类型:ET_DYN = 3,也就是我们常见的 .so 文件,经过了编译和 -share 链接产生的文件
  • e_entry

    • 可执行程序:指明程序运行时的需要跳转的第一条指令的位置
    • 对于共享库文件:在没有指定入口地址的情况下,默认是 .text 的第一个函数
    • 对于可重定位文件:默认为0,也可以使用 ld 在链接时使用 -e 来指明入口地址(多个.o 文件也可以使用 LD 工具 配合 -r -S 选项将多个 .o 文件打包成一个 .o 文件)

ELF Section Header Table

节区表是给连接器用来进行本地成员的重定位,节区表是一个数组,每个成员都是 ELF32_Shdr 类型,在 Elf32_Ehdr 中使用 e_shoff 给出节区表在重定位文件中相对于文件开始的偏移。

使用如下命令查询文件的 ELF section header table

> readelf -S app.o

There are 11 section headers, starting at offset 0xb80:

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 000038 000250 00  AX  0   0  8
  [ 2] .rel.text         REL             00000000 000a14 000118 08   I  8   1  4
  [ 3] .rodata           PROGBITS        00000250 000288 0001f8 01 AMS  0   0  1
  [ 4] .data             PROGBITS        00000448 000480 000000 00  WA  0   0  1
  [ 5] .bss              NOBITS          00000448 000480 000491 00  WA  0   0  1
  [ 6] .comment          PROGBITS        00000000 000480 0000fe 01  MS  0   0  1
  [ 7] .ARM.attributes   ARM_ATTRIBUTES  00000000 00057e 00003d 00      0   0  1
  [ 8] .symtab           SYMTAB          00000000 0005bc 0002f0 10      9  25  4
  [ 9] .strtab           STRTAB          00000000 0008ac 000165 00      0   0  1
  [10] .shstrtab         STRTAB          00000000 000b2c 000051 00      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),
  y (purecode), p (processor specific)

代码中的结构体如下所示:

/* ELF32 Section Header */
typedef struct elf32_shdr {
    /*节区名称,实际是一个索引,指向.shstrtab "节区名字表"某个位置*/
    Elf32_Word sh_name;                         /* name - index into section header string table section */
    /*节区类型:PROGBITS-程序信息,NOBITS-不占用文件空间(bss),REL-重定位表项*/
    Elf32_Word sh_type;                         /* type */
    /*权限掩码,表示节区是否载入内存、是否可读、可写、可执行等信息*/
    Elf32_Word sh_flags;                        /* flags */
    /*节区映射到内存的虚拟地址*/
    Elf32_Addr sh_addr;                         /* address */
    /*节区的第一个字节相对于文件头的偏移*/
    Elf32_Off  sh_offset;                       /* file offset */
    /*节区的长度,单位字节,NOBITS类型时有大小但是在文件中不占用空间*/
    Elf32_Word sh_size;                         /* section size */
    /*本节所使用的符号表在节头表中的索引*/
    Elf32_Word sh_link;                         /* section header table index link */
    /*节区附加信息,sh_type=SHT_REL/SHT_RELA:对于该重定位表所作用的节在节头表中的下标*/
    Elf32_Word sh_info;                         /* extra information */
    /*指明节区内数据的对齐方式*/
    Elf32_Word sh_addralign;                    /* address alignment */
    /*某些节区中包含固定大小的项目,如符号表,指明其成员固定大小,为0则表示不包括固定大小*/
    Elf32_Word sh_entsize;                      /* section entry size */
} Elf32_Shdr;
  • sh_name
    节名称 sh_name 并不是一个字符串,而是指向 .shstrtab "节区名字表"下的一个索引位置,这个索引可以从 .shstrtab 中获取当前节的字符串名称,常见节区名称和解释如下:
区名              描述说明
.bss            本区中保存初始化为0的全局和静态变量。保存未初始化的全局和静态变量,运行时载入内存并初始化为0,但是,
                在目标文件中.bss区不占用空间,其长度为0,所以它的区类型为NOBITS。
.data           本区用于存放程序中被初始化过的全局变量。在目标文件中,占用实际的存储空间的,在运行时载入内存。
.rodata         本区保存了只读数据,可以读取但不能修改,可以加载到内存中使用,也可以在XIP模式下存储在 norFlash 上。。
.rodata.str1.x:本区保存只读字符串数据,目的是为了更紧凑或更符合CPU运行的方式存储字符串,使用-fno-merge-constants 可以取消优化,
                而统一存储在.rodata中。
.text           本区包含程序指令代码。可以加载到内存中执行,也可以在XIP模式下在 norFlash 上运行。
.dynamic        本区包含动态链接信息。包括依赖于哪些共享目标文件、动态连接符号表的位置、动态连接重定位表的位置等,运行时载入内存,
                具有SHF_ALLOC和SHF_WRITE属性。
.strtab         本区用于存放字符串,主要是那些符号表项的名字。其中含有符号名称,存储的是变量名,函数名等。
.symtab         本区用于存放符号表。其中含有动态链接过程中使用的符号和连接阶段已完成重定位的符号,无需载入内存。
.dynstr         本区含有用于动态链接的字符串节。包括了.dynsym中动态符号的名字字符串,具有SHF_ALLOC属性,运行是载入内存。
.dynsym         本区含有动态链接符号表。具有SHF_ALLOC属性,只保存动态链接过程中用到的符号是 .symtab 的子集,__attribute__ ((visibility ("xxx")))可以有效减少动态符号表大小。
.got            本区包含全局偏移量表(global offset table),位于数据段的开始,用于保存全局变量以及库函数(外部函数)的引用。
.hash           本区包含一张符号哈希表,用于快速寻找。
.interp         本区含有ELF程序解析器的路径名。这是一个ASCII字符串,仅在动态链接的可执行文件类型中存在。
.plt            本区包含函数链接表。动态链接时使用的过程链接表(precedure linkage table)
.rel.text       本区用于存储代码段的重定位项,指明需要重定位的地方在.text段内,以offset指定具体要进行指令修改的位置。
                在连接时候由连接器完成重定位工作,一般由编译器编译产生,存在于obj文件内。
.rel.data       数据段重定位信息
.rel.rodata     常量区重定位信息
.rel.dyn        重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定位,
                定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修改.got表对应位置下的
                value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态加载。
                区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。
.rel.plt        重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般是函数首次
                被调用时候重定位。可看汇编,理解其首次访问是如何重定位的,实际很简单,就是初次重定位函数地址,
                然后把最终函数地址放到.got.plt内,以后读取该.got.plt就直接得到最终函数地址(参考过程说明)。  
                所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。 
                一般由连接器产生,存在于可执行文件或者动态库文件。
                借助这两个辅助段可以动态修改对应.got和.got.plt段,从而实现运行时重定位。
.init           本区包含进程初始化时要执行的程序指令,当程序开始运行时,系统会在进入主函数之前执行这一区中的代码。
.fini           本区包含进程终止代码,当程序结束运行时,系统会在最后执行这一区中的代码.
.debug          本区中含有调试信息。所有以".debug"为前缀的区名字都是保留的。
.line           本区也是一个用于调试的区,它包含那些调试符号的行号,为程序指令码与源文件的行号建立起联系。
.shstrtab       本区是"节区名字表",含有所有其它区的名字,如 `.data`,`.bss`,`.text`...
  • sh_type
    节类型 sh_type 表明了当前节的作用。
名称类型
SHT_NULL0无效节
SHT_PROGBITS1可编程节,例如.text .data .rodata
SHT_SYMTAB2符号表,例如.symtab
SHT_STRTAB3字符串表,例如.strtab .shstrtab .dynstr
SHT_RELA4重定位表,例如.rel.text rel.dyn .rel.plt
SHT_HASH5符号表的哈希表,例如.hash
SHT_DYNAMIC6动态链接信息节,例如.dynamic
SHT_NOTE7提示性的节
SHT_NOBITS8提示该节不占用文件空间,例如.bss
SHT_REL9表示该节包含重定位向,例如.rel.text .rel.dyn
SHT_SHLIB10保留
SHT_DYNSYM11动态链接符号表,例如.dynsym
  • sh_flags
    节类型 sh_flags 表明了当前节是需要否载入内存、是具有否可读、可写、可执行权限等信息。
名称类型
SHF_WRITE0x1表示节在载入后是可写的
SHF_ALLOC0x2表示节在运行时需要载入并占用内存空间
SHF_EXECINSTR0x4表示节是可执行的指令
SHF_MERGE0x10表示节是可合并的节
SHF_STRINGS0x20表示节包含以空字符结尾的字符串
SHF_STRINGS0x20表示节包含以空字符结尾的字符串
  • sh_info
    节的附加信息 sh_link 和 sh_info 是与节的类型有关的
类型sh_linksh_info
SHT_DYNAMIC本节用到的字符串所在节的索引0
SHT_HASH本节用到的符号表所在节的索引0
SHT_REL本节用到的符号表所在节的索引表示重定位节未来会修改的节的索引
SHT_SYMTAB/SHT_DYNSYM本节用到的字符串所在节的索引符号表中本地(LOCAL)符号最大索引值加1

ELF Relocation Table

重定位表的作用是找到 待重定位的指令的位置待重定位指令使用到的符号地址,在编译链接时或动态链接时进行重定位,主要是通过r_offset重定位基地址 可以计算出 需要重定位的指令所在地址,再通过 r_info 可以从符号节中找到对应的符号表项,最终找到符号所在的地址,再根据重定位项指明的 重定位类型 进行指令的修改。

使用如下命令查询文件的 ELF relocation table

>readelf -r ./app.o

Relocation section '.rel.text' at offset 0xa14 contains 35 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000000bc  00000402 R_ARM_ABS32       00000000   .bss
000000c0  00002802 R_ARM_ABS32       00000101   device_info_get
000000c4  00002902 R_ARM_ABS32       00000000   msleep
...
00000228  00002702 R_ARM_ABS32       00000000   memset
0000022c  00000202 R_ARM_ABS32       00000000   .rodata
00000230  00001902 R_ARM_ABS32       00000000   printf
00000234  00000202 R_ARM_ABS32       00000000   .rodata
00000238  00001c02 R_ARM_ABS32       00000000   memcpy
0000023c  00000202 R_ARM_ABS32       00000000   .rodata
00000240  00000202 R_ARM_ABS32       00000000   .rodata
00000244  00002a02 R_ARM_ABS32       00000000   snprintf
00000248  00002c02 R_ARM_ABS32       00000000   strlen
0000024c  00000202 R_ARM_ABS32       00000000   .rodata

代码中的结构体如下所示:

/* r_info - get symbol table index affected by relocation */
#define ELF32_R_SYM(i)          ((i) >> 8)
/* r_info - get relocation type */
#define ELF32_R_TYPE(i)         ((i)& 0xff)

/* ELF32 Relocation entry without implicit addend */
typedef struct elf32_rel {
    /*重定位偏移,重定位文件:受重定位作用的存储单元(即需要指令修正的位置)在节中的偏移量,可执行/共享目标文件:被重定位的指令的虚拟地址*/
    Elf32_Addr r_offset;                        /* offset of relocation */
    /*重定位信息,指出重定位项使用的符号在符号表中的索引,也给出了重定位的类型*/
    Elf32_Word r_info;                          /* symbol table index and type */
    /*加数是隐含在被修改的位置里*/
} Elf32_Rel;

/* ELF32 Relocation entry with explicit addend */
typedef struct elf32_rela {
    Elf32_Addr  r_offset;                       /* offset of relocation */
    Elf32_Word  r_info;                         /* symbol table index and type */
    Elf32_Sword r_addend;                       /* an addend */
} Elf32_Rela;

ARM重定位类型

名称类型重定位方式计算
0R_ARM_NONENONEALLNONE
2R_ARM_ABS32静态数据重定位(S + A) | T
10R_ARM_THM_CALL静态Thumb32指令重定位((S + A) | T) – P
21R_ARM_GLOB_DAT动态数据重定位(S + A) | T
22R_ARM_JUMP_SLOT动态数据重定位(S + A) | T
23R_ARM_RELATIVE动态数据重定位B(S) + A
27R_ARM_PLT32静态ARM指令重定位((S + A) | T) – P
28R_ARM_CALL静态ARM指令重定位((S + A) | T) – P
29R_ARM_JUMP24静态ARM指令重定位((S + A) | T) – P
30R_ARM_THM_JUMP24静态Thumb32指令重定位((S + A) | T) – P
38R_ARM_TARGET1静态数据重定位(S + A) | T
40R_ARM_V4BX静态杂项重定位xxx
42R_ARM_PREL31静态数据重定位((S + A) | T) – P
43R_ARM_MOVW_ABS_NC静态ARM指令重定位((S + A) | T) – P
44R_ARM_MOVT_ABS静态ARM指令重定位S + A
47R_ARM_THM_MOVW_ABS_NC静态Thumb32指令重定位S + A
48R_ARM_THM_MOVT_ABS静态Thumb32指令重定位(S + A) | T

重定位的类型分为静态和动态重定位方式:

  • 静态重定位用于在静态链接中使用

  • 动态重定位用于在动态链接时使用

重定位的方式决定了是对数据(符号地址)进行寻找还是涉及对指令进行重定位,主要有以下方式:

  • 数据重定位:这种类型的重定位不涉及操作指令,只是对待重定位地址下存储的地址偏移数据进行修改,例如:
// weak_symbol_test 中只是进行了 my_printf("%s\r\n", "hello");的打印
00000000 <weak_symbol_test>:
0: 4801        ldr r0, [pc, #4]  ; (8 <weak_symbol_test+0x8>)
2: 4b02        ldr r3, [pc, #8]  ; (c <weak_symbol_test+0xc>)
4: 4718        bx  r3
   6: bf00        nop
   8: 00000018  .word 0x00000018
       8: R_ARM_ABS32  .rodata
   c: 00000000  .word 0x00000000
       c: R_ARM_ABS32  my_printf

ldr r0, [pc, #4] 是在 Thumb 模式下基于 PC 位置的相对寻址,意思的是将 PC 中的值加 4 作为新地址,取该内存地址上的数据,保存到r0寄存器,其中 [pc, #4] 是获取 PC+4 地址下的内容,PC寄存器中的值 = 当前地址 + 4(Thumb模式下+4,ARM模式下+8),所以是获取地址标签 8 下的内容,此处内容还需要通过 R_ARM_ABS32 方式的计算才能得到符号的真正位置,其实就是获取只读字符串**"hello"**的首地址。

ldr r3, [pc, #8] 则是获取符号 my_printf 的地址到 r3 寄存器中,最后使用 bx 来调用 my_printf 函数。

  • ARM指令重定位:这种重定位类型主要是对 ARM 模式下的指令所包含操作数进行重定位,例如BL/BLX,ADD, SUB等指令下的立即数,只有修正了立即数才能正确的跳转到符号所在的位置。
  • Thumb指令重定位:这种重定位类型主要是对 Thumb 模式下的指令所包含操作数进行重定位,例如BL/BLX,ADD, SUB等指令下的立即数,只有修正了立即数才能正确的跳转到符号所在的位置。

ELF Symbol Table

使用如下命令查询文件的 ELF symbol table,app.o不存在引用其他共享库的符号所以不存在 .dynsym 动态符号表

>readelf -s app.o
Symbol table '.symtab' contains 47 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 
     3: 00000000     0 SECTION LOCAL  DEFAULT    4 
     4: 00000000     0 SECTION LOCAL  DEFAULT    5 
     5: 00000000     0 SECTION LOCAL  DEFAULT    6 
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 
...
    39: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND memset
    40: 00000101    96 FUNC    GLOBAL DEFAULT    1 device_info_get
    41: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND msleep
    42: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND snprintf
    43: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND device_state_get
    44: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strlen
    45: 00000000     0 NOTYPE  GLOBAL DEFAULT    4 __data_start
    46: 00000004     4 OBJECT  GLOBAL DEFAULT  COM gIrqVal

代码中的结构体如下所示:

/* st_info - get symbol binding type for use in program headers */
#define ELF_ST_BIND(info)       ((info) >> 4)
/* st_info - get symbol type for use in section headers */
#define ELF_ST_TYPE(info)       ((info) & 0xf)

/* ELF32 Symbol Table Entry */
typedef struct elf32_sym {
    /*符号名称,实际是一个索引,指向.strtab 字符串表某个位置*/
    Elf32_Word      st_name;                    /* name - index into string table */
    /*符号值,可执行/共享库文件:为符号所在的虚拟地址;重定位文件:是节内地址偏移,所在节由st_shndx给出,如果st_shndx=SHN_COMMON,则表示对齐字节*/
    Elf32_Addr      st_value;                   /* symbol value */
    /*符号的大小*/
    Elf32_Word      st_size;                    /* symbol size */
    /*符号信息:包括符号绑定,符号类型,符号信息*/
    Elf_Byte        st_info;                    /* type and binding */
    /*未使用,默认为0*/
    Elf_Byte        st_other;                   /* 0 - no defined meaning */
    /*符号所在节的节头表索引,指向节头表的某个位置(或特殊索引), 可执行/共享库文件中此值无效*/
    Elf32_Half      st_shndx;                   /* section header index */
} Elf32_Sym;

符号表主要存在于 .dynsym 节和 .symtab节中,用于存放用到的全局变量名称和函数名称,由于局部变量不需要被其他模块访问所以无需放入符号表,在执行时只需要将使用到的局部变量处理后放入寄存器即可。
.dynsym节是 .symtab节的一个子集,主要存放运行时动态链接过程中用到的全局变量和函数,在运行时是需要加载到内存中供动态链接器进行重定位使用,而 .symtab 节存放不仅存放了全局符号,还保留了一些本地符号(static 限定的函数或变量),用于编译过程中连接器进行符号重定位,编译完成后,可执行文件中可以不包括 .symtab 节。
对应静态链接来说,.symtab符号表中存放了所有链接时输入文件的符号信息,而不是存放符号内容,符号表的存在是为了让我们能够更方便的找到符号,符号表在链接过程重定位完成后就可以从静态可执行文件中删除。
而对于动态链接来说,完成在编译链接时完成静态链接类似的步骤后,只会保留动态重定位表.dynsym,用于运行时给动态连接器使用。

  • st_info

    绑定类型:

名称说明
STB_LOCAL1在当前文件中定义的本地符号(static修饰的函数和变量),其他模块不可见
STB_GLOBAL2是全局的变量或者是函数,在链接后其他模块可以引用
STB_WEAK3是显示申明的弱符号,可以被同名强符号覆盖

​ 符号类型:

名称说明
STT_NOTYPE0未指定的符号类型
STT_OBJECT1数据类型符号,例如变量 数组等
STT_FUNC2函数类型符号
STT_SECTION3与节相关的符号类型
STT_FILE4文件名相关的符号类型
STT_COMMON5未初始化化的全局数据类型符号
  • st_shndx

    特殊节头表索引:

名称说明
STN_UNDEF0表明符号在当前文件中没有定义,需要再链接过程中寻找具体符号地址
STN_ABS0xfff1表明符号是一个绝对值,在重定位过程中,此值不需要改变
STN_COMMON0xfff2表明符号是一个未分配地址的全局未初始化变量,在链接过程中链接器分配地址

ELF String Table

字符串表可以通过如下命令查看,包括符号字符串表(.strtab) 和动态符号字符串表(.dynstr),其中的内容是符号的字符串名称,包含多个以 '/0' 结尾的字符串,通过符号表中的 st_name 索引进行符号字符串获取。

> readelf -x .strtab ./app.o
> readelf -x .dynstr ./app.so

ELF Dynamic section

使用如下命令查询文件的 ELF dynamic section

> readelf -d ./out/dlapp.so

Dynamic section at offset 0x518 contains 13 entries:
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libdlapp11.so]
0x7ffffffd (AUXILIARY)                  Auxiliary library: [PIC]
0x00000004 (HASH)                       0x94
0x00000005 (STRTAB)                     0x284
0x00000006 (SYMTAB)                     0x134
0x0000000a (STRSZ)                      214 (bytes)
0x0000000b (SYMENT)                     16 (bytes)
0x00000011 (REL)                        0x35c
0x00000012 (RELSZ)                      112 (bytes)
0x00000013 (RELENT)                     8 (bytes)
0x00000016 (TEXTREL)                    0x0
0x6ffffffa (RELCOUNT)                   6	
0x00000000 (NULL)                       0x0


>readelf -S ./out/dlapp.so

There are 15 section headers, starting at offset 0xa8c:

Section Headers:
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .hash             HASH            00000094 000094 0000a0 04   A  2   0  4
[ 2] .dynsym           DYNSYM          00000134 000134 000150 10   A  3   3  4
[ 3] .dynstr           STRTAB          00000284 000284 0000d6 00   A  0   0  1
[ 4] .rel.dyn          REL             0000035c 00035c 000070 08   A  2   0  4
[ 5] .text             PROGBITS        000003cc 0003cc 000098 00  AX  0   0  4
[ 6] .rodata           PROGBITS        00000464 000464 0000b1 01 AMS  0   0  1
[ 7] .dynamic          DYNAMIC         0000051c 000518 000088 08  WA  3   0  4
[ 8] .got              PROGBITS        000005a4 0005a0 00000c 04  WA  0   0  4
[ 9] .bss              NOBITS          000005b0 0005ac 000408 00  WA  0   0  4
[10] .comment          PROGBITS        00000000 0005ac 00007e 01  MS  0   0  1
[11] .ARM.attributes   ARM_ATTRIBUTES  00000000 00062a 00003d 00      0   0  1
[12] .symtab           SYMTAB          00000000 000668 0002a0 10     13  24  4
[13] .strtab           STRTAB          00000000 000908 00010e 00      0   0  1
[14] .shstrtab         STRTAB          00000000 000a16 000074 00      0   0  1

代码中的结构体如下所示:

typedef struct {
    /* Tag 用于控制对d_un 的解析*/
    Elf32_Sword d_tag;							/* entry tag value */
    union {
        Elf32_Word d_val;					 	/* value type */	
        Elf32_Addr d_ptr;						/* addr type */
    } d_un;
} Elf32_Dyn;

动态链接信息节存在于共享类型的重定位文件和动态链接的可执行文件中,需要载入内存中,由动态连接器在运行时使用,包括依赖于哪些共享目标文件动态连接符号表的位置动态连接重定位表的位置等,可以用于动态重定位前进行关键信息的获取。

  • Tag名称
Tagd_un说明
DT_NULL0NULL表示动态信息表结束
DT_NEEDED1d_val指明所需库的名称在字符串表中的索引
DT_PLTRELSZ2d_val.rel.plt 函数链接重定位节总大小
DT_PLTGOT3d_ptr表示动态链接器中GOT表的地址或PLT的地址
DT_HASH4d_ptr.hash 符号哈希表节虚拟地址
DT_STRTAB5d_ptr.dynstr 动态符号字符串表节的虚拟地址
DT_SYMTAB6d_ptr.dynsym 动态符号表节的虚拟地址
DT_RELA7d_ptr同 DT_REL
DT_RELASZ8d_val同 DT_RELSA
DT_RELAENT9d_val同 DT_RELENT
DT_STRSZ10d_val.dynstr 动态符号字符串表节的总大小
DT_SYMENT11d_val.dynsym 动态符号表节中每个项的大小
DT_INIT12d_ptr.init 构造节函数的虚拟地址
DT_FINI13d_ptr.finit 析构节函数的虚拟地址
DT_SONAME14d_val共享目标在字符串表中的偏移
DT_RPATH15d_val用于库文件文件的路径径名在字符串表中的偏移
DT_REL17d_ptr.rel.dyn 动态重定位表的虚拟地址
DT_RELSZ18d_val.rel.dyn 动态重定位表的大小
DT_RELENT19d_val.rel.dyn 动态重定位表中每个项的大小
DT_PLTREL20d_val.rel.plt 函数链接表应用的重定位项的类型,REL/RELA
DT_TEXTREL22NULL如果存在表示在只读段在重定位中可以通过连接器进行修改
DT_JMPREL23d_ptrrel.plt 函数链接重定位表节的虚拟地址

ELF Program Header Table

一个目标文件的程序段包含一个或者多个节区。程序的头部只有对于可执行文件共享目标文件有意义
使用如下命令查询文件的 ELF program header table

> readelf -l ./out/app.bin 

Elf file type is EXEC (Executable file)
Entry point 0x405710
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R      0x8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000008e3b8 0x000000000008e3b8  R E    0x200000
  LOAD           0x000000000008e870 0x000000000068e870 0x000000000068e870
                 0x0000000000002130 0x0000000000017738  RW     0x200000
  DYNAMIC        0x000000000008fdb8 0x000000000068fdb8 0x000000000068fdb8
                 0x0000000000000220 0x0000000000000220  RW     0x8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x000000000007e7d0 0x000000000047e7d0 0x000000000047e7d0
                 0x0000000000003204 0x0000000000003204  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x000000000008e870 0x000000000068e870 0x000000000068e870
                 0x0000000000001790 0x0000000000001790  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .data.rel.ro .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .data.rel.ro .dynamic .got 

代码中的结构体如下所示

/* ELF32 Program Header */
typedef struct elf32_phdr {
    /*段类型:PT_LOAD=1 可加载的段*/
    Elf32_Word p_type;                          /* segment type */
    /*从文件头到该段的偏移*/
    Elf32_Off  p_offset;                        /* segment offset */
    /*该段被放到进程中内存中的虚拟地址*/
    Elf32_Addr p_vaddr;                         /* virtual address of segment */
    /*在linux中这个成员没有任何意义,值与p_vaddr相同*/
    Elf32_Addr p_paddr;                         /* physical address - ignored? */
    /*该段在文件映像中所占的字节数,一般p_memsz>=p_filesz,.bss段在内存中占用空间*/
    Elf32_Word p_filesz;                        /* number of bytes in file for seg. */
    /*该段在内存映像中占用的字节数*/
    Elf32_Word p_memsz;                         /* number of bytes in mem. for seg. */
    /*段标志*/
    Elf32_Word p_flags;                         /* flags */
    /*p_vaddr是否对齐*/
    Elf32_Word p_align;                         /* memory alignment */
} Elf32_Phdr;

段是从程序运行的视角来看待 ELF文件,每个段包括了多个节区,不同节区有不同的类型,程序段仅存在于共享类型的重定位文件和可执行文件中。

  • p_type
名称说明
PT_NULL0未定义的程序类型
PT_LOAD1表示是一个可装载的段,所有此类型的段按p_vddr升序排列,需要载入内存
PT_DYNAMIC2表示是动态连接的信息,包含.dynamic
PT_INTERP3表示ELF解析器路径,只存在于动态可执行文件中
  • p_flags
名称说明
PF_X1执行权限
PF_W2写权限
PF_R4读权限,例如.dynsym .dynstr仅有只读权限
PF_R+PF_X5读权限,例如.text .plt具有读和执行权限
PF_R+PF_W6读权限,例如.got .data .bss具有读写权限
PF_R+PF_W+PF_X7读权限

表中的虚拟地址说明

在如上的各种表中涉及到一些虚拟地址,并不是映射到内后的真实虚拟地,如下所示:

ELF头中的 Elf32_Ehdr->e_entry(程序入口虚拟地址)

节区表中 Elf32_Shdr->sh_addr (节映射到内存的虚拟地址)

符号表中的 Elf32_Sym->st_value (对于可执行/共享文件指向符号所在虚拟地址)

重定位表中 Elf32_Rel->r_offset (对于可执行/共享文件指向需要重定位的指令地址)

程序头表中 Elf32_Phdr->p_vaddr (对于可执行/共享文件指向该段在内存中的虚拟地址)

这些虚拟地址不是在进程中的真实地址,只是用来在重定位时进行偏移量的计算,例如

  • 通过节区计算程序真实入口地址:重定位基地址 + (e_entry - .text 节的sh_addr)
  • 通过程序段计算程序真实入口地址:重定位基地址 + (e_entry - LOAD段的低p_vaddr)
  • 共享/可执行文件中计算需要重定位的指令地址:重定位基地址 + r_offset
  • 共享/可执行文件中计算符号地址:重定位基地址 + (st_value - LOAD段的低p_vaddr)
文章作者: 路西法
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 以梦为马
ELF解析 elf
喜欢就支持一下吧