C语言 为什么x86-64中的全局变量是相对于指令指针访问的?

vwkv1x7d  于 2023-01-29  发布在  其他
关注(0)|答案(2)|浏览(149)

我试过用gcc -S -fasm foo.c把c代码编译成汇编代码,c代码在main函数中声明全局变量和变量,如下所示:

int y=6;
int main()
{
        int x=4;
        x=x+y;
        return 0;
}

现在我查看了由C代码生成的汇编代码,我发现全局变量y是使用rip指令指针的值存储的。
我以为只有const全局变量存储在文本段中,但是,看看这个例子,似乎也有常规全局变量存储在文本段中,这很奇怪。
我想我做的一些假设是错误的,所以有人能给我解释一下吗?
c编译器生成的汇编代码:

.file   "foo.c"
        .text
        .globl  y
        .data
        .align 4
        .type   y, @object
        .size   y, 4
y:
        .long   6
        .text
        .globl  main
        .type   main, @function

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $4, -4(%rbp)
        movl    y(%rip), %eax
        addl    %eax, -4(%rbp)
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
at0kjp5o

at0kjp5o1#

可执行文件不同部分之间的偏移量是链接时间常量,因此RIP相对寻址可用于任何部分(包括非const全局变量所在的.data)。请注意asm输出中的.data

这甚至适用于PIE可执行文件或共享库,其中绝对地址直到运行时才知道(ASLR)。
位置无关可执行文件(PIE)的运行时ASLR为整个程序随机化一个基址,而不是相互关联的单个段起始地址。

所有对静态变量的访问都使用RIP相对寻址,因为这是最有效的,即使在位置相关的可执行文件中,绝对寻址也是一个选项(因为静态代码/数据的绝对地址是链接时间常数,在这种情况下不会通过动态链接重新定位)。
相关,可能重复:

在32位x86中,有两种冗余的方式来编码一个没有寄存器的寻址模式和一个disp32绝对地址。(有和没有SIB字节)。x86-64将较短的一个重新用于RIP+rel32,所以mov foo, %eaxmov foo(%rip), %eax长1个字节。
64-位绝对寻址会占用更多的空间,并且仅适用于mov与RAX/EAX/AX/AL之间的寻址,除非您使用单独的指令先将地址放入寄存器。
(In x86-64 Linux PIE/PIC,允许64位绝对寻址,并通过加载时修复将正确地址放入代码或跳转表或静态初始化的函数指针进行处理。因此,代码在技术上不必 * 必须 * 与位置无关,但通常情况下这样做更有效。并且不允许32位绝对寻址,因为ASLR不限于虚拟地址空间的低31位。)
请注意,在非PIE Linux可执行文件中,gcc将使用32位绝对寻址将静态数据的地址放入寄存器。例如,puts("hello");通常编译为

mov   $.LC0, %edi     # mov r32, imm32
call  puts

在默认的非PIE内存模型中,静态代码和数据被链接到虚拟地址空间的低32位,所以32位绝对地址无论是零扩展还是符号扩展到64位都可以工作,这对于索引静态数组也很方便,比如mov array(%rax), %edx;例如add $4, %eax
请参阅32-bit absolute addresses no longer allowed in x86-64 Linux?了解更多关于PIE可执行文件的信息,PIE可执行文件对所有内容都使用位置无关代码,包括RIP相关莱亚,如7字节lea .LC0(%rip), %rdi而不是5字节mov $.LC0, %edi
我提到Linux是因为它从.cfi指令看起来像是在为非Windows平台编译。

yhqotfr8

yhqotfr82#

虽然.data和.text段彼此独立,但一旦链接,它们相对于彼此的偏移量是固定的(至少在gcc x86-64 -mcmodel=small代码模型中,这是默认的代码模型,适用于所有代码+数据小于2GB的程序)。
因此,无论系统在进程的地址空间中加载可执行文件的何处,它们引用的指令和数据都将具有相对于彼此的固定偏移量。
由于这些原因,为(默认)小型代码模型编译的x86-64程序对代码和全局数据都使用RIP相对寻址,这样做意味着编译器不需要专门指定一个寄存器来指向系统加载可执行文件的.data段的位置;程序已经知道它自己的RIP值和它想要访问的全局数据之间的偏移量,所以访问它的最有效方法是通过RIP的32位固定偏移量。
(绝对32位寻址模式会占用更多空间,而64位绝对寻址模式效率更低,仅适用于RAX/EAX/AX/AL。)
你可以在Eli Bendersky的网站上找到更多信息:Understanding the x64 code models

相关问题