可执行文件(ELF)格式的理解

     ELF文件原名Executable andLinking Format,译为“可执行可连接格式”。

     ELF规范中把ELF文件宽泛的称为“目标文件”,这与我们平时的理解不同。一般的,我们把编译但没有链接的文件(比如Linux下的.o文件)称为目标文件。而ELF文件仅指链接好的可执行文件。在ELF规范中,所用符合ELF规范的文件都成为ELF文件,也成为目标文件,这两个名字意义相同。

    经过编译但没有连接的文件则称为“可重定位文件 (relocatable file)”或“待重定位文件 (relocatable file)”。本文采用与ELF规范相同的命名方式,所以当提到可重定位文件时,一般可以理解为惯常所说的目标文件;而提到目标文件时,即指各种类型的 ELF文件。

   

ELF文件定义如下表格所示:

ELF文件

可重定位文件(relocatable file)

可重定位文件(relocatable file),用于与其它目标文件进行连接以构建可执行文件或动态链接库。可重定位文件就是常说的目标文件,由源文件编译而成,但还没有连接成可执行文件。在 UNIX系统下,一般有扩展名”.o”。之所以称其为“可重定位”,是因为在这些文件中,如果引用到其它目标文件或库文件中定义的符号(变量或者函数)的话,只是给出一个名字,这里还并不知道这个符号在哪里,其具体的地址是什么。需要在连接的过程中,把对这些外部符号的引用重新定位到其真正定义的位置上,所以称目标文件为“可重定位”或者“待重定位”的。

共享目标文件(shared object file)

即动态连接库文件。它在以下两种情况下被使用:第一,在连接过程中与其它动态链接库或可重定位文件一起构建新的目标文件;第二,在可执行文件被加载的过程中,被动态链接到新的进程中,成为运行代码的一部分。

可执行文件(executable file )

经过连接的,可以执行的程序文件。


ELF文件的作用有两个:一是用于构建程序,构建动态链接库或可执行程序等,主要体现在链接过程。二是用于运行程序。在这两种情况下,我们可以从不同的视角看待同一个目标文件。

从连接的角度和运行的角度,可以分别把目标文件的组成部分做以下划分:

 

1. ELF头文件

   位于文件最开始处,包含整个文件的结构信息。

2. 节(section)

   是专门用于连接过程而言的,在每个节中包含指令数据、符号数据、重定位数据等等。

3. 程序头表

   在运行过程中是必须的,在链接过程中是可选的,因为它的作用是告诉系统如何创建进程的映像。

4. 节头表

   包含文件中所用节的信息。

下面看一下Linux内核对ELF头文件的定义。

linux+v2.6.36/include/linux/elf.h:

 C++ Code 
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
/* 32-bit ELF base types. */
typedef __u32 Elf32_Addr;
typedef __u16 Elf32_Half;
typedef __u32 Elf32_Off;
typedef __s32 Elf32_Sword;
typedef __u32 Elf32_Word;

#define EI_NIDENT  16

typedef  struct elf32_hdr
{
     unsigned  char e_ident[EI_NIDENT];  //16字节的信息,下文详细解释
    Elf32_Half e_type;   //目标文件类型
    Elf32_Half e_machine;   //体系结构类型
    Elf32_Word e_version;   //目标文件版本
    Elf32_Addr e_entry;  /* Entry point 程序入口的虚拟地址*/
    Elf32_Off e_phoff;   //程序头部表的偏移量
    Elf32_Off e_shoff;   //节区头部表的偏移量
    Elf32_Word e_flags;   //
    Elf32_Half e_ehsize;   //ELF头部的大小
    Elf32_Half e_phentsize;   //程序头部表的表项大小
    Elf32_Half e_phnum;   //程序头部表的数目
    Elf32_Half e_shentsize;  //节区头部表的表项大小
    Elf32_Half e_shnum;  //节区头部表的数目
    Elf32_Half e_shstrndx;  //
} Elf32_Ehdr;   //此结构体一共52个字节

我使用Ultraedit打开一个ELF文件,来分析它的前52个字节:


e_ident[0]

7F

目标文件最开头的前四个字节。

文件标志,表示是ELF文件

EI_MAG0 = 0, EI_MAG1 = 1, EI_MAG2 = 2, EI_MAG3 = 3

e_ident[1]

45 == ‘E’

e_ident[2]

4C == ’L’

e_ident[3]

46 == ‘F’

e_ident[4]

01

文件类别,取值如下:(EI_CLASS = 4)

ELFCLASSNONE

0

非法目标文件

ELFCLASS32

1

32为目标文件

ELFCLASS64

2

64为目标文件

e_ident[5]

01

编码格式,取值如下:(EI_DATA = 5)

ELFDATANONE

0

非法编码格式

ELFDATA2LSB

1

小端编码格式

ELFDATA2MSB

2

大端编码格式

e_ident[6]

01

文件版本,(EI_VERSION),为1表明是目前版本

e_ident[7] 

to

e_ident[15]

00 00 00 00 00 00 00 00 00

这就个字节暂且不使用,留在以后扩展。

e_type

00 01

此字段表明文件属于那种类型:

ET_NONE

0

未知文件类型

ET_REL

1

可重定位文件

ET_EXEC

2

可执行文件

ET_DYN

3

动态链接库文件

ET_CORE

4

CORE文件

ET_LOPROC

0xff00

特定处理器文件扩展下边界

ET_HIPROC

0xffff

特定处理器文件扩展上边界

e_machine

00 03

体系结构类型 : Intel 80386

e_version

00 00 00 01

 目前版本

e_entry

00 00 00 00

 

e_phoff

00 00 00 00

没有程序头部表。

e_shoff

00 00 00 FC

e_ident[0]是文件的第一个字节,从它开始FC(252)字节后就是节的数据信息。

e_flags

00 00 00 00

 

e_ehsize

00 34

52字节的ELF头部结构体

e_phentsize

00 00

因为没有程序头部表,所以大小为0

e_phnum

00 00

同上

e_shentsize

00 28

40字节的节区头部表

e_shnum

00 0B

节区头部表的表项有B(11)个

e_shstrndx

00 08

 节头表中与节名字表相对应的表项的索引。

下面的一段程序用来读ELF文件的头部:readelf_h.c:


 C++ Code 
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FALSE  0
#define TURE  1
#define MAX_SIZE  52

typedef  short  int     Elf32_Half;
typedef  int         Elf32_Word;
typedef Elf32_Word     Elf32_Addr;
typedef Elf32_Word     Elf32_Off;
/*Elf头部文件部分重要数据*/
typedef  struct
{
    Elf32_Half e_type;
    Elf32_Half e_machine;
    Elf32_Word e_version;
    Elf32_Addr e_entry;  //程序入口的虚拟地址,如果目标文件没有程序入口,为0

    Elf32_Off e_phoff;  //程序头部表格的偏移量(按字节),如果文件中没有,为0

    Elf32_Off e_shoff;  //节区头部表格的偏移量(按字节),如果文件中没有,为0

    Elf32_Word e_flags;  //

    Elf32_Half e_ehsize;  //ELF头部的大小

    Elf32_Half e_phentsize;  //程序头部表格的表项大小。

    Elf32_Half e_phnum;  //程序头部表格的表项数目。

    Elf32_Half e_shentsize;  //节区头部表格的表项大小。

    Elf32_Half e_shnum;  //节区头部表格的表项数目。

    Elf32_Half e_shstrndx;
} Elf_lan;
static Elf_lan lan_elf;
int OpenElf( char *filename)
{
     int fd;
    fd = open(filename, O_RDONLY);
     if(fd == - 1)
    {
        printf( "Open %s Error!\n", filename);
         return FALSE;
    }
     return fd;
}
//读取Elf头部表函数 :int ReadElf(int fd);

int ReadElf( int fd)
{
     char str[MAX_SIZE];
     int num;
    memset(str,  0, MAX_SIZE);
     if((num = read(fd, str,  52)) !=  52)
    {
        perror( "File NO ELF!\n");
         return FALSE;
    }
     if((str[ 0] == 0x7f) && (str[ 1] ==  'E') && (str[ 2] ==  'L') && (str[ 3] ==  'F'))
    {
        printf( "This is ELF file.\n");
        printf( "文件类别: ");
         switch(str[ 4])
        {
         case  0:
            printf( "非法目标文件\n");
             break;
         case  1:
            printf( "32位目标文件\n");
             break;
         case  2:
            printf( "64位目标文件\n");
             break;
         default:
             break;
        }
        printf( "编码格式: ");
         switch(str[ 5])
        {
         case  0:
            printf( "非法编码格式\n");
             break;
         case  1:
            printf( "小端编码格式\n");
             break;
         case  2:
            printf( "大端编码格式\n");
             break;
         default:
             break;
        }
        printf( "文件版本: ");
         if(str[ 6] ==  1)
        {
            printf( "当前版本\n");
        }
         else
        {
            printf( "NULL\n");
        }
        printf( "目标文件类型: ");
        lan_elf.e_type = *((Elf32_Half *)&str[ 16]);
        printf( "e_type = %d\t", lan_elf.e_type);
         switch(lan_elf.e_type)
        {
         case  0:
            printf( "未知文件类型\n");
             break;
         case  1:
            printf( "可重定位文件类型\n");
             break;
         case  2:
            printf( "可执行文件\n");
             break;
         case  3:
            printf( "动态链接库文件\n");
             break;
         case  4:
            printf( "CORE文件\n");
             break;
         default:
             break;
        }
        printf( "体系结构为:");
        lan_elf.e_machine = *((Elf32_Half *)&str[ 18]);
        printf( "e_machine = %d\n", lan_elf.e_machine);
         switch(lan_elf.e_machine)
        {
         case  0:
            printf( "未知体系结构");
             break;
         case  3:
            printf( "Intel 8086");
        }
        printf( "版本信息: ");
        lan_elf.e_version = *((Elf32_Word *)&str[ 20]);
         if(lan_elf.e_version ==  1)
        {
            printf( "当前版本\n");
        }
         else
        {
            printf( "NULL\n");
        }
        printf( "程序入口的虚拟地址:");
        lan_elf.e_entry = *((Elf32_Word *)&str[ 24]);
        printf( "0x%x\n", lan_elf.e_entry);

        printf( "程序头部表格的偏移量(按字节): ");
        lan_elf.e_phoff = *((Elf32_Off *)&str[ 28]);
        printf( "0x%x, %d\n", lan_elf.e_phoff, lan_elf.e_phoff);

        printf( "节区头部表格的偏移量(按字节): ");
        lan_elf.e_shoff = *((Elf32_Off *)&str[ 32]);
        printf( "0x%x, %d\n", lan_elf.e_shoff, lan_elf.e_shoff);

        printf( "处理器标志位: ");
        lan_elf.e_flags = *((Elf32_Off *)&str[ 36]);
        printf( "%d\n", lan_elf.e_flags);

        printf( "ELF头文件大小: ");
        lan_elf.e_ehsize = *((Elf32_Half *)&str[ 40]);
        printf( "0x%x, %d\n", lan_elf.e_ehsize, lan_elf.e_ehsize);

        printf( "程序头部表大小: ");
        lan_elf.e_phentsize = *((Elf32_Half *)&str[ 42]);
        printf( "0x%x, %d\n", lan_elf.e_phentsize, lan_elf.e_phentsize);

        printf( "程序头部表的数目:");
        lan_elf.e_phnum = *((Elf32_Half *)&str[ 44]);
        printf( "0x%x, %d\n", lan_elf.e_phnum, lan_elf.e_phnum);

        printf( "节区头部表大小: ");
        lan_elf.e_shentsize = *((Elf32_Half *)&str[ 46]);
        printf( "0x%x, %d\n", lan_elf.e_shentsize, lan_elf.e_shentsize);

        printf( "节区头部表数目: ");
        lan_elf.e_shnum = *((Elf32_Half *)&str[ 48]);
        printf( "0x%x, %d\n", lan_elf.e_shnum, lan_elf.e_shnum);

        printf( "节头表与节名字相对应的表项的索引: ");
        lan_elf.e_shstrndx = *((Elf32_Half *)&str[ 50]);
        printf( "0x%x, %d\n", lan_elf.e_shstrndx, lan_elf.e_shstrndx);
         return TURE;
    }
     else
    {
        perror( "File NO ELF!\n");
         return FALSE;
    }
}

int main( int argc,  char *argv[])
{
     int boolen;
     if(argc ==  2)
    {
        boolen = OpenElf(argv[ 1]);
         if(boolen == FALSE)
        {
             return - 1;
        }
        ReadElf(boolen);
    }

     return  0;
}

$./readelf_lan readelf_lan.o
This is ELF file.
文件类别: 32位目标文件
编码格式:  小端编码格式
文件版本:  当前版本
目标文件类型: e_type = 1       可重定位文件类型
体系结构为:e_machine = 3
Intel 8086版本信息: 当前版本
程序入口的虚拟地址:0x0
程序头部表格的偏移量(按字节): 0x0, 0
节区头部表格的偏移量(按字节): 0xc00, 3072
处理器标志位: 0
ELF头文件大小: 0x34, 52
程序头部表大小: 0x0, 0
程序头部表的数目:0x0, 0
节区头部表大小: 0x28, 40
节区头部表数目: 0xc, 12
节头表与节名字相对应的表项的索引: 0x9, 9


你可能感兴趣的:(深入理解计算机系统)