ELF64架构下NASM编译的数据是否始终存储在固定地址?
我用NASM编写了一个简单的Hello World程序,出于好奇使用objdump -d查看反汇编结果,程序代码如下:
BITS 64 SECTION .text GLOBAL _start _start: mov rax, 0x01 mov rdi, 0x00 mov rsi, hello_world mov rdx, hello_world_len syscall mov rax, 0x3C syscall SECTION .data hello_world: db "Hello, world!", 0x0A hello_world_len: equ $-hello_world
查看时发现实际指令用movabs加载十六进制值0x402000替代hello_world符号,这意味着程序认定字符串每次运行都会存在于0x402000,且objdump -d输出中无hello_world的引用(输出如下)。我尝试将第8行的hello_world替换为mov rsi, 0x402000,程序仍能正常编译运行。修改.data段中hello_world的名称也未改变结果。我感到困惑:编译器如何在编译时确定该地址?为何重编译后地址也不会变化?
objdump -d hello_world输出:
./hello_world: file format elf64-x86-64 Disassembly of section .text: 0000000000401000 <_start>: 401000: b8 01 00 00 00 mov $0x1,%eax 401005: bf 00 00 00 00 mov $0x0,%edi 40100a: 48 be 00 20 40 00 00 movabs $0x402000,%rsi 401011: 00 00 00 401014: ba 0e 00 00 00 mov $0xe,%edx 401019: 0f 05 syscall 40101b: b8 3c 00 00 00 mov $0x3c,%eax 401020: bf 00 00 00 00 syscall
(如你所见,输出中没有.data段的反汇编结果,这更让我困惑)
编译时确定地址的原因:你使用NASM编译后,会通过
ld链接生成静态ELF可执行文件。这类文件的段地址由ld的默认链接脚本决定,默认配置下,.text段起始地址为0x401000,.data段起始地址为0x402000(遵循页对齐规则,.text段大小较小,对齐后.data段刚好从0x402000开始)。链接阶段,链接器会将hello_world符号解析为.data段中字符串的实际地址,直接把0x402000硬编码到movabs指令里——这是64位平台加载64位立即数的标准指令。重编译后地址不变的原因:只要你没有修改链接器的配置(比如自定义链接脚本),
ld每次都会使用相同的默认地址分配规则,.data段的起始地址固定为0x402000,字符串的位置自然不会变化。objdump未显示.data段的原因:
objdump -d选项默认只反汇编具有执行权限的段,.data段是只读数据段(权限为READ+WRITE),不具备执行权限,所以不会被反汇编。如果要查看.data段的内容或地址,可以使用objdump -s(显示段的原始内容)或者objdump -x(显示所有段的详细信息)。
内容的提问来源于stack exchange,提问作者Basil

