卡卷网
当前位置:卡卷网 / 每日看点 / 正文

想学习Linux下的ELF文件有什么好书推荐吗?

作者:卡卷网发布时间:2024-11-20 21:17浏览数量:95次评论数量:0次

大家好,这里是物联网心球。

今天我们来聊聊ELF文件,了解一下Linux如何创建进程以及ELF文件如何转变成Linux进程?

1.什么是ELF文件?

ELF(Executable and Linkable Format)文件是一种目标文件格式,用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。它主要用于Linux平台,用于存储和传输可执行文件和库。

文件类型‌:

    • 可执行文件‌:包含可执行的机器代码,可直接运行。
    • 可重定位文件‌(.o文件):机器代码和数据地址相对,需重定位才能运行,通常用于编译过程。
    • 共享对象文件(.so文件)‌:动态链接库,包含可共享代码和数据,可在运行时被多个进程共享。
    • 核心转储文件‌(core文件):程序崩溃或异常终止时生成,包含内存状态和寄存器信息,用于调试。

往期精品文章推荐:

Linux高性能编程_malloc原理

Linux高性能编程_Reactor模型

Linux高性能编程_时间轮

Linux高性能编程_协程

Linux高性能编程_无锁队列

2.ELF文件格式

如下图所示,ELF文件主要由:ELF头,程序头表,节区,节头表组成。

    • ‌ELF头‌:包含文件的基本信息,如类型、架构、入口地址等。
    • ‌程序头表‌:描述可执行文件中的段(Segment)信息,如类型、偏移地址、大小等。
    • ‌节区:存储实际的代码、数据等信息。
    • ‌节头表‌:描述目标文件中的节(Section)信息,如名字、类型、偏移地址、大小等。

注意:.o文件没有程序头表,ELF文件并不一定有程序头表。

想学习Linux下的ELF文件有什么好书推荐吗?  第1张

段(Segment)和节(Section)有什么区别?

节是ELF文件的基本单位,包含了程序的代码,数据等信息。Linu系统为了高效加载ELF文件,将多个节划分为一个组(段),段是节的集合,Linux系统通过段加载代码(节)和数据(节)。

2.1 ELF头

ELF头是整个ELF文件的起始部分,位置固定,包含了识别和解释文件内容的关键信息。

查看ELF文件头信息:

#read -h ELF文件

root@raspberrypi:/home/mfn# readelf -h a.out 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: DYN (Position-Independent Executable file) Machine: AArch64 Version: 0x1 Entry point address: 0x600 Start of program headers: 64 (bytes into file) Start of section headers: 68504 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 29 Section header string table index: 28

ELF文件头字段解析:

  • Magic:魔数,ELF文件识别信息。
  • Class:文件类型,32位或64位。
  • Data:编码方式,大端或小端。
  • Version:ELF版本。
  • OS/ABI:操作系统信息。
  • ABI:ABI版本。
  • ‌Type:文件类型‌:可重定位文件(REL)、可执行文件(DYN)、共享对象文件(DYN),核心转储文件(CORE)。
  • ‌Machine:‌机器类型‌,表示ELF文件的平台属性,如x86、AArch64等。
  • ‌Entry point address:‌ 程序入口地址。
  • Start of program headers: 程序头表偏移量。
  • Start of section headers: 节头表偏移量。
  • Flags‌:标志。
  • Size of this header:ELF头大小。
  • Size of program headers:程序头表条目大小。
  • Number of program header:程序头表有多少条目。
  • Size of section headers:节头表条目大小。
  • Number of section headers:节头表有多少条目。


2.2 程序头表

程序头表(Program Header Table)用于描述文件中的段(Segment)信息,指导操作系统加载程序。

程序头表由多个程序头组成,每个头对应一个段,包含该段的详细信息:段的类型、在文件中的偏移地址、映射到内存的虚拟地址、大小及权限等。

查看程序头表信息:

#readelf -l ELF文件

想学习Linux下的ELF文件有什么好书推荐吗?  第2张

程序头表字段解析:

  • TYPE:段类型,常见类型如下:
    • PT_PHDR‌:程序头表本身在文件中的位置和大小。
    • PT_LOAD:表示一个可加载的段,这种段包含了程序的实际代码和数据,需要被加载到内存中以便执行。
    • PT_DYNAMIC‌:指向动态链接信息,包含了动态链接器所需的各种表和字符串,如动态库路径、依赖库列表、符号表等。
    • PT_INTERP‌:指定了程序解释器的路径,这通常是动态链接器的路径。
    • PT_NOTE‌:附加信息。
  • Offset:文件偏移,段在文件中的偏移量。
  • VirtAddr:虚拟地址,段加载至内存后的虚拟地址。
  • PhysAddr:物理地址:段的物理地址。
  • FileSiz:文件大小,段在文件中大小。
  • MemSiz:内存大小,段在内存中大小。
  • Flags:段标识,段属性:只读属性(R),只写属性(W),可执行属性(E)。
  • Align:对齐方式。


2.3 节区

ELF文件中包含多种节(Section),这些节在文件的编译、链接及执行过程中发挥关键作用。以下是一些常见的ELF文件节:

  • ‌.text‌:包含程序的代码段,是程序执行的主要部分。
  • ‌.data‌:包含已初始化的全局和静态变量,程序运行时需要的数据。
  • ‌.rodata‌:包含只读数据,如常量字符串、浮点数等。
  • ‌.bss‌:包含未初始化的全局和静态变量,运行时被分配内存并初始化为零。
  • ‌.symtab‌:符号表,包含程序中使用的符号信息,如函数名、变量名等。
  • ‌.strtab‌:字符串表,包含符号表中使用的字符串。
  • ‌.shstrtab‌:节名字符串表,包含所有节的名字。
  • ‌.dynamic‌:动态链接信息,包含动态链接器所需的各种信息。
    通过查看ELF文件符号表,可以协助我们排查程序bug:
    #readelf -s ELF文件

想学习Linux下的ELF文件有什么好书推荐吗?  第3张

2.4 节头表

节头表(Section Header Table),用于描述文件中各个节(Section)的属性和信息。

每个节都有一个对应的节表头,它包含了节的名称、类型、大小、偏移量等关键数据,为链接器和加载器提供必要的信息。

查看节头表信息:

#readelf -s ELF文件

root@raspberrypi:/home/mfn# readelf -S a.out There are 29 section headers, starting at offset 0x10b98: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000238 00000238 000000000000001b 0000000000000000 A 0 0 1 [ 2] .note.gnu.bu[...] NOTE 0000000000000254 00000254 0000000000000024 0000000000000000 A 0 0 4 [ 3] .note.ABI-tag NOTE 0000000000000278 00000278 0000000000000020 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000000298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000000002b8 000002b8 00000000000000d8 0000000000000018 A 6 3 8 [ 6] .dynstr STRTAB 0000000000000390 00000390 000000000000008d 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 000000000000041e 0000041e 0000000000000012 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000000430 00000430 0000000000000030 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000000460 00000460 00000000000000c0 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000000520 00000520 0000000000000060 0000000000000018 AI 5 22 8 [11] .init PROGBITS 0000000000000580 00000580 0000000000000018 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 00000000000005a0 000005a0 0000000000000060 0000000000000000 AX 0 0 16 [13] .text PROGBITS 0000000000000600 00000600 000000000000012c 0000000000000000 AX 0 0 64 [14] .fini PROGBITS 000000000000072c 0000072c 0000000000000014 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 0000000000000740 00000740 0000000000000004 0000000000000004 AM 0 0 4 [16] .eh_frame_hdr PROGBITS 0000000000000744 00000744 000000000000003c 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000000780 00000780 00000000000000a4 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 000000000001fdc8 0000fdc8 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 000000000001fdd0 0000fdd0 0000000000000008 0000000000000008 WA 0 0 8 [20] .dynamic DYNAMIC 000000000001fdd8 0000fdd8 00000000000001e0 0000000000000010 WA 6 0 8 [21] .got PROGBITS 000000000001ffb8 0000ffb8 0000000000000030 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 000000000001ffe8 0000ffe8 0000000000000038 0000000000000008 WA 0 0 8 [23] .data PROGBITS 0000000000020020 00010020 0000000000000010 0000000000000000 WA 0 0 8 [24] .bss NOBITS 0000000000020030 00010030 0000000000000008 0000000000000000 WA 0 0 1 [25] .comment PROGBITS 0000000000000000 00010030 000000000000001f 0000000000000001 MS 0 0 1 [26] .symtab SYMTAB 0000000000000000 00010050 0000000000000828 0000000000000018 27 65 8 [27] .strtab STRTAB 0000000000000000 00010878 000000000000021d 0000000000000000 0 0 1 [28] .shstrtab STRTAB 0000000000000000 00010a95 0000000000000103 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), D (mbind), p (processor specific)

节头表字段解析:

  • Name:节名。
  • Type:节类型
  • Address:虚拟地址。
  • Offset:文件偏移量。
  • Size:节大小,节在文件中的大小。
  • EntSize:节条目大小。
  • Flags:节属性:只读属性(R),只写属性(W),可执行属性(E)。
  • Link:链接,表示与该节相关联的符号表或者字符串表。
  • Info:节信息。
  • Align:对齐方式。


3.从ELF文件到Linux进程

前面已经介绍介绍了ELF文件,相信很多小伙伴都很好奇,ELF文件是如何转变成Linux进程的。

Linux创建一个新的进程需要经过两个步骤:

  • 步骤1:父进程通过克隆方式创建子进程。
  • 步骤2:子进程加载ELF文件生成新的进程地址空间。

对于用户程序来说,实现以上两个步骤需要调用fork和execve两个系统调用。

Linux子进程的创建必须由父进程完成,因为这样的一个约束,Linux进程之间形成了类似于家族关系的进程关系,而所有进程的共同祖先就是1号进程(init)。

子进程创建时需要继承父进程的关键信息才能正常工作,从父进程继承而来的信息只能保证子进程按照父进程的方式去工作。当子进程有特定的任务需要执行,此时从父进程继承的信息就没有意义了。子进程如果需要执行特定的任务,需要加载ELF文件,替换掉子进程从父进程继承的旧信息,生成新的进程信息,这样子进程就能独立工作了。

想学习Linux下的ELF文件有什么好书推荐吗?  第4张


3.1 fork创建子进程

如下图所示,用户程序调用fork系统调用后,内核主要完成两部分工作:

  • 创建子进程并克隆关键信息。
  • 将子进程插入CPU就绪队列,等待CPU调度。

具体流程已在图中详细展示,这里不再赘述。

想学习Linux下的ELF文件有什么好书推荐吗?  第5张


Linux内核克隆子进程是一个很复杂的过程,这里我们保留一些关键流程,我们来看一下子进程从父进程继承了哪些信息:

  • files:已打开文件表,子进程和父进程拥有相同的已打开文件表,父子进程可以操作相同的文件。
  • fs:文件系统信息。
  • sighand:信号处理函数表,子进程和父进程处理信号的方式相同。
  • signal:信号信息,同上。
  • mm:进程地址空间,子进程和父进程的代码,数据相同。需要注意:数据相同表示虚拟地址和内存存储内容相同,而实际的物理地址则不相同。
  • nxproxy:命名空间。

想学习Linux下的ELF文件有什么好书推荐吗?  第6张

fork创建完子进程,如果子进程不需要执行特定的任务,此时子进程已经可以工作。如果子进程需要执行特定的任务,那么我们需要将任务编译成ELF文件,再通过ELF文件加载至子进程。

3.2 execve加载ELF文件

如下图所示,execve系统调用主要工作就是替换进程地址空间(mm),而替换进程地址空间需要用到编译好的ELF文件。由于进程地址空间替换时,原来从父进程继承的信息已经没有用,所以替换的过程需要清理旧信息。

进程地址空间替换内核流程如下图:

想学习Linux下的ELF文件有什么好书推荐吗?  第7张

具体流程已经在图中详细展示,这里也不再赘述。

如下图所示,我们来看一下ELF文件转变成Linux进程的详细流程,用户程序调用execve系统调用后,首先会根据文件路径在磁盘中找到ELF文件,找到文件后打开文件(open),读取文件内容(read)。

此时ELF文件已经从磁盘读入内存, 接着execve系统调用按照ELF文件格式解析ELF文件,解析出.bss,.data,.text将三者以文件映射方式映射至进程地址空间。文件映射可以减少拷贝,提高访问效率。

想学习Linux下的ELF文件有什么好书推荐吗?  第8张

总结:

了解ELF文件以及ELF文件如何转换为Linux进程,可以让我们对Linux程序有更深入的理解,我们在编程和调试程序的时,思路会更加清晰。

原文地址:从ELF文件到Linux进程

END

免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。

卡卷网

卡卷网 主页 联系他吧

请记住:卡卷网 Www.Kajuan.Net

欢迎 发表评论:

请填写验证码