gcc - 如何在GNU汇编器中将函数或标签的地址加载到寄存器中

标签 gcc assembly x86-64 att addressing-mode

我正在尝试将'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, %edilea main, %rdi在Linux非PIE可执行文件中也可以使用,但绝不能在[disp32]绝对寻址模式下使用LEA(即使在不需要SIB字节的32位代码中); mov至少总是一样好。

当您有一个唯一确定它的寄存器操作数时,操作数大小的后缀是多余的。我更喜欢只写mov而不是movlmovq



愚蠢/糟糕的方法是将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/

相关文章:

GCC:-static 和 -pie 与 x86 不兼容?

c++ - SetThreadContext x64 volatile 寄存器

c++ - x86、C++、gcc 和内存对齐

assembly - CMP 后的 JG/JNLE/JL/JNGE

pointers - 为什么 RBP 而不是另一个寄存器作为帧指针?

C 字符串使用索引或指针复制字符

c++ - 通过命令行使用 gcc 编译 win32 应用程序时,如何链接 .rc(资源)文件?

C,静态库,链接器: How to give preference to strong symbol over weak symbol

c - 打印控制台页面后,如何暂停我的 C 程序?

c - Nios II 汇编代码转 C