ELF

解析ELF文件(一)

ELF Header

Posted by 婷 on October 10, 2021 本文总阅读量

ELF文件

ELFExecutable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。

可以利用file命令来判断一个文件是不是ELF格式,比如我以可执行文件uxplay为例子

1

ELF格式

常见的ELF格式如下图所示,左边为链接视图,右边为执行视图。

img

  • 左边是ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。
  • segmentssections是包含的关系,一个segment包含若干个section。当ELF文件被操作系统加载到内存中后(加载到内存中也就是说这个elf要运行),系统会将多个具有相同权限(flg值)section合并成一个segment(优化空间利用),减少内存碎片。

区分

  • 节(section)
    • 平常我们说.text.bss.data这些都是section层面上的,比如.text,告诉汇编器后面的代码放入.text section中。
    • .text:保存程序代码,权限只读
    • .data:保存已经初始化的全局变量和局部静态变量
    • .bss: 保存未初始化的全局变量和局部静态变量
    • 目标代码文件中的sectionsection header table中的条目是一一对应的。section的信息用于链接器对代码重定位。
  • 段(segment)
    • 平常说的代码段,数据段则是segment层面上的
    • 目标代码中的section会被链接器组织到可执行文件的各个segment中。.text section的内容会组装到代码段中,.data, .bss等节的内容会包含在数据段中。
    • 而文件载入内存执行时,是以segment组织的,每个segment对应ELF文件中program header table中的一个条目,用来建立可执行文件的进程映像。

现在以我的test.c编译生成的目标文件test.o跟可执行文件test为例,用readelf命令来初步展现下链接视图跟执行视图的些许不同(代码在文章后面有)

先介绍下readelf的用法

2

  • readelf -h:显示elf文件的头部
  • readelf -l:显示elf文件的segment
  • readelf -S:显示elf文件的section

readelf -l test.o 结果为目标文件中没有程序头,也就是没有段

3

readelf -l test 则都打印出来

4

copyright@copyright-Vostro-3559:~/code_elf$ readelf -l test

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

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000005b8 0x00000000000005b8  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x000000000000020d 0x000000000000020d  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000170 0x0000000000000170  R      0x1000
  LOAD           0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000270 0x0000000000000278  RW     0x1000
  DYNAMIC        0x0000000000002dc0 0x0000000000003dc0 0x0000000000003dc0
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x0000000000002028 0x0000000000002028 0x0000000000002028
                 0x000000000000003c 0x000000000000003c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000250 0x0000000000000250  R      0x1

 Section to Segment mapping:
  段节...
   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 
   03     .init .plt .plt.got .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.ABI-tag .note.gnu.build-id 
   08     .eh_frame_hdr 
   09     
   10     .init_array .fini_array .dynamic .got 

readelf -S test.o

copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test.o
There are 15 section headers, starting at offset 0x448:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000004e  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000002f8
       00000000000000a8  0000000000000018   I      12     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000090
       0000000000000004  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  00000094
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000094
       0000000000000023  0000000000000000   A       0     0     1
  [ 6] .data.rel.local   PROGBITS         0000000000000000  000000b8
       0000000000000008  0000000000000000  WA       0     0     8
  [ 7] .rela.data.rel.lo RELA             0000000000000000  000003a0
       0000000000000018  0000000000000018   I      12     6     8
  [ 8] .comment          PROGBITS         0000000000000000  000000c0
       000000000000002c  0000000000000001  MS       0     0     1
  [ 9] .note.GNU-stack   PROGBITS         0000000000000000  000000ec
       0000000000000000  0000000000000000           0     0     1
  [10] .eh_frame         PROGBITS         0000000000000000  000000f0
       0000000000000038  0000000000000000   A       0     0     8
  [11] .rela.eh_frame    RELA             0000000000000000  000003b8
       0000000000000018  0000000000000018   I      12    10     8
  [12] .symtab           SYMTAB           0000000000000000  00000128
       0000000000000198  0000000000000018          13    10     8
  [13] .strtab           STRTAB           0000000000000000  000002c0
       0000000000000035  0000000000000000           0     0     1
  [14] .shstrtab         STRTAB           0000000000000000  000003d0
       0000000000000076  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)

但是readelf -S test还是会显示section的信息,但是section headers的数量要比上面的多

copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test
There are 29 section headers, starting at offset 0x39b0:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         00000000000002a8  000002a8
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             00000000000002c4  000002c4
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             00000000000002e4  000002e4
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000308  00000308
       0000000000000024  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000000330  00000330
       00000000000000c0  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000000003f0  000003f0
       0000000000000089  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000000047a  0000047a
       0000000000000010  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000000490  00000490
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             00000000000004b0  000004b0
       00000000000000d8  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000000588  00000588
       0000000000000030  0000000000000018  AI       5    22     8
  [11] .init             PROGBITS         0000000000001000  00001000
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000001020  00001020
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000001050  00001050
       0000000000000008  0000000000000008  AX       0     0     8
  [14] .text             PROGBITS         0000000000001060  00001060
       00000000000001a1  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000001204  00001204
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000002000  00002000
       0000000000000027  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000002028  00002028
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000002068  00002068
       0000000000000108  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000003db0  00002db0
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000003db8  00002db8
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000003dc0  00002dc0
       00000000000001f0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000003fb0  00002fb0
       0000000000000050  0000000000000008  WA       0     0     8
  [23] .data             PROGBITS         0000000000004000  00003000
       0000000000000020  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000004020  00003020
       0000000000000008  0000000000000000  WA       0     0     4
  [25] .comment          PROGBITS         0000000000000000  00003020
       000000000000002b  0000000000000001  MS       0     0     1
  [26] .symtab           SYMTAB           0000000000000000  00003050
       0000000000000648  0000000000000018          27    44     8
  [27] .strtab           STRTAB           0000000000000000  00003698
       000000000000021a  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  000038b2
       00000000000000fe  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文件(一):ELF Header

ELF的结构声明位于系统头文件 elf.h 中,在/usr/include目录下,ELF格式分为32位与64位两种,下面以64位为例。

ELF Header

结构体Elf64_Ehdr代表elf的头部,头部可以提供的信息如下所示。

5

ELF header字节布局

readelf -h也可查看头部信息

6

e_ident
unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */

包含了Maigc Number和其它信息,共16字节。16个字节在这个头文件中都有定义,而且实际上readelf -h也把这16个包含的信息显示出来了。

0~3:前4字节为Magic Number,固定为ELFMAG ,也即下图的\177ELF。刚好对应readelf读出来的7f 45 4c 46,刚好0x45 0x4c 0x46的十进制为 69 76 70 ,刚好ASCII码对应的就是ELF

7

4(EI_CLASS)ELFCLASS32代表是32位ELFELFCLASS64 代表64ELFreadelf读出来的是0x02,代表64位。

8

5(EI_DATA)ELFDATA2LSB代表小端,ELFDATA2MSB代表大端。readelf读出来的是0x01,代表小端。

9

6(EI_VERSION):固定为EV_CURRENT(1)readelf读出来的是0x01

10

11

7(EI_OSABI):操作系统ABI标识。readelf读出来的是0x00,而且readelf同时也打印出来了。

12

13

8(EI_ABIVERSION)ABI版本(实际未使用)。

9~15:对齐填充,无实际意义。都是0x00

14

e_type
Elf64_Half	e_type;			/* Object file type */

代表文件类型

15

  • ET_REL:可重定位文 件(如目标文件)
  • ET_EXEC:可执行文件(可直接执行的文件)
  • ET_DYN:共享目标文件(如SO库)
  • ET_CORE:Core文件(吐核文件)

注意GCC使用编译选项 -pie 编译的可执行文件实际也是DT_DYN类型。

16

17

e_machine
Elf64_Half	e_machine;		/* Architecture */

代表架构的信息,头文件里面定义了200多种类型。

18

test.otest利用readelf -h读出来的都是Advanced Micro Devices X86-64

19

e_verison
Elf64_Word	e_version;		/* Object file version */

文件版本,目前常见的ELF 文件版本均为EV_CURRENT(1)

20

e_entry
 Elf64_Addr	e_entry;		/* Entry point virtual address */

入口虚拟地址

readelf -h test.o

 入口点地址:               0x0

readelf -h test

  入口点地址:               0x1060
e_phoff
Elf64_Off	e_phoff;		/* Program header table file offset */

段表文件偏移

` readelf -l test`

Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 11 program headers, starting at offset 64
e_shoff
Elf64_Off	e_shoff;		/* Section header table file offset */

节表文件偏移

readelf -S test.o

copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test.o
There are 15 section headers, starting at offset 0x448:

readelf -S test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test
There are 29 section headers, starting at offset 0x39b0:
e_flags
Elf64_Word	e_flags;		/* Processor-specific flags */

处理器特定的标志,一般为0

e_ehsize
Elf64_Half	e_ehsize;		/* ELF header size in bytes */

Elf_Header的大小(字节),64位则为64,如果是32位则为52。正如前面的这张图所示。

ELF header字节布局

32位程序

21

22

64位程序

23

e_phentsize
Elf64_Half	e_phentsize;		/* Program header table entry size */

段头(Program Header)的大小(字节)

readelf -h test.o

24

readelf -h test

25

e_phnum
Elf64_Half	e_phnum;		/* Program header table entry count */

段的数量。

readelf -l test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -l test

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

readelf -h test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.........
  Number of program headers:         11
.........
e_shentsize
Elf64_Half	e_shentsize;		/* Section header table entry size */

节头(Section Header)的大小(字节)。因为test也是由test.o而来的,所以section的大小应该是一样的,但是section的数量不一定是一样的。

个人看法,因为,当ELF文件被操作系统加载到内存中后(加载到内存中也就是说这个elf要运行),系统会将多个具有相同权限(flg值)section合并成一个segment(优化空间利用),在这个过程中section的数量可能会发生改变。

readelf -h test.o

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
  节头大小:         64 (字节)
.............

readelf -h test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
  节头大小:         64 (字节)
.............
e_shnum
Elf64_Half	e_shnum;		/* Section header table entry count */

节头数量

readelf -h test.o

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
  节头数量:         15
.............

readelf -h test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
  节头数量:         29
.............
e_shstrndx
Elf64_Half	e_shstrndx;		/* Section header string table index */

节字符串表的节索引。

readelf -h test.o

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
  字符串表索引节头: 14
.............

readelf -h test

copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
  字符串表索引节头: 28
.............

代码

编译指令

gcc -c test.c -o test.o

test.c

#include <stdio.h>

int a = 1;
int b;
char *p = "123";

int main(void)
{
	int c = 3;
	b = 2;
	printf("hello\n");
	printf("a is %d,b is %d,c is %d\n",a,b,c);
	return 0;
}

参考链接

其他想法

本来想直接写段代码读出来elf二进制的内容,照着二进制的内容去分析,会更加具体一点,但是自己的代码总是有问题,后来怕耽误时间就没去做,以后有空再做。