elf是一种文件格式,用于存储linux程序,它内部有什么信息呢?大概包括编制好的计算机指令,数据,计算机在需要的时候把这个文件读取到内存中,cpu就可以从内存中一条一条的读取指令来执行。要想明白elf的格式,应该先了解下计算机执行程序需要哪些信息,对此我们先来回顾下计算机系统的一些基础知识:
虚拟内存
对于32位的总线,linux系统给每个进程分配了4g的空间,其中 0xC0000000到0xFFFFFFFF 这个地址段是留给系统使用的,主要用于linux内核程序的运行, 用户可以使用3GB的空间从(0x00000000-0xBFFFFFFF)。当然这4g的内存是虚拟内存,一个系统有那么多进程,要是物理内存那还得了,操作系统的虚拟性也是其一重大特征,使用虚拟内存的好处就是各个进程之间的运行空间看起来是独立的,彼此相互不干扰。那么一个进程的创建过程是什么样的呢?
程序执行过程
- 用户请求运行程序时,操作系统会读取存储在磁盘上的可执行文件,(对于linux系统,这个文件就是elf格式文件),然后为用户分配4g的虚拟内存空间。
- 根据可执行文件的信息指示,把不同的文件内容放到为你分配的这3g虚拟内存。
- 根据可执行文件的信息指示,系统设置代码段和数据段寄存器
- 根据可执行文件的信息指示,跳转到用户的代码入口地址(一般就是我们的main函数)
- 从main开始,cpu一条条执行我们给的指令,处理我们的数据,直到程序结束。
我们看到在执行指令前有多次“根据可执行文件提示”,而学习elf就是学习到底是指示了哪些信息。
可执行的elf文件
elf文件分三种类型: 目标文件(.o)、可执行文件、动态库(.so),可执行文件一般分成,elf文件头、segment表、section表。下面我们通过一个例子一一介绍下:
- elf文件头: 这个文件是对elf文件整体信息的描述,在32位系统下是56的字节,在64位系统下是64个字节。
- segment表:这个表是加载指示器。
.section .data
.global data_item
data_item:
.long 3,67,28
.section .text
.global _start
_start:
mov $1,%eax
mov $4,%ebx
int $0x80
上面是一段简单的汇编代码,编译: as -o hello.o hello.s 链接: ld -o hello hello.o 完成之后执行readelf -h hello (-h 表示读取elf文件头的命令)
readelf -h hello
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: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4000b0 // 程序的入口地址
Start of program headers: 64 (bytes into file)
Start of section headers: 472 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes) // 头长度是56字节(32位系统是56字节)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 6
Section header string table index: 3
对于程序的装载,我们关心这三项:
Entry point address: 0x4000b0 // 程序入口地址
Start of program headers: 64 (bytes into file) // segment表在文件64字节偏移处
Size of program headers: 56 (bytes)
上面内容告诉我们segment表在文件的64字节处,我们看看64字节处有什么内容:
执行readelf -l hello 输出segments信息,(-l 表示读取segment)
readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x4000b0
There are 2 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000bc 0x00000000000000bc R E 200000
LOAD 0x00000000000000bc 0x00000000006000bc 0x00000000006000bc
0x000000000000000c 0x000000000000000c RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
我们看到程序有两个segment,分别是.text和.data,
- .text的Offset是0,FileSiz是0x0,MemSiz是0xbc, VirtAddr是0x400000,Flags是R E,表示加载起将把elf文件中从0字节开始直到oxbc处的内容加载到虚拟内存中的0x400000处,占用0xbc长度的内存,设置该内存的权限是RE(可读,可执行),这一段的内容正好是elf头,segments table,和代码段,而elfheader的entry地址是0x4000b0,这个地址是代码段的起始地址。
- .data的Offset是0,FileSiz是0xbc,MemSiz是0x0c, VirtAddr是0x6000bc,Flags是R W,表示加载起将把elf文件中从bc字节开始直到oxbc + 0xc处的内容加载到虚拟内存中的0x6000bc处,占用0x0c长度的内存。设置该内存的权限是RE(可读,可执行)
这样系统就可以根据elf文件提供的这些信息创建进程了。
网友评论