一个函数的序言是否可以写在它的框架之外?
Does the prologue of a function can write outside of its frame?
我目前正在尝试分析来自 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,但没有使用所有 space:我们只存储 5 个寄存器,所以它最多应该 subs 0x014
- 为什么,这对我来说是主要问题,A1 存储在 SP 之外?堆栈 subs 0x030,但 A1 保存到 SP[0x034]
我根本不是 ASM 方面的专家,所以我可能会错过一些关于堆栈如何工作的东西,但对我来说,序言中的每个保存的数据都应该保存在 SP 和 SP - 0x030 之间(在我的例子中)。
如果我没看错的话,第四行写在另一个函数的栈帧上,好像不好。
一个 MIPS 调用约定让调用者为所有参数分配堆栈内存space,尽管前 4 个参数(至少)实际上是在寄存器中传递的。
这意味着被调用的函数可以将 $a0
、$a1
、$a2
、$a3
存储到堆栈,期望这些内存位置可用。
当一个函数调用另一个函数时,作为调用者它也应该分配相同的 4 个堆栈字 space。无论如何,一个调用另一个函数的函数至少需要一个最小的堆栈帧,因此就序言和结尾而言,让它分配这 4 个额外的单词是免费的。
部分较旧的调用约定往往包括对 varargs(可变参数函数)的支持,这些函数甚至可能无法在 C 代码中正确声明。允许函数将它们的参数存储回调用者分配的内存允许所有参数被刷新到内存并且是连续的,这对于可变参数函数很重要。对所有功能都这样做有点矫枉过正,但简化了一些问题。
有关堆栈帧的图片,请参阅 https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf,其中包含寄存器参数的 4 个字。
在实践中,我认为在调用者的堆栈中保留 4 个字供被调用者使用 space 被高估了,并且作为证据表明这已在 RISC V 中被删除,例如。
一些 Intel 系统改为使用红色区域,表示系统同意在堆栈指针下方的某个小距离内不使用您的堆栈 space(即在未分配的 space堆)。这在英特尔上是有意义的,因为 return 地址会自动写入内存,因此不需要像在 MIPS 上那样单独推送,但这些系统也受益于一些预分配的堆栈 space 来实现简单的功能无需设置堆栈框架(即红色区域)即可使用。
我目前正在尝试分析来自 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,但没有使用所有 space:我们只存储 5 个寄存器,所以它最多应该 subs 0x014
- 为什么,这对我来说是主要问题,A1 存储在 SP 之外?堆栈 subs 0x030,但 A1 保存到 SP[0x034]
我根本不是 ASM 方面的专家,所以我可能会错过一些关于堆栈如何工作的东西,但对我来说,序言中的每个保存的数据都应该保存在 SP 和 SP - 0x030 之间(在我的例子中)。 如果我没看错的话,第四行写在另一个函数的栈帧上,好像不好。
一个 MIPS 调用约定让调用者为所有参数分配堆栈内存space,尽管前 4 个参数(至少)实际上是在寄存器中传递的。
这意味着被调用的函数可以将 $a0
、$a1
、$a2
、$a3
存储到堆栈,期望这些内存位置可用。
当一个函数调用另一个函数时,作为调用者它也应该分配相同的 4 个堆栈字 space。无论如何,一个调用另一个函数的函数至少需要一个最小的堆栈帧,因此就序言和结尾而言,让它分配这 4 个额外的单词是免费的。
部分较旧的调用约定往往包括对 varargs(可变参数函数)的支持,这些函数甚至可能无法在 C 代码中正确声明。允许函数将它们的参数存储回调用者分配的内存允许所有参数被刷新到内存并且是连续的,这对于可变参数函数很重要。对所有功能都这样做有点矫枉过正,但简化了一些问题。
有关堆栈帧的图片,请参阅 https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf,其中包含寄存器参数的 4 个字。
在实践中,我认为在调用者的堆栈中保留 4 个字供被调用者使用 space 被高估了,并且作为证据表明这已在 RISC V 中被删除,例如。
一些 Intel 系统改为使用红色区域,表示系统同意在堆栈指针下方的某个小距离内不使用您的堆栈 space(即在未分配的 space堆)。这在英特尔上是有意义的,因为 return 地址会自动写入内存,因此不需要像在 MIPS 上那样单独推送,但这些系统也受益于一些预分配的堆栈 space 来实现简单的功能无需设置堆栈框架(即红色区域)即可使用。