如何在GNU汇编器中将函数或标签的地址加载到寄存器中 [英] How to load address of function or label into register in GNU Assembler

查看:101
本文介绍了如何在GNU汇编器中将函数或标签的地址加载到寄存器中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将 main的地址加载到GNU汇编器中的寄存器(R10)中。我做不到这里是我所拥有的以及收到的错误消息。

I am trying to load the address of 'main' into a register (R10) in the GNU Assembler. I am unable to. Here I what I have and the error message I receive.

main:
   lea main, %r10

我也尝试了以下语法(这次使用mov)

I also tried the following syntax (this time using mov)

main:
   movq $main, %r10

以上两种情况都出现以下错误:

With both of the above I get the following error:

/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编译不能解决问题,只是给了我相同的错误。

Compiling with -fPIC does not resolve the issue and just gives me the same exact error.

推荐答案

在x86-64中,大多数立即数和位移仍然是32位,因为64位会浪费太多的代码大小(I缓存占用空间和获取/解码带宽)。

In x86-64, most immediates and displacements are still 32-bits because 64-bit would waste too much code size (I-cache footprint and fetch/decode bandwidth).

lea main,%reg 是绝对 disp32 寻址方式,它将停止加载-随机选择64位(或47位)地址进行实时地址随机化(ASLR),因此在位置相关可执行文件之外的Linux上或MacOS上都不支持该地址。 (有关文档和指南的链接,请参见 x86标签Wiki 。)在Windows上,您可以将可执行文件构建为可识别大地址的地址。 或不。如果不选择,则地址将适合32位。

lea main, %reg is an absolute disp32 addressing mode which would stop load-time address randomization (ASLR) from choosing a random 64-bit (or 47-bit) address so it's not supported on Linux outside of position-dependent executables, or at all on MacOS. (See the x86 tag wiki for links to docs and guides.) On Windows, you can build executables as "large address aware" or not. If you choose not, addresses will fit in 32 bits.

将静态地址放入寄存器的标准有效方法是 RIP相对LEA

The standard efficient way to put a static address into a register is a RIP-relative 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

请参见如何使用RIP相对变量引用,例如 [RIP + _a]?在x86-64 GAS Intel语法中工作?来解释这3种语法。

See How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? for an explanation of the 3 syntaxes.

这使用从末尾开始的32位相对位移当前指令,例如 jmp / call 。这可以访问 .data .bss .rodata .text 中的函数,假定静态代码+数据通常具有2GiB的总大小限制。

This uses a 32-bit relative displacement from the end of the current instruction, like jmp/call. This can reach any static data in .data, .bss, .rodata, or function in .text, assuming the usual 2GiB total size limit for static code+data.

依赖于位置的代码(使用 gcc -fno-pie -no-pie 构建例如)在Linux上,您可以利用32位绝对寻址来节省代码大小。另外, mov r32,imm32 在Intel / AMD CPU上的吞吐量比相对于RIP的LEA稍好,因此,乱序执行可能可以与RIP更好地重叠。周围的代码。 (优化代码大小通常比大多数其他事情没有那么重要,但是当所有其他条件相等时,请选择较短的指令。在这种情况下,所有其他都是至少相等,或者在<$ c时也更好$ c> mov imm32 。)

In position dependent code (built with gcc -fno-pie -no-pie for example) on Linux, you can take advantage of 32-bit absolute addressing to save code size. Also, mov r32, imm32 has slightly better throughput than RIP-relative LEA on Intel/AMD CPUs, so out-of-order execution may be able to overlap it better with the surrounding code. (Optimizing for code-size is usually less important than most other things, but when all else is equal pick the shorter instruction. In this case all else is at least equal, or also better with mov imm32.)

请参见在x86-64 Linux中不再允许使用32位绝对地址?,详细了解默认PIE可执行文件的方式。 (这就是为什么使用32位绝对值时出现有关 -fPIC 的链接错误的原因。)

See 32-bit absolute addresses no longer allowed in x86-64 Linux? for more about how PIE executables are the default. (Which is why you got a link error about -fPIC with your use of a 32-bit absolute.)

# 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)。

Note that writing any 32-bit register always zero-extends into the full 64-bit register (R10 and RDI).

lea main,%edi lea main,%rdi 也可以在Linux非PIE可执行文件中使用,但切勿将LEA与 [disp32] 绝对寻址模式(即使在不需要SIB字节的32位代码中); mov 总是至少一样好。

lea main, %edi or lea main, %rdi would also work in a Linux non-PIE executable, but never use LEA with a [disp32] absolute addressing mode (even in 32-bit code where that doesn't require a SIB byte); mov is always at least as good.

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

The operand-size suffix is redundant when you have a register operand that uniquely determines it; I prefer to just write mov instead of movl or movq.

愚蠢/糟糕的方法是将10字节的64位绝对地址作为立即数:

The stupid/bad way is a 10-byte 64-bit absolute address as an immediate:

# Inefficient, DON'T USE
movabs  $main, %r10            # 10 bytes including the 64-bit absolute address

如果您使用 mov rdi,main 代替 mov edi,主要,所以很多人最终都这样做了。 Linux动态链接实际上支持64位绝对地址的运行时修补程序。但是用例是用于跳转表,而不是用于绝对地址的立即数。

This is what you get in NASM if you use mov rdi, main instead of mov edi, main so many people end up doing this. Linux dynamic linking does actually support runtime fixups for 64-bit absolute addresses. But the use-case for that is for jump tables, not for absolute addresses as immediates.

movq $ sign_extended_imm32,%reg (7字节)仍使用32位绝对地址,但在符号扩展的 mov 上浪费代码字节到64位寄存器,而不是从写入32位寄存器隐式扩展到64位。

movq $sign_extended_imm32, %reg (7 bytes) still uses a 32-bit absolute address, but wastes code bytes on a sign-extended mov to a 64-bit register, instead of implicit zero-extension to 64-bit from writing a 32-bit register.

通过使用 movq ,您告诉GAS您想要进行 R_X86_64_32S 重定位,而不是 R_X86_64_64 64位绝对重定位。

By using movq, you're telling GAS you want a R_X86_64_32S relocation instead of a R_X86_64_64 64-bit absolute relocation.

您想要这种编码的唯一原因是内核代码,其中静态地址在64位的高2GiB中,位虚拟地址空间,而不是较低的2GiB。在某些CPU上, mov lea 上具有轻微的性能优势(例如,在更多端口上运行) ,但通常情况下,如果您可以使用32位绝对值,则它位于虚拟地址空间的低2GiB中,其中 mov r32,imm32 起作用。

The only reason you'd ever want this encoding is for kernel code where static addresses are in the upper 2GiB of 64-bit virtual address space, instead of the lower 2GiB. mov has slight performance advantages over lea on some CPUs (e.g. running on more ports), but normally if you can use a 32-bit absolute it's in the low 2GiB of virtual address space where a mov r32, imm32 works.

PS:我故意忽略了任何有关大或大内存/代码模型的讨论,而RIP相对+ -2GiB寻址不能到达静态数据,或者甚至无法到达其他代码地址。以上是针对x86-64 System V ABI的 small和/或 small-PIC代码模型的。对于中型和大型型号,您可能需要 movabs $ imm64 ,但这很少见。

PS: I intentionally left out any discussion of "large" or "huge" memory / code models, where RIP-relative +-2GiB addressing can't reach static data, or maybe can't even reach other code addresses. The above is for x86-64 System V ABI's "small" and/or "small-PIC" code models. You may need movabs $imm64 for medium and large models, but that's very rare.

我不知道如果 mov $ imm32,%r32 可以在带有运行时修复程序的Windows x64可执行文件或DLL中工作,但是相对于RIP的LEA肯定可以。

I don't know if mov $imm32, %r32 works in Windows x64 executables or DLLs with runtime fixups, but RIP-relative LEA certainly does.

半相关的:在x86机器代码中调用绝对指针-如果您正在JITing,请尝试将JIT缓冲区放在现有代码附近,以便您可以调用rel32 ,否则 movabs 指向寄存器的指针。

Semi-related: Call an absolute pointer in x86 machine code - if you're JITing, try to put the JIT buffer near existing code so you can call rel32, otherwise movabs a pointer into a register.

这篇关于如何在GNU汇编器中将函数或标签的地址加载到寄存器中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆