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

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

问题描述

我正在尝试将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-cache 占用空间和获取/解码带宽).

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 寻址模式,它将阻止加载时地址随机化 (ASLR) 选择随机 64 位(或 47-位)地址.所以 Linux 不支持除了位置相关的可执行文件,或者在 MacOS 上,静态代码/数据总是在低 32 位之外加载.(有关文档和指南的链接,请参阅 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 except in position-dependent executables, or at all on MacOS where static code/data are always loaded outside the low 32 bits. (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:

# RIP-relative LEA always works.  Syntax for various assemblers:
  lea main(%rip), %r10       # AT&T syntax

  lea  r10, [rip+main]       # GAS .intel_syntax noprefix   equivalent
  lea  r10, [rel main]       ; NASM equivalent, or use  default rel
  lea  r10, [main]           ; FASM defaults to RIP-relative.  MASM may also

RIP 相关的变量引用(如[RIP + _a]")如何?在 x86-64 GAS Intel 语法工作中? 对 3 种语法的解释,以及 为什么 x86-64 中的全局变量是相对于指令指针访问的?(和 this) 的原因,为什么 RIP-relative 是处理静态数据的标准方法.

See How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? for an explanation of the 3 syntaxes, and Why are global variables in x86-64 accessed relative to the instruction pointer? (and this) for reasons why RIP-relative is the standard way to address static data.

这使用了从当前指令末尾开始的 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.

在 Linux 上定位依赖代码(例如使用 gcc -fno-pie -no-pie 构建),您可以利用32 位绝对寻址以节省代码大小.此外,mov r32, imm32 在 Intel/AMD CPU 上的吞吐量略高于 RIP 相对 LEA,因此乱序执行可能能够更好地与周围代码重叠.(优化代码大小通常不如大多数其他事情重要,但是当所有其他条件相同时,选择较短的指令.在这种情况下,所有其他至少相同,或者使用 更好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.)

参见 32 位绝对地址 否x86-64 Linux 中不再允许使用? 了解更多关于 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
# same as you'd use in 32-bit code
## 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

;;  mov  edi, main     ; NASM and FASM syntax

请注意,写入任何 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, %edilea 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 而不是 movlmovq.

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, main 这就是你在 NASM 中得到的结果,所以很多人最终都会这样做.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-bit 绝对重定位.

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.mov 在某些 CPU 上比 lea 具有轻微性能优势(例如在更多端口上运行),但通常如果您可以使用 32 位绝对它位于 mov r32, imm32 工作的虚拟地址空间的低 2GiB 中.

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.

(相关:x86-64中movq和movabsq的区别)

PS:我故意省略了对大"的任何讨论.或巨大的"内存/代码模型,其中 RIP 相对 +-2GiB 寻址无法访问静态数据,甚至可能无法访问其他代码地址.以上是针对 x86-64 System V ABI 的小"和/或小型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机器码中调用绝对指针 - 如果您正在 JIT,请尝试将 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.

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

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