.text 与 .data 需要对齐
Required alignment of .text versus .data
我一直在研究 ELFIO library. One of the examples,特别是它允许人们从头开始创建 ELF 文件——定义部分、段、入口点,并为相关部分提供二进制内容。
I noticed that a program created this way segfaults when the code segment alignment is chosen less than the page size (0x1000):
// Create a loadable segment
segment* text_seg = writer.segments.add();
text_seg->set_type( PT_LOAD );
text_seg->set_virtual_address( 0x08048000 );
text_seg->set_physical_address( 0x08048000 );
text_seg->set_flags( PF_X | PF_R );
text_seg->set_align( 0x1000 ); // can't change this
注意 .text
部分 在同一示例中仅与 0x10 的倍数对齐:
section* text_sec = writer.sections.add( ".text" );
text_sec->set_type( SHT_PROGBITS );
text_sec->set_flags( SHF_ALLOC | SHF_EXECINSTR );
text_sec->set_addr_align( 0x10 );
但是数据段虽然通过同样的机制单独加载,但是没有这个问题:
segment* data_seg = writer.segments.add();
data_seg->set_type( PT_LOAD );
data_seg->set_virtual_address( 0x08048020 );
data_seg->set_physical_address( 0x08048020 );
data_seg->set_flags( PF_W | PF_R );
data_seg->set_align( 0x10 ); // note here!
现在,在这种特定情况下,数据按设计适合已分配的页面。不确定这是否有任何区别,但我将其虚拟地址更改为 0x8148020,结果仍然正常。
这是 readelf
的输出:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000001000 0x0000000008048000 0x0000000008048000
0x000000000000001d 0x000000000000001d R E 1000
LOAD 0x0000000000001020 0x0000000008148020 0x0000000008148020
0x000000000000000e 0x000000000000000e RW 10
为什么可执行段的对齐不是0x1000的倍数,但是对于数据0x10没问题,为什么程序执行失败?
更新: 不知怎的,第二次尝试 text_seg->set_align( 0x100 );
也可以,text_seg->set_align( 0x10 );
失败了。页面大小为 0x1000,有趣的是,工作程序的 VirtAddr
在任何一个段中都没有遵守它:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000100 0x08048100 0x08048100 0x0001d 0x0001d R E 0x100
LOAD 0x000120 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
SIGSEGV'ing 之一:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000080 0x08048100 0x08048100 0x0001d 0x0001d R E 0x10
LOAD 0x0000a0 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
生成的 ELF 是 here。
(抱歉,更多的是扩展评论而不是答案)
有一些关于 ELF 可执行文件应该是什么的规范。特别阅读elf(5) and most importantly the relevant ABI specification (see also this question), e.g. on https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
AFAIU,这些规范要求代码和数据段都是页面对齐的,但您需要检查一下,特别是在 ABI 规范的第 5 章(程序加载和动态链接)中。
当前生成 ELF 可执行文件的工具(特别是 binutils)正在努力遵守这些规范。如果你编写一些 ELF 生成器,你也应该努力遵守这些规范(因此测试生成的 ELF 显然有效是不够)。
内核正在实现 execve(2), and dynamic loading is done also with ld-linux(8) using mmap(2)。由于某些原因(可能是性能),它不会检查可执行文件是否符合所有规范。
(当然,内核人员希望通常生成的 ELF 可执行文件能够成功 execve
-d)
在某些极端情况下(比如您观察到的情况),内核不会出现故障 execve
并且不会对构造不当的 ELF 文件执行某些操作。
但恕我直言,对此无法保证。未来的内核和未来的 x86-64 处理器可能会在此类构造不当的 ELF 文件上失败。
我的感觉是您处于某个灰色区域,有点 execve
的 "undefined behavior"。如果碰巧成功了,那就是运气不好。
Why does the program fail to execute when the alignment of the executable segment is not a multiple of 0x1000 but for data 0x10 is no problem?
要准确理解原因,您需要深入研究特定内核的源代码(与 execve
相关)。而且我相信它可能会在未来发生变化(内核的未来版本)。
内核社区或多或少地承诺过去的兼容性,但这是与 规范 相关的。某些格式错误的 ELF 可执行文件可能会被 Linux 3.10 execve
-d 而不是 Linux 4.13 或某些未来的 Linux 5.
(我确实读到一些过去的内核已经能够 execve
-d 一些格式错误的 ELF 可执行文件,但我忘记了细节,也许与 16 字节对齐有关堆栈指针)
ELF ABI 不要求 VirtAddr
或 PhysAddr
页面对齐。 (我相信)它只需要
({Virt,Phys}Addr - Offset) % PageSize == 0
对于两个工作二进制文件都是正确的,对于非工作二进制文件是错误的。
更新:
I don't see how this fails for the latter.
我们有:VirtAddr == 0x08048100
和 Offset == 0x80
(和 PageSize == 4096 == 0x1000
)。
(0x08048100 - 0x80) % 0x1000 == 0x80 != 0
has to agree when align == 0x10, doesn't it?
否:它必须与页面大小一致(正如我之前所说),否则内核将无法 mmap
该段。
我一直在研究 ELFIO library. One of the examples,特别是它允许人们从头开始创建 ELF 文件——定义部分、段、入口点,并为相关部分提供二进制内容。
I noticed that a program created this way segfaults when the code segment alignment is chosen less than the page size (0x1000):
// Create a loadable segment
segment* text_seg = writer.segments.add();
text_seg->set_type( PT_LOAD );
text_seg->set_virtual_address( 0x08048000 );
text_seg->set_physical_address( 0x08048000 );
text_seg->set_flags( PF_X | PF_R );
text_seg->set_align( 0x1000 ); // can't change this
注意 .text
部分 在同一示例中仅与 0x10 的倍数对齐:
section* text_sec = writer.sections.add( ".text" );
text_sec->set_type( SHT_PROGBITS );
text_sec->set_flags( SHF_ALLOC | SHF_EXECINSTR );
text_sec->set_addr_align( 0x10 );
但是数据段虽然通过同样的机制单独加载,但是没有这个问题:
segment* data_seg = writer.segments.add();
data_seg->set_type( PT_LOAD );
data_seg->set_virtual_address( 0x08048020 );
data_seg->set_physical_address( 0x08048020 );
data_seg->set_flags( PF_W | PF_R );
data_seg->set_align( 0x10 ); // note here!
现在,在这种特定情况下,数据按设计适合已分配的页面。不确定这是否有任何区别,但我将其虚拟地址更改为 0x8148020,结果仍然正常。
这是 readelf
的输出:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000001000 0x0000000008048000 0x0000000008048000
0x000000000000001d 0x000000000000001d R E 1000
LOAD 0x0000000000001020 0x0000000008148020 0x0000000008148020
0x000000000000000e 0x000000000000000e RW 10
为什么可执行段的对齐不是0x1000的倍数,但是对于数据0x10没问题,为什么程序执行失败?
更新: 不知怎的,第二次尝试 text_seg->set_align( 0x100 );
也可以,text_seg->set_align( 0x10 );
失败了。页面大小为 0x1000,有趣的是,工作程序的 VirtAddr
在任何一个段中都没有遵守它:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000100 0x08048100 0x08048100 0x0001d 0x0001d R E 0x100
LOAD 0x000120 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
SIGSEGV'ing 之一:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000080 0x08048100 0x08048100 0x0001d 0x0001d R E 0x10
LOAD 0x0000a0 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
生成的 ELF 是 here。
(抱歉,更多的是扩展评论而不是答案)
有一些关于 ELF 可执行文件应该是什么的规范。特别阅读elf(5) and most importantly the relevant ABI specification (see also this question), e.g. on https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
AFAIU,这些规范要求代码和数据段都是页面对齐的,但您需要检查一下,特别是在 ABI 规范的第 5 章(程序加载和动态链接)中。
当前生成 ELF 可执行文件的工具(特别是 binutils)正在努力遵守这些规范。如果你编写一些 ELF 生成器,你也应该努力遵守这些规范(因此测试生成的 ELF 显然有效是不够)。
内核正在实现 execve(2), and dynamic loading is done also with ld-linux(8) using mmap(2)。由于某些原因(可能是性能),它不会检查可执行文件是否符合所有规范。
(当然,内核人员希望通常生成的 ELF 可执行文件能够成功 execve
-d)
在某些极端情况下(比如您观察到的情况),内核不会出现故障 execve
并且不会对构造不当的 ELF 文件执行某些操作。
但恕我直言,对此无法保证。未来的内核和未来的 x86-64 处理器可能会在此类构造不当的 ELF 文件上失败。
我的感觉是您处于某个灰色区域,有点 execve
的 "undefined behavior"。如果碰巧成功了,那就是运气不好。
Why does the program fail to execute when the alignment of the executable segment is not a multiple of 0x1000 but for data 0x10 is no problem?
要准确理解原因,您需要深入研究特定内核的源代码(与 execve
相关)。而且我相信它可能会在未来发生变化(内核的未来版本)。
内核社区或多或少地承诺过去的兼容性,但这是与 规范 相关的。某些格式错误的 ELF 可执行文件可能会被 Linux 3.10 execve
-d 而不是 Linux 4.13 或某些未来的 Linux 5.
(我确实读到一些过去的内核已经能够 execve
-d 一些格式错误的 ELF 可执行文件,但我忘记了细节,也许与 16 字节对齐有关堆栈指针)
ELF ABI 不要求 VirtAddr
或 PhysAddr
页面对齐。 (我相信)它只需要
({Virt,Phys}Addr - Offset) % PageSize == 0
对于两个工作二进制文件都是正确的,对于非工作二进制文件是错误的。
更新:
I don't see how this fails for the latter.
我们有:VirtAddr == 0x08048100
和 Offset == 0x80
(和 PageSize == 4096 == 0x1000
)。
(0x08048100 - 0x80) % 0x1000 == 0x80 != 0
has to agree when align == 0x10, doesn't it?
否:它必须与页面大小一致(正如我之前所说),否则内核将无法 mmap
该段。