Почему программа .NET переживает поврежденный стек? (при использовании неправильного соглашения о вызовах) - PullRequest
8 голосов
/ 19 марта 2011

В VS2010 помощник по управляемой отладке выдаст вам исключение pInvokeStackImbalance ( pInvokeStackImbalance MDA ), если вы вызываете функцию с использованием неправильного соглашения о вызовах, обычно потому, что вы не указали 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);

и получили вместо Cdelc соглашение о вызовах StdCall.

Если вы ответите на это, вы уже знаете разницу, но для других посетителей этого потока: StdCall означает, что вызываемый объект очищает аргументы из стека, тогда как Cdecl означает, что вызывающий объект очищает стек.

Итак, если вы неправильно указали соглашение о вызовах в коде C, ваш стек не будет очищен, и ваша программа вылетит.

Однако программы .NET, похоже, не аварийно завершают работу, даже если они используют StdCall для функций Cdecl. Проверка дисбаланса стека не была включена по умолчанию на VS2008, поэтому некоторые проекты VS2008 используютнеправильное соглашение о вызовах без ведома их авторов.Я только что попробовал GnuMpDotNet , и пример работает просто отлично, хотя объявление Cdelc отсутствует.То же самое относится и к X-MPIR .

Они оба выдают исключение pInvokeStackImbalance MDA в режиме отладки, но не аварийно завершают работу в режиме выпуска.Почему это?Виртуальная машина .NET объединяет все вызовы с собственным кодом и восстанавливает сам стек после этого?Если это так, зачем вообще беспокоиться о свойстве CallingConvention?

Ответы [ 2 ]

7 голосов
/ 19 марта 2011

Это из-за способа восстановления указателя стека при выходе из метода. Стандартный пролог метода, показанный для джиттера 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. Однако оптимизатор джиттера нередко оптимизирует это, когда он может хранить локальные переменные в регистрах процессора. Вы будете аварийно завершать работу, когда инструкция RET получит неправильный адрес возврата из стека. Надеюсь, в любом случае, действительно неприятно, когда случается случайное попадание на кусок машинного кода.

Это может произойти, когда вы запускаете сборку релиза без отладчика, трудно устранить неполадки, если у вас нет MDA, чтобы помочь вам.

3 голосов
/ 19 марта 2011

Среда выполнения может обнаружить дисбаланс стека, потому что указатель стека не там, где он ожидается. То есть в случае StdCall, где ожидаемая функция должна очистить стек, среда выполнения могла бы сделать это:

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

Теперь, если значение SP меньше, чем SavedSP, в стеке есть дополнительные данные - это означает, что среда выполнения может просто продолжить и восстановить сохраненный указатель стека.

Среда выполнения всегда должна обнаруживать дисбаланс в стеке. Могу ли я всегда выздоравливать, мне неизвестно. Но в случае непреднамеренного вызова метода Cdecl как StdCall он должен быть в состоянии восстановиться без проблем, так как в стеке будет дополнительный материал, который он может игнорировать.

А зачем? Как вы говорите, разница между StdCall и Cdecl заключается только в том, кто отвечает за очистку стека. Кроме того, StdCall не совместим со списками переменных аргументов (т.е. printf в C), хотя я не знаю, возможно ли вообще вызывать такой метод из .NET (никогда не было необходимости). В любом случае, хотя не возникает особой проблемы с вызовом Cdecl метода с StdCall, мне нравится знать, что существует потенциальная ошибка. Для меня это похоже на сообщение об ошибке, которое выдает компилятор, когда вы пишете:

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

Я знаю, что с назначением все в порядке, но компилятор запрещает его, потому что это потенциальный источник ошибок. На мой взгляд, несбалансированный стек является потенциальным источником ошибок. Нет, это - это ошибка, которая может вызвать некоторые очень плохие вещи. Я бы предпочел, чтобы среда выполнения сказала мне об этом, чтобы я мог решить проблему.

...