我正在尝试将'main'的地址加载到GNU汇编器中的寄存器(R10)中。我没办法在这里,我所拥有的以及收到的错误消息。
main:
lea main, %r10
我也尝试了以下语法(这次使用mov)
main:
movq $main, %r10
通过以上两种,我得到以下错误:
/usr/bin/ld: /tmp/ccxZ8pWr.o: relocation R_X86_64_32S against symbol `main' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status
使用-fPIC进行编译无法解决问题,只是给了我同样的错误。
最佳答案
在x86-64中,大多数立即数和位移仍然是32位,因为64位会浪费太多的代码大小(I缓存占用空间和获取/解码带宽)。lea main, %reg
是绝对的disp32
寻址模式,它将阻止加载时地址随机化(ASLR)选择随机的64位(或47位)地址,因此Linux不受位置相关的可执行文件之外的支持,或者在MacOS上完全没有。 (有关文档和指南的链接,请参见x86 tag wiki。)在Windows上,可以将可执行文件构建为“不支持大型地址”。如果不选择,地址将以32位为宜。
将静态地址放入寄存器的标准有效方法是相对于RIP的LEA:
# Use this, works everywhere
lea main(%rip), %r10 # 7 bytes
lea r10, [rip+main] # GAS .intel_syntax noprefix equivalent
lea r10, [rel main] # NASM equivalent, or use default rel
有关3种语法的说明,请参见How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work?。
从当前指令的末尾开始使用32位相对位移,例如
jmp
/ call
。假设静态代码+数据通常具有2GiB的总大小限制,则此代码可以访问.data
,.bss
,.rodata
中的任何静态数据或.text
中的函数。在Linux上的位置相关代码(例如,使用
gcc -fno-pie -no-pie
构建)中,您可以利用32位绝对寻址来节省代码大小。同样,mov r32, imm32
的吞吐量比Intel / AMD CPU上相对于RIP的LEA更好,因此无序执行可能能够使其与周围的代码更好地重叠。 (优化代码大小通常比大多数其他事情没有那么重要,但是当所有其他条件都相等时,请选择较短的指令。在这种情况下,所有其他条件至少相等,或者使用mov imm32
也更好。)有关如何将PIE可执行文件默认设置为更多信息,请参见32-bit absolute addresses no longer allowed in x86-64 Linux?。 (这就是为什么使用32位绝对值时出现有关
-fPIC
的链接错误的原因。)# in a non-PIE executable, mov imm32 into a 32-bit register is even better
## GAS AT&T syntax
mov $main, %r10d # 6 bytes
mov $main, %edi # 5 bytes: no REX prefix needed for a "legacy" register
## GAS .intel_syntax
mov edi, OFFSET main
;; NASM syntax: mov edi, main
请注意,写入任何32位寄存器总是零扩展到完整的64位寄存器(R10和RDI)。
lea main, %edi
或lea main, %rdi
在Linux非PIE可执行文件中也可以使用,但绝不能在[disp32]
绝对寻址模式下使用LEA(即使在不需要SIB字节的32位代码中); mov
至少总是一样好。当您有一个唯一确定它的寄存器操作数时,操作数大小的后缀是多余的。我更喜欢只写
mov
而不是movl
或movq
。愚蠢/糟糕的方法是将10字节的64位绝对地址作为立即数:
# Inefficient, DON'T USE
movabs $main, %r10 # 10 bytes including the 64-bit absolute address
如果您使用
mov rdi, main
而不是mov edi, main
,这就是您在NASM中获得的结果,因此很多人最终都这样做了。 Linux动态链接实际上确实支持64位绝对地址的运行时修复。但是用例是跳转表,而不是绝对地址作为立即数。movq $sign_extended_imm32, %reg
(7个字节)仍使用32位绝对地址,但是将符号扩展mov
上的代码字节浪费在64位寄存器上,而不是通过写入32-位来隐式扩展到64位。位寄存器。通过使用
movq
,您将告诉GAS您需要R_X86_64_32S
重定位,而不是R_X86_64_64
64位绝对重定位。您想要这种编码的唯一原因是内核代码,其中静态地址位于64位虚拟地址空间的高2GiB中,而不是低2GiB中。在某些CPU上(例如在更多端口上运行),
mov
在性能上优于lea
,但是通常如果您可以使用32位绝对值,则它在mov r32, imm32
工作的虚拟地址空间的低2GiB中。PS:我故意忽略了有关“大”或“大”内存/代码模型的讨论,在这些模型中,相对于RIP的+ -2GiB寻址无法到达静态数据,或者甚至无法到达其他代码地址。上面是针对x86-64 System V ABI的“ small”和/或“ small-PIC”代码模型的。中型和大型型号可能需要
movabs $imm64
,但这非常少见。我不知道
mov $imm32, %r32
是否可以在Windows x64可执行文件或带有运行时修补程序的DLL中工作,但是相对于RIP的LEA确实可以。半相关的:Call an absolute pointer in x86 machine code-如果要进行JIT,请尝试将JIT缓冲区放在现有代码附近,以便可以
call rel32
,否则movabs
指针进入寄存器。关于gcc - 如何在GNU汇编器中将函数或标签的地址加载到寄存器中,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57212012/