我的理解是,二进制文件加载到内存并运行时的实际映射地址不一定相同。
不,从这些地址我们可以看到这是一个链接在的非 PIE ELF 可执行文件ld
的默认基地址。这是一个位置相关可执行的。
可执行文件本身将始终加载到固定的虚拟地址,因此可以使用 32 位立即数而不是 RIP 相关 LEA 将静态地址放入寄存器中。可执行文件本身的 ASLR 是不允许/不可能的。
libc 是一个 ELF“共享对象”,可以是 ALSRed,因此调用__libc_start_main
通过 GOT 中的指针。在这个 CRT 启动代码的 gcc 源代码中,这可能看起来像call *__libc_start_main@GOTPCREL(%rip)
(AT&T 语法)。
顺便说一句,我们可以看出这是手写的 asm,因为错过了使用 7 字节的优化mov rdi, sign_extended_imm32
(与 RIP 相关 LEA 大小相同)而不是 5 字节mov edi, imm32
。 x86-64 System V ABI 中的默认非 PIE 代码模型将所有静态代码/数据放在虚拟地址空间的低 2GiB 中,因此静态地址可以通过零或符号扩展至 64 位来使用。
ELF“可执行文件”can被加载到随机基地址的称为 PIE(位置无关可执行文件)。就 ELF 细节而言,它们使用与共享库相同的 ELF“类型”,因此它们实际上是具有“入口点”并被标记为可执行文件的 ELF 共享对象。
现代 Linux 发行版有 gcc 默认构建 PIE。看x86-64 Linux 中不再允许使用 32 位绝对地址?(可重定位 ELF 共享对象可以重定位在地址空间中的任何位置,不限于低 2GiB,因此没有用于 32 位绝对地址的运行时修复的重定位类型。)
64 位绝对地址有重定位类型,因此(函数/代码指针的)跳转表仍然是可能的,10 字节也是如此mov rdi, imm64
,但是即使 ELF 程序加载器或动态链接器不需要修改这些重定位的程序文本,其效率也比 RIP 相关的 LEA 低。
e.g. readelf -a /bin/ls
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 (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x5ae0
...
请注意类型字段:DYN,与实际库中的相同,例如readelf -a /lib/libc.so.6
。入口点是一个相对地址,相对于它映射的基地址。
非 PIE 可执行文件(例如静态链接,或使用-fno-pie -no-pie
)看起来像这样:
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: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
请注意Type: EXEC
和绝对入口点(在链接时选择ld
).