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

Joh*_*lds 8 .net pinvoke dllimport

在VS2010中,如果使用错误的调用约定调用函数,则托管调试助手将为您提供pInvokeStackImbalance异常(pInvokeStackImbalance MDA),这通常是因为在调用C库时未指定CallingConvention = Cdecl.比如你写的

[DllImport("some_c_lib.dll")]
static extern void my_c_function(int arg1, int arg2);
Run Code Online (Sandbox Code Playgroud)

代替

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

因此获得了StdCall调用约定而不是Cdelc.

如果你回答这个问题,你已经知道了这个差异,但是对于这个线程的其他访问者:StdCall意味着被调用者清除堆栈中的参数,而Cdecl意味着调用者清理堆栈.

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

但是,即使.NET程序使用StdCall进行Cdecl功能,.NET程序似乎也不会崩溃.默认情况下,VS2008上没有启用堆栈不平衡检查,因此一些VS2008项目使用了他们的作者不知道的错误调用约定.我刚尝试过GnuMpDotNet,即使缺少Cdelc声明,样本运行也很好.X-MPIR也是如此.

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

Han*_*ant 7

这是因为方法退出时堆栈指针的恢复方式.一个方法的标准序言,显示为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
Run Code Online (Sandbox Code Playgroud)

它的结束方式:

0000014a  mov         esp,ebp             ; restore stack pointer
0000014c  pop         ebp                 ; restore base pointer
0000014d  ret 
Run Code Online (Sandbox Code Playgroud)

获取esp值不平衡不是问题,它从ebp寄存器值恢复.但是,当抖动优化器可以在cpu寄存器中存储局部变量时,它不会很少优化它.当RET指令从堆栈中检索错误的返回地址时,您将崩溃并刻录.无论如何,当它恰巧落在一大堆机器代码上时,真的很讨厌.

当您在没有调试器的情况下运行发布版本时,这很容易发生,如果您没有MDA来帮助您,则很难进行故障排除.