Gcc 如何将函数或标签的地址加载到寄存器中

Gcc 如何将函数或标签的地址加载到寄存器中,gcc,assembly,x86-64,att,addressing-mode,Gcc,Assembly,X86 64,Att,Addressing Mode,我试图将“main”的地址加载到GNU汇编程序的寄存器(R10)中。我不能。这里是我所拥有的和收到的错误消息 main: lea main, %r10 我还尝试了以下语法(这次使用mov) 通过以上两种方法,我得到以下错误: /usr/bin/ld: /tmp/ccxZ8pWr.o: relocation R_X86_64_32S against symbol `main' can not be used when making a shared object; recompile wi

我试图将“main”的地址加载到GNU汇编程序的寄存器(R10)中。我不能。这里是我所拥有的和收到的错误消息

main:
   lea main, %r10
我还尝试了以下语法(这次使用mov)

通过以上两种方法,我得到以下错误:

/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-cache占用空间和获取/解码带宽)

leamain,%reg
是一种绝对的
disp32
寻址模式,它将阻止加载时间地址随机化(ASLR)选择随机64位(或47位)地址。因此,在Linux上,除了位置相关的可执行文件,或者在MacOS上,静态代码/数据总是加载在低位32位之外。(有关文档和指南的链接,请参阅。)在Windows上,您可以将可执行文件构建为“大地址感知”或“不感知”。如果不选择,地址将适合32位


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

#RIP relative LEA始终有效。各种汇编程序的语法:
lea main(%rip),%r10#AT&T语法
lea r10,[rip+main]#GAS.intel#
lea r10,[rel main];NASM等效,或使用默认的rel
lea r10,[main];FASM默认为RIP relative。MASM也可能
有关3种语法的解释,请参见;和(和)有关为什么RIP relative是处理静态数据的标准方法的原因,请参见

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


在Linux上的位置相关代码(例如使用
gcc-fno pie-no pie
构建)中,您可以利用32位绝对寻址来节省代码大小。另外,
mov r32、imm32
在Intel/AMD CPU上的吞吐量略高于RIP相对LEA,因此无序执行可能会更好地与周围的代码重叠。(优化代码大小通常不如其他大多数事情重要,但当所有其他因素都相等时,选择较短的指令。在这种情况下,所有其他因素至少相等,或者使用
mov imm32
更好)

有关如何将饼图可执行文件作为默认文件的详细信息,请参见。(这就是为什么使用32位绝对值时出现关于
-fPIC
的链接错误。)

#在非饼图可执行文件中,将imm32移动到32位寄存器更好
#与您在32位代码中使用的相同
##气体AT&T语法
mov$main,%r10d#6字节
mov$main,%edi#5字节:“遗留”寄存器不需要REX前缀
##GAS.intel\u语法
移动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\u extended\u imm32,%reg
(7字节)仍然使用32位绝对地址,但在符号上浪费代码字节,将
mov
扩展到64位寄存器,而不是写入32位寄存器时隐式零扩展到64位

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

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

(相关:)


PS:我有意省略了任何关于“大”或“大”内存/代码模型的讨论,其中RIP relative+-2GiB寻址无法访问静态数据,或者甚至可能无法访问其他代码地址。以上内容适用于x86-64 System V ABI的“小型”和/或“小型PIC”代码模型。对于中型和大型机型,您可能需要
movabs$imm64
,但这种情况非常罕见

我不知道
mov$imm32,%r32
是否适用于Windows x64可执行文件或具有运行时修复的DLL,但RIP relative LEA肯定适用


半相关:-如果要进行JIT,请尝试将JIT缓冲区放在现有代码附近,以便
调用rel32
,否则
movabs
将指针放入寄存器。

在x86-64中,大多数即时和置换仍然是32位,因为64位会浪费太多的代码大小(I-cache占用空间和获取/解码带宽)
/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