linux - 在 ELF 中,为什么标题需要在一个段中?
问题描述
我制作了这个简单的 ELF 用于学习目的:
bits 64
org 0x08048000
elfHeader:
db 0x7F, "ELF", 2, 1, 1, 0 ; e_ident
db 0 ; abi version
times 7 db 0 ; unused padding
dw 2 ; e_type
dw 62 ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq programHeader - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
dw elfHeaderSize ; e_ehsize
dw programHeaderSize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
elfHeaderSize equ $ - elfHeader
programHeader:
dd 1 ; p_type
dd 7 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq fileSize ; p_filesz
dq fileSize ; p_memsz
dq 0x1000 ; p_align
programHeaderSize equ $ - programHeader
_start:
xor rdi, rdi
xor eax,eax
mov al,60
syscall
fileSize equ $ - $$
为了编译该代码,我使用 NASM:
nasm -f bin exe.asm -o exe
如果你看一下,你会programHeader
看到p_offset
0 和。这意味着该段包含整个文件。这是我没有预料到的(而且我不是唯一的),但显然 Linux 操作系统需要标头位于类型段中,以便加载信息。p_filesz
fileSize
PT_LOAD
这是我能找到的唯一提到标题位于一个段内的事实的资源:https ://www.intezer.com/blog/research/executable-linkable-format-101-part1-sections-segments/
关于段要强调的重要一点是只有 PT_LOAD 段被加载到内存中。因此,每隔一个段映射到 PT_LOAD 段之一的内存范围内。
为了理解 Sections 和 Segments 之间的关系,我们可以将 Segments 想象成一种使 linux 加载器的生活更轻松的工具,因为它们通过属性将 section 分组为单个段,以使可执行文件的加载过程更高效,而不是将每个单独的部分加载到内存中。下图试图说明这个概念:
但我不明白为什么 Linux 需要在运行时加载这些标头。它们是用来做什么的?如果进程运行需要它们,Linux不能自己加载它吗?
编辑:
评论中已经提到不需要加载标题,但是,有时无论如何都会加载它们以避免必须添加填充。我尝试添加填充以使其对齐 4KB,但它不起作用。这是我的尝试:
bits 64
org 0x08048000
elfHeader:
db 0x7F, "ELF", 2, 1, 1, 0 ; e_ident
db 0 ; abi version
times 7 db 0 ; unused padding
dw 2 ; e_type
dw 62 ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq programHeader - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
dw elfHeaderSize ; e_ehsize
dw programHeaderSize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
elfHeaderSize equ $ - elfHeader
programHeader:
dd 1 ; p_type
dd 7 ; p_flags
dq _start - $$ ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq codeSize ; p_filesz
dq codeSize ; p_memsz
dq 0x1000 ; p_align
programHeaderSize equ $ - programHeader
; padding until 4KB
paddingUntil4k equ 4*1024 - ($ - elfHeader)
times paddingUntil4k db 0
_start:
xor rdi, rdi
xor eax,eax
mov al,60
syscall
codeSize equ $ - _start
fileSize equ $ - $$
解决方案
但我不明白为什么 Linux 需要在运行时加载这些标头。
它没有。
它们是用来做什么的?如果进程运行需要它们,Linux不能自己加载它吗?
要回答所有这些问题,您需要查看 Linux 内核源代码。
在源代码中,您可以看到实际上程序头不需要是任何PT_LOAD
段的一部分,内核将自行读取它们。
像这样更改您的原始程序:
diff -u exe.asm.orig exe.asm
--- exe.asm.orig 2021-02-07 18:54:34.449336515 -0800
+++ exe.asm 2021-02-07 18:53:19.773532451 -0800
@@ -24,9 +24,9 @@
programHeader:
dd 1 ; p_type
dd 7 ; p_flags
- dq 0 ; p_offset
- dq $$ ; p_vaddr
- dq $$ ; p_paddr
+ dq _start - $$ ; p_offset
+ dq _start ; p_vaddr
+ dq _start ; p_paddr
dq fileSize ; p_filesz
dq fileSize ; p_memsz
dq 0x1000 ; p_align
生成一个运行良好的程序,但程序头不在PT_LOAD
段中:
eu-readelf --all exe
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
Ident Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: AMD x86-64
Version: 1 (current)
Entry point address: 0x8048078
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags:
Size of this header: 64 (bytes)
Size of program header entries: 56 (bytes)
Number of program headers entries: 1
Size of section header entries: 0 (bytes)
Number of section headers entries: 0 ([0] not available)
Section header string table index: 0
Section Headers:
[Nr] Name Type Addr Off Size ES Flags Lk Inf Al
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000078 0x0000000008048078 0x0000000008048078 0x000081 0x000081 RWE 0x1000
我试过添加填充
你没有正确地做到这一点。使用“带填充”源会导致以下结果exe-padding
:
...
Entry point address: 0x8049000
...
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x0000000008048000 0x0000000008048000 0x000009 0x000009 RWE 0x1000
这个二进制文件由内核启动,并立即跳转到未映射0x8049000
的起始地址(因为它没有被段覆盖),从而导致立即.PT_LOAD
SIGSEGV
要解决这个问题,您需要调整入口地址:
diff -u exe-padding.asm.orig exe-padding.asm
--- exe-padding.asm.orig 2021-02-07 18:57:31.800871195 -0800
+++ exe-padding.asm 2021-02-07 19:34:27.303071700 -0800
@@ -8,7 +8,7 @@
dw 2 ; e_type
dw 62 ; e_machine
dd 1 ; e_version
- dq _start ; e_entry
+ dq _start - 0x1000 ; e_entry
dq programHeader - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
这再次产生了一个工作可执行文件。作为记录:
eu-readelf --all exe-padding
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
Ident Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: AMD x86-64
Version: 1 (current)
Entry point address: 0x8048000
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags:
Size of this header: 64 (bytes)
Size of program header entries: 56 (bytes)
Number of program headers entries: 1
Size of section header entries: 0 (bytes)
Number of section headers entries: 0 ([0] not available)
Section header string table index: 0
Section Headers:
[Nr] Name Type Addr Off Size ES Flags Lk Inf Al
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x0000000008048000 0x0000000008048000 0x000009 0x000009 RWE 0x1000
PS 您将 64 位程序链接到0x08048000
,这是i*86
(32 位)可执行文件的传统加载地址。x86_64
二进制文件传统上从0x400000
.
更新:
关于第一个示例,p_filesz 仍然是 fileSize,我认为应该超出文件的边界。
这是正确的:p_filesz
并且p_memsz
应该减少标题的大小(0x78
此处)。请注意,这两个都将被四舍五入到页面大小(在添加 之后p_offset
),因此对于此示例,没有实际区别。
更新 2:
pastebin.ubuntu.com/p/rgfVMrbcmJ
这导致以下LOAD
部分:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000078 0x0000000008048000 0x0000000008048000 0x000081 0x000081 RWE 0x1000
这个二进制文件不会运行(内核会拒绝它),因为它要求内核做不可能的事情:到mmap
偏移量的字节0x78
到页面开始。
如果应用程序执行等效mmap
调用,它会得到EINVAL
错误,因为mmap
需要(offset % pagesize) == (addr % pagesize)
.
推荐阅读
- cakephp-3.0 - 保存复杂的多对多关联 CakePHP 4
- python - Python Pandas 添加 DataRow 修订号
- r - R如何根据以下行中的条件过滤数据框?
- sql - 来自嵌套 JSON 数组的 MarkLogic TDE XPath 值
- javascript - 获取新数组后组件未更新
- javascript - 我在 POSTMAN 中收到 500 个内部服务器错误
- c# - 从 Blazor Webassembly 流式传输大文件?
- javascript - 如何为我用 JavaScript 编写的这个 HTML 生成器添加适当的缩进
- regex - Perl - 如何从文本文件中省略行?
- elasticsearch - Elastic Search 多个 AND + OR 查询