如何将函数或标签的地址加载到寄存器中
How to load address of function or label into register
我正在尝试将 '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 缓存占用空间和 fetch/decode 带宽)。
lea main, %reg
是一种绝对 disp32
寻址模式,它将阻止加载时地址随机化 (ASLR) 选择随机 64 位(或 47 位)地址。因此 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 到文档和指南。)在 Windows 上,您可以将可执行文件构建为“大地址感知”或不。如果您不选择,地址将适合 32 位。
将静态地址放入寄存器的标准有效方法是相对于 RIP 的 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
请参阅 How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? for an explanation of the 3 syntaxes, and (and ) 以了解为什么相对 RIP 是解决静态数据的标准方法的原因。
这使用当前指令末尾的 32 位相对位移,如 jmp
/call
。这可以达到 .data
、.bss
、.rodata
中的任何静态数据或 .text
中的函数,假设静态代码+数据的总大小限制通常为 2GiB。
在 dependent 代码(例如用 gcc -fno-pie -no-pie
构建)在 Linux 上,你 可以 利用 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
的 link 错误。)
# 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)。
lea main, %edi
或 lea main, %rdi
也可以在 Linux 非 PIE 可执行文件中工作,但切勿将 LEA 与 [disp32]
绝对寻址模式一起使用(即使在 32-不需要 SIB 字节的位代码); mov
总是至少一样好。
当你有一个唯一确定它的寄存器操作数时,操作数大小后缀是多余的;我更喜欢只写 mov
而不是 movl
或 movq
.
stupid/bad方式是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 动态 linking 实际上支持 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 space,而不是低 2GiB。 mov
在某些 CPU 上比 lea
有 轻微 的性能优势(例如,在更多端口上 运行),但通常如果你可以使用 32 位绝对是在虚拟地址 space 的低 2GiB 中,其中 mov r32, imm32
有效。
(相关:)
PS:我故意省略了任何关于“大”或“巨大”内存/代码模型的讨论,其中 RIP 相关 +-2GiB 寻址无法访问静态数据,或者可能无法访问甚至到达其他代码地址。以上是针对 x86-64 System V ABI 的“small”and/or“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
一个指向寄存器的指针.
我正在尝试将 '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 缓存占用空间和 fetch/decode 带宽)。
lea main, %reg
是一种绝对 disp32
寻址模式,它将阻止加载时地址随机化 (ASLR) 选择随机 64 位(或 47 位)地址。因此 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 到文档和指南。)在 Windows 上,您可以将可执行文件构建为“大地址感知”或不。如果您不选择,地址将适合 32 位。
将静态地址放入寄存器的标准有效方法是相对于 RIP 的 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
请参阅 How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? for an explanation of the 3 syntaxes, and
这使用当前指令末尾的 32 位相对位移,如 jmp
/call
。这可以达到 .data
、.bss
、.rodata
中的任何静态数据或 .text
中的函数,假设静态代码+数据的总大小限制通常为 2GiB。
在 dependent 代码(例如用 gcc -fno-pie -no-pie
构建)在 Linux 上,你 可以 利用 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
的 link 错误。)
# 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)。
lea main, %edi
或 lea main, %rdi
也可以在 Linux 非 PIE 可执行文件中工作,但切勿将 LEA 与 [disp32]
绝对寻址模式一起使用(即使在 32-不需要 SIB 字节的位代码); mov
总是至少一样好。
当你有一个唯一确定它的寄存器操作数时,操作数大小后缀是多余的;我更喜欢只写 mov
而不是 movl
或 movq
.
stupid/bad方式是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 动态 linking 实际上支持 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 space,而不是低 2GiB。 mov
在某些 CPU 上比 lea
有 轻微 的性能优势(例如,在更多端口上 运行),但通常如果你可以使用 32 位绝对是在虚拟地址 space 的低 2GiB 中,其中 mov r32, imm32
有效。
(相关:
PS:我故意省略了任何关于“大”或“巨大”内存/代码模型的讨论,其中 RIP 相关 +-2GiB 寻址无法访问静态数据,或者可能无法访问甚至到达其他代码地址。以上是针对 x86-64 System V ABI 的“small”and/or“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
一个指向寄存器的指针.