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

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

问题描述

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

 主要:lea main,%r10 

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

 主要:movq $ main,%r10 

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

 /usr/bin/ld:/tmp/ccxZ8pWr.o:在创建共享库时,不能使用针对符号'main'的重定位R_X86_64_32S;用-fPIC重新编译/usr/bin/ld:最终链接失败:输出中的不可代表部分collect2:错误:ld返回1退出状态 

使用-fPIC编译无法解决问题,只是给了我相同的确切错误.

解决方案

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

lea main,%reg 是绝对的 disp32 寻址模式,它将阻止加载时地址随机化(ASLR)选择随机的64位(或47-位)地址.因此,在Linux上不受支持除了与位置相关的可执行文件外,或者在所有MacOS上(总是将静态代码/数据始终加载到低32位之外)的情况除外.(有关指向文档和指南的链接,请参见 x86标签Wiki .)在Windows上,您可以将可执行文件构建为大地址"意识到"或不.如果不选择,地址将以32位为准.


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

 <代码>#相对于RIP的LEA始终有效.各种汇编器的语法:lea main(%rip),%r10#AT& T语法lea r10,[rip + main]#GAS .intel_syntax noprefix等效项lea r10,[rel main];相当于NASM,或使用默认rellea r10,[main];FASM默认为相对RIP.MASM也可能 

请参见来解释这3种语法,以及),原因是RIP相对是解决静态数据的标准方法.

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


在Linux上的

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

请参见 32位绝对地址在x86-64 Linux中允许更长的时间?,以了解更多有关默认PIE可执行文件的信息.(这就是为什么使用32位绝对值时出现关于 -fPIC 的链接错误的原因.)

在非PIE可执行文件中的

 #,将imm32移动到32位寄存器中甚至更好#与您在32位代码中使用的相同## GAS AT& T语法mov $ main,%r10d#6个字节mov $ main,%edi#5个字节:旧版"无需REX前缀登记## GAS .intel_syntaxmov edi,偏移量主;;mov edi,main;NASM和FASM语法 

请注意,始终将所有32位寄存器 零扩展到完整的64位寄存器(R10和RDI)中.

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

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


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

 #低效,请勿使用movabs $ main,%r10#10个字节,包括64位绝对地址 

如果您使用 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中.

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


PS:我特意省去了对大公司"的任何讨论.或巨大"内存/代码模型,其中相对于RIP的+ -2GiB寻址无法到达静态数据,甚至可能无法到达其他代码地址.上面是针对x86-64系统V ABI的小型"产品.和/或小型PIC"代码模型.对于中型和大型型号,您可能需要 movabs $ imm64 ,但这很少见.

我不知道 mov $ imm32,%r32 是否可以在Windows x64可执行文件或带有运行时修补程序的DLL中工作,但是相对于RIP的LEA确实可以.

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

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

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

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

解决方案

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 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.


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

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.

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.


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.)

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

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

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.

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.


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

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 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.

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.

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.

(Related: Difference between movq and movabsq in x86-64)


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.

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

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天全站免登陆