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

时间:2019-05-22 18:26:50

标签: c assembly compiler-construction x86-64

我尝试使用gcc -S -fasm foo.c将C代码编译为汇编代码。 C代码在全局函数中声明全局变量和变量,如下所示:

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:

2 个答案:

答案 0 :(得分:6)

可执行文件的不同部分之间的偏移量是链接时间常数,因此RIP相对寻址可用于任何部分(包括.data,其中非const全球)。请注意您的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或从RAX / EAX / AX / AL来回的地址,除非您先使用单独的指令将地址放入寄存器。 / p>

(在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

有关PIE可执行文件的更多信息,请参见32-bit absolute addresses no longer allowed in x86-64 Linux?,该文件对所有内容都使用与位置无关的代码,包括相对于RIP的LEA,例如7字节lea .LC0(%rip), %rdi而不是5字节mov $.LC0, %edi。 / p>

我之所以提到Linux,是因为它从.cfi指令中看起来就像您正在为非Windows平台进行编译。

答案 1 :(得分:2)

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

相关问题