为什么 .NET 程序能够在损坏的堆栈中幸存下来? (当使用错误的调用约定时)

2023-11-22

在VS2010中,托管调试助手会给你一个pInvokeStackImbalance异常(pInvokeStackImbalance MDA) 如果您使用错误的调用约定调用函数,通常是因为您在调用 C 库时没有指定 CallingConvention = Cdecl。例如。你写了

[DllImport("some_c_lib.dll")]
static extern void my_c_function(int arg1, int arg2);

代替

[DllImport("some_c_lib.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void my_c_function(int arg1, int arg2);

因此得到了 StdCall 调用约定而不是 Cdelc。

如果您回答这个问题,您已经知道其中的区别,但对于该线程的其他访问者来说:StdCall 意味着被调用者从堆栈中清除参数,而 Cdecl 意味着调用者清除堆栈。

因此,如果您的 C 代码中的调用约定错误,您的堆栈不会被清理,并且您的程序会崩溃。

然而,.NET 程序似乎不会崩溃,即使它们使用 StdCall 来执行 Cdecl 函数。VS2008 默认情况下未启用堆栈不平衡检查,因此某些 VS2008 项目使用了作者不知道的错误调用约定。我刚刚尝试过GnuMpDotNet即使缺少 Cdelc 声明,示例也运行得很好。这同样适用于X-MPIR.

它们都在调试模式下抛出 pInvokeStackImbalance MDA 异常,但在发布模式下不会崩溃。为什么是这样? .NET VM 是否包装对本机代码的所有调用并随后恢复堆栈本身?如果是这样,为什么还要费心 CallingConvention 属性呢?


这是因为方法退出时堆栈指针的恢复方式。方法的标准序言,针对 x86 抖动显示;

00000000  push        ebp                 ; save old base pointer
00000001  mov         ebp,esp             ; setup base pointer to point to activation frame
00000003  sub         esp,34h             ; reserve space for local variables

以及它的结束方式:

0000014a  mov         esp,ebp             ; restore stack pointer
0000014c  pop         ebp                 ; restore base pointer
0000014d  ret 

使 esp 值不平衡在这里不是问题,它可以从 ebp 寄存器值恢复。然而,当抖动优化器可以将局部变量存储在 CPU 寄存器中时,它经常会优化这一点。当 RET 指令从堆栈中检索到错误的返回地址时,您将崩溃并烧毁。无论如何,希望当它偶然落在一大块机器代码上时真的很糟糕。

当您在没有调试器的情况下运行发布版本时,很可能会发生这种情况,如果没有 MDA 来帮助您,则很难排除故障。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 .NET 程序能够在损坏的堆栈中幸存下来? (当使用错误的调用约定时) 的相关文章

随机推荐