开发者

Why does a .NET program survive a corrupt stack? (when using the wrong calling convention)

开发者 https://www.devze.com 2023-02-17 23:20 出处:网络
In VS2010, the managed debugging assistant will give you a pInvokeStackImbalance exception (pInvokeStackImbalance MDA) if you call a function using the wrong calling convention, commonly because you d

In VS2010, the managed debugging assistant will give you a pInvokeStackImbalance exception (pInvokeStackImbalance MDA) if you call a function using the wrong calling convention, commonly because you didn't specify CallingConvention = Cdecl when calling a C library. E.g. you wrote

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

instead of

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

and thus got the StdCall calling convention instead of Cdelc.

If you answer this, you already know the difference, but for other visitors to this thread: StdCall means that the callee cleans the arguments from the stack, whereas Cdecl means that the caller cleans up the stack.

So, if you get the calling convention wrong in your C code, your stack doesn't get cleaned up and your program crashes.

However, .NET programs don't seem to crash even though they use StdCall for Cdecl functions. The stack imbalance check wasn't enabled by default on VS2008, so some VS2008 projects use the wrong calling convention unbeknownst to their authors. I just tried GnuMpDotNet and the sample runs just fine even though the Cdelc declaration is missing. The same holds true for X-MPIR.

They both throw the pInvokeStackImbalance MDA exception in debug mode, but don't crash in release mode. Why is this? Does the .NET VM wrap all calls to native code and restore the stack itself afterwards? If so, why bother 开发者_如何学编程with the CallingConvention property at all?


It is because of the way the stack pointer is restored when the method exits. The standard prologue of a method, shown for the x86 jitter;

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

And the way it ends:

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

Getting the esp value unbalanced is not a problem here, it gets restored from the ebp register value. However, the jitter optimizer not infrequently optimizes this away when it can store local variables in cpu registers. You'll crash and burn when the RET instruction retrieves the wrong return address from the stack. Hopefully anyway, really nasty when it happens to land on a chunk of machine code by accident.

This is liable to happen when you run the release build without a debugger, tough to troubleshoot if you didn't have the MDA to help you.


The runtime can detect the stack imbalance because the stack pointer isn't where it's expected. That is, in the case of StdCall, where the called function is expected to clean up the stack, then the runtime could do this:

SavedSP = SP; // save the stack pointer
// now push parameters
// call the external function.
if (SP != SavedSP)
{
    // error!
}

Now, if the value of SP is less than SavedSP, then there's extra stuff on the stack--meaning that the runtime can just go ahead and restore the saved stack pointer.

The runtime should always be able to detect a stack imbalance. Whether or not it can always recover is unknown to me. But in the case of inadvertently calling a Cdecl method as StdCall, it should be able to recover without trouble, since there will be extra stuff on the stack that it can ignore.

As to why bother? As you say, the difference between StdCall and Cdecl is really only who's responsible for stack cleanup. Also, StdCall is not compatible with variable argument lists (i.e. printf in C), although I don't know if it's even possible to call such a method from .NET (haven't ever had a need to). In any case, although there doesn't appear to be a particular problem with calling a Cdecl method with StdCall, I kind of like knowing that there's a potential error. To me, it's like the error message that the compiler gives when you write:

uint x = 3;
int y = x;  // error!

I know that the assignment is okay, but the compiler disallows it because it's a potential source of bugs. In my mind, an unbalanced stack is a potential source of bugs. No, it is a bug that can cause some very bad things to happen. I'd rather the runtime told me about it so that I can fix the problem.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号