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

标签 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:

最佳答案

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

这甚至适用于 PIE 可执行文件或共享库,其中绝对地址未知直到运行时 (ASLR)。

位置无关可执行文件 (PIE) 的运行时 ASLR 随机化整个程序的一个基地址,而不是相对于彼此的各个段起始地址。

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


相关并且可能重复:


在 32 位 x86 中,有 2 种冗余方式来编码无寄存器寻址模式和 disp32 绝对地址。 (有和没有 SIB 字节)。 x86-64 将较短的一个重新用作 RIP+rel32,因此 mov foo, %eaxmov foo(%rip), %eax< 长 1 个字节.

64 位绝对寻址将占用更多空间,并且仅适用于 mov 到/从 RAX/EAX/AX/AL 除非您使用单独的指令将地址放入寄存器首先。

(在 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例如添加 $4, %eax

参见 32-bit absolute addresses no longer allowed in x86-64 Linux?有关 PIE 可执行文件的更多信息,它对所有内容都使用与位置无关的代码,包括 RIP 相关的 LEA,如 7 字节 lea .LC0(%rip), %rdi 而不是 5 字节 mov $.LC0, %edi.参见 How to load address of function or label into register

我提到 Linux 是因为它从 .cfi 指令看起来就像是为非 Windows 平台编译。

关于c - 为什么 x86-64 中的全局变量是相对于指令指针访问的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56262889/

相关文章:

复制位操作的指针

c++ - 如何以图形方式显示 .map 文件中的内存布局?

assembly - GDB ret "cannot access memory at address"

debugging - 如何在 QEMU 内使用 GDB 对 x86 代码进行源代码级调试?

compiler-construction - 混合 C++/CLI TypeLoadException 内部限制 : too many fields

optimization - 是否可以优化编译后的二进制文件?

c++ - 如何减少简单的 C++ 应用程序大小? (使用 RAD Studio 2010 c++ builder 编译)

c - 如何从 C 中的整数数组构建二叉搜索树?

c++ - .load(std::memory_order_relaxed) 的成本是否与读取非原子变量相同?

c++ - C中相同优先级操作数的计算顺序