type
Post
status
Published
slug
2022/12/10/wiki-osdev-org-elf
summary
tags
osdev
category
osdev
icon
password
new update day
Property
Oct 22, 2023 01:31 PM
created days
Last edited time
Oct 22, 2023 01:31 PM
ELF(可执行和可链接格式)是由 Unix 系统实验室在与 Sun Microsystems 在 SVR4(UNIX System V Release 4.0)上合作时设计的。因此,ELF 最早出现在基于 SVR4 的 Solaris 2.0(又名 SunOS 5.0)中。格式在 System V ABI 中指定。
它是一种非常通用的文件格式,后来被许多其他操作系统采用,用作可执行文件和共享库文件。它区分了 TEXT、DATA 和 BSS。
今天,ELF 被认为是类 Unix 系统上的标准格式。虽然它有一些缺点(例如,在使用与位置无关的代码时,会占用 IA-32 中一个稀缺的通用寄存器)),但它拥有良好的支持和文档。
1 File Structure
ELF 是一种用于在磁盘上存储程序或程序片段的格式,是编译和链接的结果。 ELF 文件分为多个部分。对于可执行程序,它们是作为代码的 TEXT 部分、存储全局变量的 data 部分和通常保存常量字符串的 rodata 部分。 ELF 包含描述如何在内存中存储这些部分的头文件。
请注意,根据您的文件是可链接文件还是可执行文件,ELF 文件中的标头将不相同:例如
process.o
是运行,gcc -c process.c $SOME_FLAGS
得到的结果。C32/kernel/bin/.process.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000333 00000000 00000000 00000040 2**4 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000050 00000000 00000000 00000380 2**5 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 000003d0 2**2 ALLOC 3 .note 00000014 00000000 00000000 000003d0 2**0 CONTENTS, READONLY 4 .stab 000020e8 00000000 00000000 000003e4 2**2 CONTENTS, RELOC, READONLY, DEBUGGING 5 .stabstr 00008f17 00000000 00000000 000024cc 2**0 CONTENTS, READONLY, DEBUGGING 6 .rodata 000001e4 00000000 00000000 0000b400 2**5 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .comment 00000023 00000000 00000000 0000b5e4 2**0 CONTENTS, READONLY
“flags”
会告诉您 ELF 文件中实际可用的内容。在这里,我们有符号表和重定位:我们需要将文件链接到另一个文件,但实际上没有关于如何将文件加载到内存中的信息(即使可以猜到)。例如,我们没有程序入口点,我们有一个 sections 表而不是程序头。.text | 如上所述,代码所在的位置。 以下命令 objdump -drS .process.o 会向你展示它。 |
.data | 全局表、全局变量等所在的位置。 objdump -s -j .data .process.o 命令会将它 hexdump 出来。 |
.bss | 不要在您的文件中寻找 .bss 所在的位:根本没有。它是存放你未初始化的数组和变量所在的位置,并且加载程序“知道”它们应该用零填充.....所以没有必要在你的磁盘上存储更多的零,不是吗? |
.rodata | 那就是你的字符串所在的地方,通常是你在链接时忘记的东西,这将会导致你的内核无法工作。 objdump -s -j .rodata .process.o 会将它 hexdump 出来。请注意,由于编译器的不同,您可能会有更多的这个部分。 |
.comment & .note | 只是编译器/链接器工具链放在那里的注释 |
.stab & .stabstr | 调试符号以及类似的信息。 |
/bin/bash
,一个真正的可执行文件/bin/bash: file format elf32-i386 /bin/bash architecture: i386, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x08056c40 Program Header: PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2 filesz 0x000000e0 memsz 0x000000e0 flags r-x
程序头本身......占用 224
(filesz 0x000000e0)
个字节,并从文件中的偏移量 0x34
开始INTERP off 0x00000114 vaddr 0x08048114 paddr 0x08048114 align 2**0 filesz 0x00000013 memsz 0x00000013 flags r--
应该用于“执行”二进制文件的程序。在这里,它读作
“/lib/ld-linux.so.2”
,这意味着在我们运行程序之前需要一些动态库链接。LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x0007411c memsz 0x0007411c flags r-x
现在我们被要求读取
7411c
字节,从文件的开头 (?) 开始,大小为 7411c
字节(这实际上是整个文件!),这将是只读、但可执行
。它们将从虚拟地址 0x08048000
开始出现,以使程序能够正常运行。LOAD off 0x00074120 vaddr 0x080bd120 paddr 0x080bd120 align 2**12 filesz 0x000022ac memsz 0x000082d0 flags rw-
加载更多的位(可能是
.data
部分)。你可能注意到,'filesize'
和 'memsize'
是不同的,这意味着 .bss
部分实际上是通过此语句分配的,但保留为零,而 'real'
数据仅占用从虚拟地址 0x80bd120
开始的前 0x22ac
字节。DYNAMIC off 0x00075f4c vaddr 0x080bef4c paddr 0x080bef4c align 2**2 filesz 0x000000e8 memsz 0x000000e8 flags rw-
动态 (dynamic) 部分用于存储动态链接过程中使用的信息,例如所需的库和重定位条目。
NOTE off 0x00000128 vaddr 0x08048128 paddr 0x08048128 align 2**2 filesz 0x00000020 memsz 0x00000020 flags r--
NOTE 节包含程序员或链接器留下的信息,对于大多数使用 GNU 'ld' 链接器链接的程序,它只显示 'GNU’
EH_FRAME off 0x000740f0 vaddr 0x080bc0f0 paddr 0x080bc0f0 align 2**2 filesz 0x0000002c memsz 0x0000002c flags r--
这是关于异常处理程序的信息,以防我们在执行时链接到某些 C++ 二进制文件(需要引用)。
/bin/bash, loaded (as in /proc/xxxx/maps) 08048000-080bd000 r-xp 00000000 03:06 30574 /bin/bash 080bd000-080c0000 rw-p 00074000 03:06 30574 /bin/bash 080c0000-08103000 rwxp 00000000 00:00 0 40000000-40014000 r-xp 00000000 03:06 27304 /lib/ld-2.3.2.so 40014000-40015000 rw-p 00013000 03:06 27304 /lib/ld-2.3.2.so
我们可以识别我们的“代码位”和“数据位”,通过声明第二个应该加载到
0x080bd
120
并且它在文件中从 0x00074
120
开始,我们实际上保留了页面到磁盘块的映射(例如,如果页面 0x80bc000
未命中,只需从 0x75000
获取文件块)。然而,这意味着部分代码被映射了两次,但具有不同的权限。如果您不想以可修改的代码结束(上面的第三行与第四行执行权限不同),我建议您也为它们提供不同的物理页面。2 Loading ELF Binaries
标头包含加载 ELF 可执行文件所需的所有相关信息。 ELF 规范中描述了此标头的格式。与此目的最相关的部分是 1.1 至 1.4 和 2.1 至 2.7。关于加载可执行文件的说明包含在2.7节中。
以下是 ELF 可执行加载程序必须执行的步骤的粗略概述:
- 验证文件是否以 ELF 魔数(4 个字节)开头,如 ELF 规范第 11 页的图 1-4(及后续表格)中所述。
- 读取 ELF 标头。 ELF 标头始终位于 ELF 文件的最开头。 ELF 标头包含有关文件其余部分如何布局的信息。可执行加载器只关心程序头。
- 读取 ELF 可执行文件的程序头。它们指定了程序段在文件中的位置,以及它们需要加载到内存中的位置。
- 解析程序头以确定必须加载的程序段的数量。每个程序头都有一个关联的类型,如 ELF 规范的图 2-2 中所述。只有类型为
PT_LOAD
的标头描述可加载段。
- 加载每个可加载段。具体操作如下:
- 在程序头中的
p_vaddr
成员指定的地址为每个段分配虚拟内存。内存中段的大小由p_memsz
成员指定。 - 将段数据从
p_offset
成员指定的文件偏移量复制到p_vaddr
成员指定的虚拟内存地址。文件中段的大小包含在p_filesz
成员中。这可以为零。 p_memsz
成员指定段在内存中占用的大小。这可以为零。如果p_filesz
和p_memsz
成员不同,这表明该段是用零填充的。内存中文件大小的结束偏移量和段的虚拟内存大小之间的所有字节都将被清除为零。
- 从 ELF 标头中读取可执行文件的入口点。
- 跳转到新加载内存中可执行文件的入口点。
3 Relocation(重定位)
当您需要加载模块或驱动程序时,重定位会变得很方便。可以使用 ld 的
“-r”
选项,允许您将多个目标文件链接到一个大文件中,这意味着更容易编码和更快测试。关于重定位你需要做的事情的基本大纲:
- 检查目标文件头(例如,它必须是 ELF,而不是 PE)
- 获取加载地址(例如,所有驱动程序都从
0xA0000000
开始,需要一些方法来跟踪驱动程序的位置)
- 为所有程序段分配足够的空间
(ST_PROGBITS)
- 从 RAM 中的映像复制到分配的空间
- 遍历所有节,根据内核符号表解析外部引用
- 如果全部成功,您可以使用标头的
“e_entry”
字段作为加载地址的偏移量来调用入口点(如果已指定),或进行符号查找,或仅返回成功错误代码。
一旦您可以重定位 ELF 目标文件,一旦您可以重定位 ELF 目标文件,您就可以在需要时而不用在启动时加载驱动程序——这总是一件好事 (tm)。——这总是一件好事 (tm)。
3 Tables
3.1 Header
标头位于 ELF 文件的开头。
Position (32 bit) | Position (64 bit) | Value |
0-3 | 0-3 | Magic number - 0x7F, then 'ELF' in ASCII |
4 | 4 | 1 = 32 bit, 2 = 64 bit |
5 | 5 | 1 = little endian, 2 = big endian |
6 | 6 | ELF header version |
7 | 7 | OS ABI - usually 0 for System V |
8-15 | 8-15 | Unused/padding |
16-17 | 16-17 | 1 = relocatable, 2 = executable, 3 = shared, 4 = core |
18-19 | 18-19 | Instruction set - see table below |
20-23 | 20-23 | ELF Version |
24-27 | 24-31 | Program entry position(程序入口位置) |
28-31 | 32-39 | Program header table position |
32-35 | 40-47 | Section header table position |
36-39 | 48-51 | Flags - architecture dependent; see note below(标志 - 架构依赖;见下面的注释) |
40-41 | 52-53 | Header size |
42-43 | 54-55 | Size of an entry in the program header table(程序头表中条目的大小) |
44-45 | 56-57 | Number of entries in the program header table |
46-47 | 58-59 | Size of an entry in the section header table |
48-49 | 60-61 | Number of entries in the section header table |
50-51 | 62-63 | Index in section header table with the section names(带有节名的节头表中的索引) |
对于 x86 ELF,可以忽略 flags 条目,因为实际上没有定义任何标志。
指令集架构:
Architecture | Value |
No Specific | 0 |
2 | |
3 | |
8 | |
0x14 | |
0x28 | |
0x2A | |
0x32 | |
0x3E | |
0xB7 | |
0xF3 |
最常见的架构以粗体显示。
3.2 Program header
这是一个包含N个(在主标题中给出)条目的数组,格式如下。确保根据文件是 32 位还是 64 位使用正确的版本,因为表格完全不同。
32 位版本:
Position | Value |
0-3 | Type of segment (see below) |
4-7 | The offset in the file that the data for this segment can be found (p_offset) |
8-11 | Where you should start to put this segment in virtual memory (p_vaddr) |
12-15 | Undefined for the System V ABI |
16-19 | Size of the segment in the file (p_filesz) |
20-23 | Size of the segment in memory (p_memsz) |
24-27 | Flags (see below) |
28-31 | The required alignment for this section (must be a power of 2) |
64 bit version:
Position | Value |
0-3 | Type of segment (see below) |
4-7 | Flags (see below) |
8-15 | The offset in the file that the data for this segment can be found (p_offset) |
16-23 | Where you should start to put this segment in virtual memory (p_vaddr) |
24-31 | Undefined for the System V ABI |
32-39 | Size of the segment in the file (p_filesz) |
40-47 | Size of the segment in memory (p_memsz) |
48-55 | The required alignment for this section (must be a power of 2) |
段类型:
- 0 = null
- 忽略该条目;
- 1 = 加载
- 将
p_vaddr
处的p_memsz
字节清零,然后将p_filesz
字节从p_offset
复制到p_vaddr
;
- 2 = 动态
- 需要动态链接
- 3 = interp
- 包含可执行文件的文件路径,用作后续段的解释器;
- 4 = 注释部分。
- 有更多的值,但主要包含体系结构/环境特定信息,这对于大多数 ELF 文件来说可能不是必需的。
Flags:1 = 可执行,2 = 可写,4 = 可读。
3.3 Dynamic Linking
动态链接是指操作系统在程序需要时为其提供共享库。这意味着,库是在系统中找到的,然后在程序运行时“绑定”到需要它们的程序,而静态链接是在程序运行前链接库。动态链接的主要优点是程序占用内存更少,文件大小也更小。然而,主要的缺点是该程序的可移植性较差,因为该程序依赖于许多不同的共享库。
为了实现这一点,您需要有适当的调度、一个库和一个使用该库的程序。您可以使用 GCC 创建一个库:
myos-gcc -c -fPIC -o oneobject.o oneobject.c myos-gcc -c -fPIC -o anotherobject.o anotherobject.c myos-gcc -shared -fPIC -Wl,-soname,nameofmylib oneobject.o anotherobject.o -o mylib.so
这个库应该被视为一个文件,当操作系统检测到它的尝试使用时加载它。您需要将此“动态链接器”实现到特定的代码分类中,例如在内存管理或任务管理部分中。当 ELF 程序运行时,系统应将共享对象数据附加到
malloc()
内存区域,其中对库函数调用重定向到 malloc()
内存区域。程序完成后,可以通过调用 free()
将该区域交还给操作系统。这应该是编写动态链接器的一个很好的起点。
4 See Also
4.1 Articles
4.2 External Links
- The ELF file format in detail
- ELF Format Specifications Detailed and up-to-date ELF information (including SPARC in depth) by Oracle.
- System V ABI about ELF
- LSB specificationsSee (generic or platform-specific) 'Core' specifications for additional ELF information.
- Executable and Linkable Format on Wikipedia,which contains a detail of elf references
- The ELF file format(64-bit) ELF 64-Bit, General extension to ELF32.
- x86-64 ABI Documented x86-64 specific extensions with ELF64.
- Manually Creating an ELF Executable (dead, link from archive.org) Detailed guide on how to create ELF binaries from scratch.
欢迎加入“喵星计算机技术研究院”,原创技术文章第一时间推送。
- 作者:tangcuyu
- 链接:https://expoli.tech/articles/2022/12/10/wiki-osdev-org-elf
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章
2022-12-11
wiki.osdev.org 系列之 - Category:Bare Bones (类别:基本教程)
2022-12-11
wiki.osdev.org 系列之 - Linux Kernel Primer(Linux 内核入门)
2022-12-11
wiki.osdev.org 系列之 - OS theory
2022-12-11
wiki.osdev.org 系列之 - Symbol Table
2022-12-11
wiki.osdev.org 系列之 - System V ABI
2022-12-10
wiki.osdev.org 系列之 - Category:Object Files (类别:目标文件)