[一天一个命令]ELF目标文件与readelf

readelf elf文件格式分析

背景

目标文件

首先需要介绍的概念是目标文件(Object file)的概念。目标文件是计算机科学中编译器或汇编器处理源代码后所生成的代码(目标代码,Object code)的计算机文件,它常被称作二进制文件(binaries)。这个文件类型主要是区别于你看得懂的用人话写的代码文件(.c、.cpp etc.)、中间文件(.i)、汇编文件(.s)。常见的.exe、.dll、.so啥的都算目标文件。

目标文件有三种类型:

  • 可重定位的对象文件(Relocatable file)
    由汇编器汇编生成的 .o 文件(下面会详细讲到)
  • 可执行的对象文件(Executable file)
    可执行应用程序
  • 可被共享的对象文件(Shared object file)
    动态库文件

编译过程

编译的过程如下图所示
编译过程

代码文件经过语言预处理器、编译器、汇编器和链接器处理,最终生成可执行目标文件。

下面以一个简单的c语言文件为例:

sum.c源文件内容如下:

1
2
3
4
5
6
7
8
9
int sum(int *a, int n)
{
int i, s = 0;

for (i = 0; i < n; ++i) {
s += a[i];
}
return s;
}

通过运行C预处理器(cpp)可以生成sum.i中间文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1 "sum.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "sum.c"
int sum(int *a, int n)
{
int i, s = 0;

for (i = 0; i < n; ++i) {
s += a[i];
}
return s;
}

通过运行C编译器(cc1),将中间文件生成为sum.s汇编文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  .file›"sum.i"
.text
.globl sum
.type›sum, @function
sum:
.LFB0:
.cfi_startproc
movl $0, %eax
movl $0, %edx
jmp›.L2
.L3:
movslq %edx, %rcx
addl (%rdi,%rcx,4), %eax
addl $1, %edx
.L2:
cmpl %esi, %edx
jl .L3
rep ret
.cfi_endproc
.LFE0:
.size›sum, .-sum
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits

最终生成通过汇编器(as)生成一个可重定位目标文件(reloacatable object file)。

什么是ELF

系统里的目标文件是按照特定的目标文件格式来组织的,各个系统的目标文件格式都不相同。

从贝尔实验室诞生的第一个Unix系统使用的是a.out格式(直到今天,可执行文件仍然称为a.out文件)。Windows使用可移植可执行(PortableExecutable,PE)格式。Mac OS-X使用Mach-O格式。现代x86-64Linux和Unix系统使用可执行可链接格式(Executable and Linkable Format,ELF)。

ELF格式的文件在Linux系统下有.axf、 .bin、 .elf、 .o、 .prx、 .puff、 .ko、 .mod和.so等等

readelf指令

前面介绍了这么多ELF的背景知识,下面回来来说说readelf这个指令。这个指令正是用来查看目标文件的内容的。

ELF可重定位目标文件的格式

典型格式

典型的ELF可重定位目标文件的格式如下图:
ELF可重定位目标文件格式

其中:

  • .text 节里装载了程序的可执行机器码
  • .rodata 节里装载了只读数据
  • .data 节里面装载了被初始化的数据,包括全局和静态C变量
  • .bss 节里面装载了未被初始化的全局和静态C变量(在目标文件中只是占位符,不占空间)
  • .symtab 或者 .dynsym 节里面装载了符号信息
  • 以 .rel 打头的 节里面装载了重定位条目
  • .debug 一个调试符号表,只有使用了-g参数编译时才会有,用于debug
  • .line 用于记录C源程序的行号和.text节中机器指令之间的映射,也是只有使用了-g参数编译时才会有
  • .strtab 或者 .dynstr 节里面装载了字符串信息(以null结尾的字符串信息)

符号表部分解析

符号表每节定义如下:

1
2
3
4
5
6
7
8
9
typedef struct { 
int name; /* String table offset */
char type:4, /* Function or data (4 bits) */
binding:4; /* Local or global (4 bits) */
char reserved; /* Unused */
short section; /* Section header index */
long value; /* Section offset or absolute address */
long size; /* Object size in bytes */
} Elf64_Symbol;

具体解释如下:
符号表思维导图

举个例子🌰

以上面的sum.c生成的sum.o为例,我们选取readelf-all参数输出全部内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
$readelf -all sum.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 536 (bytes into file)
Flags: 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: 11
Section header string table index: 10

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
000000000000001b 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
1 int sum(int *a, int n)
2 {
[ 4] .comment PROGBITS 0000000000000000 0000005b
000000000000002e 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 00000089
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 00000090
0000000000000030 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 000001a8
0000000000000018 0000000000000018 I 8 6 8
[ 8] .symtab SYMTAB 0000000000000000 000000c0
00000000000000d8 0000000000000018 9 8 8
[ 9] .strtab STRTAB 0000000000000000 00000198
000000000000000b 0000000000000000 0 0 1
[10] .shstrtab STRTAB 0000000000000000 000001c0
0000000000000054 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)

There are no section groups in this file.

There are no program headers in this file.

Relocation section '.rela.eh_frame' at offset 0x1a8 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS sum.i
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 27 FUNC GLOBAL DEFAULT 1 sum

No version information found in this file.

其中第一部分是ELF头(ELF header)中的描述信息。(用-h参数可以单独得到)。

最后一部分是符号表部分(用-s参数可以单独得到该部分),前面八个条目是链接器内部使用的局部符号,最后一行是全局符号sum定义的条目。可以通过最后一行看出,它是一个位于.text节中偏移量为0处的27字节函数。(Ndx部分表示在哪个节中,1表示.text节,3表示.data节,对应上面输出的Section Headers部分)