首页 > 解决方案 > 组装中的“对齐堆栈”是什么意思?

问题描述

堆栈对齐在 ASMx64 中如何工作?什么时候需要在函数调用之前对齐堆栈,需要减去多少?

我不明白它的目的是什么。我知道还有其他关于此的帖子,但对我来说还不够清楚。例如:

extern foo
global bar

section .text
bar:
  ;some code...
  sub  rsp, 8     ; Why 8 (I saw this on some posts) ? Can it be another value ? Why do we need to substract?
  call foo        ; Do we need to align stack everytime we call a function?
  add  rsp, 8
  ;some code...
  ret

标签: assemblyx86-64memory-alignmentcalling-conventionstack-pointer

解决方案


什么时候需要在函数调用和......之前对齐堆栈?

当您调用的函数需要对齐的堆栈时,您需要对齐堆栈。

用其他语言(例如 C)编写的函数,以及用汇编编写但设计为从其他语言调用的函数,将遵守某种调用约定(其中不仅包括堆栈对齐 - 参数如何通过,参数在哪里,比如“红区”等);对于 64 位 80x86,2 常见调用约定期望堆栈与 16 字节边界对齐。

在一个“纯汇编”项目中,您正在为汇编调用者调用以汇编语言编写的函数;程序员可以自由地做任何他们喜欢的事情(例如,任何对性能最好的事情),而不用关心降低性能的其他语言的限制/限制(调用约定)。在这种情况下,您可能根本不需要对齐堆栈(但如果您正在处理 AVX-512,一个函数可能希望堆栈对齐到 64 个字节,如果您正在处理 AVX2,一个函数可能希望堆栈对齐到 32 个字节,并且 ..)。

...你需要减去多少?

如果您不知道堆栈是否足够对齐;然后对齐堆栈通常使用 AND 完成(例如,可能and rsp,0xFFFFFFFFFFFFFFF0将堆栈对齐到 16 字节边界)。这也意味着您需要将旧的堆栈指针存储在某个地方以便您可以恢复它;这通常意味着还有 4 条指令(在对齐之前,然后是,然后push rbp是稍后恢复)。mov rbp,rspmov rsp,rbppop rbp

然而; 如果您知道您的调用者为您对齐了堆栈(并且您调用的函数需要相同或更少的对齐),那么您可以通过跟踪您在堆栈上推送的数量来计算要减去多少额外内容。例如,如果调用者将堆栈对齐为 32 字节,并且您将四个 64 位(8 字节)值压入堆栈,一条call指令将压入另一个 64 位值(返回地址);那么总共是 5*8 = 40 个字节;所以你知道如果你想对齐到 16 个字节,你需要再减去 8 个字节来得到总共 48 个字节,或者如果你想对齐到 32 个字节,再减去 24 个字节来得到总共 64 个字节。这也避免了保存原始堆栈指针的需要(您可以添加以后减去的任何内容),因此它可以保存 4 条指令。

当然(对于“纯汇编”),您会查看您调用的所有函数的要求并选择最坏的情况并将堆栈对齐一次(并避免多次以不同的方式对齐堆栈,一次为您调用的每个函数); 您可能会说“我的函数要求堆栈与我调用的函数的最坏情况保持一致”以确保您可以计算要减去多少(并避免更昂贵的“AND with ...”方法) . 但是(对于“纯汇编”),这会给您的调用者带来负担(他们可能会给调用者带来负担,可能......)因此它会使性能变得更糟(调用链中的所有祖先都必须这样做额外的工作,这样你就可以避免更少的工作)。换句话说; 对于“纯组装”;

这也是编译器将对齐放在其调用约定中的部分原因 - 所需的“在大多数情况下不太可能是最佳的”标准对齐使编译器更容易。


推荐阅读