gpt4 book ai didi

assembly - 布置堆栈变量开始比 rbp 更接近 rsp 的意义

转载 作者:行者123 更新时间:2023-12-04 08:56:18 24 4
gpt4 key购买 nike

这个问题是关于 x86 汇编的,但我用 C 提供了一个例子,因为我试图检查 GCC 在做什么。
当我遵循各种汇编指南时,我注意到人们,至少是我阅读过的 Material 中的少数人,似乎习惯于将堆栈变量分配为更接近 rsp 而不是 rbp。
然后我检查了 GCC 会做什么,它似乎是一样的。
在下面的反汇编中,首先保留 0x10 个字节,然后调用 Leaf 的结果通过 eax 到 rbp-0xc,常量值 2 到 rbp-0x8,在 rbp-0x8 和 rbp 之间为变量“q”留出空间。
我可以想象在另一个方向上做它,首先在 rbp 处分配一个地址,然后在 rbp-0x4 处,即在 rbp 到 rsp 的方向上进行,然后在 rbp-0x8 和 rsp 之间留一些空间用于“q”。
我不确定的是,我所观察到的事物是否应该是因为我更好地意识到并坚持的一些架构限制,还是纯粹是这种特定实现的产物以及人们习惯的表现我读到的代码我不应该赋予任何意义,例如这需要在一个方向或另一个方向上完成,只要一致,哪个方向无关紧要。
或者,也许我现在只是在阅读和编写琐碎的代码,而这将是双向的,因为我会在一段时间内获得更重要的东西?
我只想知道我应该如何在我自己的汇编代码中处理它。
所有这些都在 Linux 64 位,GCC 版本 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) 上进行。谢谢。

00000000000005fa <leaf>:
5fa: 55 push rbp
5fb: 48 89 e5 mov rbp,rsp
5fe: b8 01 00 00 00 mov eax,0x1
603: 5d pop rbp
604: c3 ret

0000000000000605 <myfunc>:
605: 55 push rbp
606: 48 89 e5 mov rbp,rsp
609: 48 83 ec 10 sub rsp,0x10
60d: b8 00 00 00 00 mov eax,0x0
612: e8 e3 ff ff ff call 5fa <leaf>
617: 89 45 f4 mov DWORD PTR [rbp-0xc],eax ; // <--- This line
61a: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 ; // <-- And this too
621: 8b 55 f4 mov edx,DWORD PTR [rbp-0xc]
624: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
627: 01 d0 add eax,edx
629: 89 45 fc mov DWORD PTR [rbp-0x4],eax
62c: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
62f: c9 leave
630: c3 ret
这是C代码:
int leaf() {
return 1;
}

int myfunc() {
int x = leaf(); // <--- This line
int y = 2; // <-- And this too
int q = x + y;
return q;
}

int main(int argc, char *argv[]) {
return myfunc();
}
我如何编译它:
gcc -O0 main.c -o main.bin
我如何拆卸它:
objdump -d -j .text -M intel main.bin

最佳答案

它产生零差异,对必须存在的局部变量做任何你想做的事情(因为你不能将它们优化到寄存器中)。

GCC 正在做的事情的重要性为零;未使用的间隙在哪里(由于堆栈对齐而存在)并不重要。在这种情况下,它是 [rsp] 处的 4 个字节,又名 [rbp - 0x10] .[rbp - 4] 处的 4 个字节用于 q .
此外,您没有告诉 GCC 进行优化,因此没有理由期望它的选择甚至是最佳的或有用的学习指南。 -O3volatile int本地人会更有意义。 (但由于没有任何重要的事情发生,实际上仍然没有帮助。)

重要的事情:

  • 局部变量应该自然对齐(dword 值至少对齐 4 字节)。 C ABI 要求:alignof(int) = 4。调用之前的 RSP 将是 16 字节对齐的,因此函数入口 RSP-8 是 16 字节对齐的。
  • 代码大小:尽可能多的寻址模式可以使用来自 RBP(或 RSP,如果您寻址与 RSP 相关的本地地址如 gcc -fomit-frame-pointer )的小(有符号 8 位)位移1。
    当您只有几个标量局部变量时,这种情况很简单,远不及 128 个字节。
  • 您可以一起操作的任何本地人都是相邻的,最好不要跨越对齐边界,因此您可以最有效地使用一个 qword 或 XMM 存储来初始化它们。
    如果您有很多局部变量(或数组),如果在此函数(及其子函数)运行时有一整个缓存行可能“冷”,则将它们分组以进行空间局部性。
  • 空间局部性:您之前在函数中使用的变量在堆栈帧中应该更高 (更接近由 call 存储到此函数的返回地址)。堆栈通常在缓存中很热,但是如果在较早的加载/存储之后完成,随着堆栈内存的增长接触新的堆栈内存缓存行的影响会稍微小一些。乱序 exec 有望很快获得那些稍后的存储指令,并将缓存未命中存储进入管道以尽早启动 RFO(读取所有权),从而最大限度地减少早期负载阻塞存储缓冲区所花费的时间。
    这仅在超过 16 字节的边界上有意义;您知道一个 16 字节对齐块中的所有内容都在同一缓存行中。
    一个缓存行内的降序访问模式可能会向下触发下一个缓存行的预取,但我不确定这是否会发生在真实的 CPU 中。如果是这样,这可能是不这样做的一个原因,而是倾向于首先存储到堆栈帧的底部(在 RSP,或您实际使用的最低红色区域地址)。

  • 如果在另一个 call 之前有未使用的堆栈对齐空间,通常最多只有8个字节。这比缓存线小得多,因此对局部变量的空间局部性没有任何显着影响。您知道堆栈指针相对于 16 字节边界的对齐方式,因此在堆栈帧的顶部或底部保留填充的选择永远不会对是否可能触及新的缓存缓存行产生影响。
    如果您将指向本地的指针传递到不同的线程,请注意错误共享:可能将这些本地分隔至少 64 个字节,以便它们位于不同的缓存行中,或者甚至更好地分隔 128 个字节(L2 空间预取器可以创建“破坏性”相邻缓存行之间的干扰”)。

    脚注 1 :x86 符号扩展 8 位与符号扩展 32 位位移在寻址模式(如 [rsp + disp8])中的比较这就是 x86-64 System V ABI 选择 128 字节的原因 red-zone RSP 下方:它最多提供约 256 字节的空间,可以使用更紧凑的代码大小访问,包括红色区域加上 RSP 上方的保留空间。

    PS:
    请注意,您不必在函数中的每个点为相同的高级“变量”使用相同的内存位置。您可以将某些内容溢出/重新加载到函数某个部分的一个位置,然后在函数的另一个位置溢出/重新加载。 IDK 为什么你会,但如果你浪费了对齐的空间,这是你可以做的。可能是如果您希望一个缓存行早期很热(例如,在函数入口的堆栈帧顶部附近),而另一条缓存行稍后会很热(靠近一些其他被大量使用的变量)。
    “变量”是一个高级概念,您可以随意实现。这不是 C,没有要求它有一个地址,或者有相同的地址。 (如果地址没有被占用,或者在内联后没有转义函数,C 编译器在实践中会将变量优化到寄存器中。)
    这是一种跑题或至少是迂腐的转移;通常,当它不能在寄存器中时,您只是为同一件事一致地使用相同的内存位置。

    关于assembly - 布置堆栈变量开始比 rbp 更接近 rsp 的意义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63817094/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com