【问题标题】:Understanding the concept of STDCALL vs CDECL with EBP and ESP cleanup通过 EBP 和 ESP 清理了解 STDCALL 与 CDECL 的概念
【发布时间】:2020-02-15 15:16:38
【问题描述】:

我相信我了解 STDCALL 和 CDECL 之间的区别,但我想知道是否可以在这段代码中找到一些说明。

我了解在 STDCALL 中 CALLEE 负责清理堆栈,并且我了解在 CDECL 中 CALLER 负责清理堆栈。

我也明白“清理堆栈”基本上意味着重新设置堆栈指针,但我想我的 困惑 出现在 esp 的值被移动到的这行代码中ebp,基指针。如果该功能正在发生,那与“清理堆栈”是否相同?或者它必须是专门进入 ESP 的东西?

这是我正在查看的代码

main PROC
    push 4
    push 5
    call sub_12
    push 5
    call sub_48
    add esp, 4
    INVOKE ExitProcess, 0
main endp

sub_12 PROC
    push ebp
    mov ebp, esp
    mov eax, 10
    mul DWORD PTR [ebp+12]
    pop ebp
    ret 8
sub_12 endp

sub_48 PROC
    push ebp
    mov ebp, esp
    mov eax, [ebp+8]
    mul DWORD PTR [ebp+8]
    pop ebp
    ret
sub_48 endp

我原来的答案是 sub_12 和 sub_48 都是 CDECL 因为调用者负责清理堆栈。但现在我一直在看 [mov ebp, esp] 指令,我想知道这是否真的是 STDCALL 的一个例子。

是否有人对我有任何提示或一些我似乎缺少的额外信息?

【问题讨论】:

  • 恢复ebp会清除函数分配的堆栈空间。无论调用约定如何,都必须始终这样做。 Cdecl 与 stdcall 是关于谁清理参数。
  • 我明白了!我认为问题在于我从来没有被教导过如何识别参数
  • 在这两种约定中,参数都是在调用函数之前由调用者推送的。因此,它们位于返回地址正上方的堆栈中。无法确定参数的数量。

标签: assembly x86 callstack calling-convention stdcall


【解决方案1】:

这是关于 CDECL 与 STDCALL 的一个很好的讨论:

不管调用约定如何,被调用者通常会将当前堆栈指针保存到帧指针 (EBP),以便他可以随意将局部变量推入/拉出堆栈。

当他准备好返回时,被调用者必须然后恢复堆栈指针 (ESP) 以使“返回”成功。

问:有意义吗?这能回答你的问题吗?

附加信息:

有两个问题:1) 调用子例程(这部分是“stdcall”与“cdecl”(除其他外 - 这不是唯一的两个选项),以及 2) 从子例程返回。

CDECL 和 STDCALL 的主要区别在于谁负责在“返回”时为局部变量清理堆栈。

被调用者总是恢复堆栈指针。只有这样,“return”才能正常工作。

对于 STDCALL,被调用者 ALSO 清除它自己的局部变量的堆栈。

粗略地说:

  • STDCALL 可能会使用更少的空间,因为“清理代码”只存在于一个地方:在被调用者中。对于 CDECL 调用,必须在调用子例程的任何地方重复清理。您的示例 sub_12 是“STDCALL”。

  • CDECL 更加灵活:它允许您将可变数量的参数传递到子例程中。您的示例 sub_48 是“CDECL”。

'希望对您有所帮助...

【讨论】:

  • 感谢您的帮助!在发布我自己的问题之前,我确实阅读了该讨论,但这无助于消除我的误解。当你说'''被调用者必须恢复堆栈指针''时,我想我更困惑。因为我认为 STDCALL 和 CDECL 之间的区别在于调用者是否负责 ESP 或 CALEE 是否负责 ESP。如果被调用者总是清理它,那么我不确定 CDECL 何时适用。
  • 被调用者总是清理自己的栈帧。使用 stdcall,被调用者 also 清除调用者推送的函数参数。在您的示例代码中,这是通过“ret 8”指令完成的,该指令在弹出返回地址后将 8 添加到 esp。
  • 在您的示例代码中,指令“add esp, 4”(在调用函数中)清除了传递给 cdecl 函数的参数。
  • 对于 CDECL 调用,必须在调用子例程的所有地方重复清理 仅当您不优化调用者时。而不是add esp, 8 / push / push,您可以只使用mov [esp], eax / mov [esp+4], edi 将新参数写入堆栈。即,保留分配的 arg 传递空间并将其重用于同一函数中的未来调用。这对于分支来说不太容易,并且对于代码大小(以字节为单位)来说可能更糟。当然,与带有一些寄存器参数的约定(如 Windows vectorcall 或 x86-64 调用约定)相比,这两种约定都非常糟糕。
  • @Peter Cordes:我的挑战是平衡对复杂主题的简短、“有意义”的回答......而不是肤浅。我试图建议还有其他调用约定(尤其是在其他平台上)。是的,这些约定在 DOS 中很流行,当每个字节都很重要时,CPU 的寄存器数量相对较少,而且 640K 的地址空间似乎比任何人都需要的多:(
猜你喜欢
  • 2011-08-23
  • 1970-01-01
  • 2014-08-30
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多