函数的序言可以写在它的框架之外吗?

问题描述

我目前正在尝试分析来自 N64 的旧视频游戏的组装。为此,我使用了一些 N64 调试器来阅读和理解底层的 MIPS 代码

在我看到的其中一个调用中,序言的定义如下:

ADDIR SP,SP,-0x030
SW    RA,0x0024 (SP)
SW    S0,0x0020 (SP)
SW    A1,0x0034 (SP)

在这函数后面,我们还有两个堆栈推送:

SW    V0,0x002C (SP)
[...]
SW    R0,0x0010 (SP)

我不明白的是:

  • 为什么 prologue subs 0x030,但没有使用所有空间:我们只存储 5 个寄存器,所以它最多应该 subs 0x014
  • 为什么,这是我在这里的主要问题,A1 是存储在 SP 之外?堆栈子 0x030,但 A1 保存到 SP[0x034]

我根本不是 ASM 方面的专家,所以我可能会错过关于堆栈如何工作的一些内容,但对我来说,序言中保存的每个数据都应该保存在 beetwen SP ans SP - 0x030(在我的情况下)。 如果我理解正确,第四行写在另一个函数的堆栈帧上,看起来很糟糕。

解决方法

一个 MIPS 调用约定让调用者为所有参数分配堆栈内存空间,尽管前 4 个参数(至少)实际上是在寄存器中传递的。

这意味着被调用的函数可以将 $a0$a1$a2$a3 存储到堆栈中,期望这些内存位置可用。

当一个函数调用另一个函数时,作为调用者,它也应该分配相同的 4 个字的堆栈空间。无论如何,调用另一个函数的函数至少需要一个最小的堆栈帧,因此就序言和结尾而言,让它分配这 4 个额外的字是免费的。

部分较旧的调用约定往往包括对可变参数(可变参数函数)的支持,这些参数甚至可能无法在 C 代码中正确声明。允许函数将其参数存储回调用者分配的内存允许将所有参数刷新到内存并保持连续,这对于可变参数函数很重要。对所有功能都这样做是矫枉过正,但简化了一些问题。

参见 https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf 以获得包含寄存器参数的 4 个字的堆栈帧的图片。

在实践中,我认为在调用者的堆栈空间中保留 4 个字供被调用者使用被高估了,并且作为证据会注意到,例如,在 RISC V 中这已被删除。


某些英特尔系统使用红色区域代替,这表示系统同意不在堆栈指针下方的某个小距离内(即在堆栈的未分配空间中)使用您的堆栈空间。这在英特尔上是有意义的,因为返回地址会自动写入内存,因此不需要像在 MIPS 上那样单独推送,但这些系统也受益于一些预先分配的堆栈空间,以便使用简单的函数而不必设置堆栈框架(即红色区域)。