我一直在玩ELFIO库。特别是One of the examples允许从头开始创建一个ELF文件 - 定义部分,段,入口点,并为相关部分提供二进制内容。
我注意到当选择的代码段对齐小于页面大小(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的一个:
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。
答案 0 :(得分:2)
(对不起,更多的是评论而非答案)
有关ELF可执行文件应该是什么的一些规范。请特别阅读elf(5),最重要的是相关的ABI规范(另请参阅this问题),例如在https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
AFAIU,这些规范要求代码和数据段都是页面对齐的,但您需要检查一下,特别是在ABI规范的第5章(程序加载和动态链接)中。
生成ELF可执行文件(特别是binutils)的当前工具正在努力遵守这些规范。如果您编写一些ELF生成器,您也应该努力遵守这些规范(因此测试生成的ELF显然有效 )。
内核正在实施execve(2),dynamic loading也使用ld-linux(8) mmap(2)。由于某些原因(可能性能),它不会检查可执行文件是否符合所有规范。
(当然,内核人员希望通常生成的ELF可执行文件成功execve
- d)
在某些极端情况下(就像你观察到的那样),内核不会失败execve
并使用构造不良的ELF文件做一些事情。
但恕我直言,这并不能保证。未来的内核和未来的x86-64处理器可能会在这种构造错误的ELF文件上失败。
我的感觉是你处于一些灰色地带,有些不确定的行为" execve
。如果它碰巧运作,那就是运气不好。
当可执行段的对齐不是0x1000的倍数但是对于数据0x10没有问题时,为什么程序无法执行?
要准确理解原因,您需要深入研究特定内核的源代码(与execve
相关)。而且我相信它可能会在未来发生变化(未来版本的内核)。
内核社区或多或少具有过去的兼容性,但这与规范有关。可能会发生一些格式错误的ELF可执行文件可能是Linux {3.1},而不是Linux 4.13或未来的Linux 5 {/ 1}}。
(我确实读过一些过去的内核已经能够execve
- 一些生成错误的ELF可执行文件,但我忘记了细节,也许是与堆栈指针的16字节对齐有关的东西)< / SUP>
答案 1 :(得分:2)
ELF ABI不要求VirtAddr
或PhysAddr
页面对齐。 (我相信)它只需要
({Virt,Phys}Addr - Offset) % PageSize == 0
对于两个工作二进制文件都是如此,对于非工作二进制文件也是如此。
<强>更新强>
我不知道后者如何失败。
我们有:VirtAddr == 0x08048100
和Offset == 0x80
(以及PageSize == 4096 == 0x1000
)。
(0x08048100 - 0x80) % 0x1000 == 0x80 != 0
当p = = 0x10时,必须同意,不是吗?
否:它必须同意页面大小(正如我之前所说),否则内核将无法mmap
该段。