Странная ошибка MSC 8.0: «Значение ESP не было правильно сохранено при вызове функции ...» - PullRequest
45 голосов
/ 27 сентября 2008

Недавно мы попытались разбить некоторые из наших проектов Visual Studio на библиотеки, и, похоже, все компилировалось и строилось нормально в тестовом проекте с одним из библиотечных проектов в качестве зависимости. Однако, попытка запустить приложение дала нам следующее неприятное сообщение об ошибке во время выполнения:

Ошибка проверки времени выполнения # 0 - значение ESP не было должным образом сохранено при вызове функции. Обычно это результат вызова указателя функции, объявленного с другим соглашением о вызовах.

Мы никогда даже не указывали соглашения о вызовах (__cdecl и т. Д.) Для наших функций, оставляя все переключатели компилятора по умолчанию. Я проверил, и настройки проекта соответствуют правилам вызова для библиотеки и тестовых проектов.

Обновление: один из наших разработчиков изменил настройку проекта «Базовые проверки времени выполнения» с «Оба (/ RTC1, эквивалентно / RTCsu)» на «По умолчанию», и время выполнения исчезло, оставив программу работающей, по-видимому, правильно. Я не доверяю этому вообще. Это было правильное решение или опасный взлом?

Ответы [ 19 ]

1 голос
/ 18 апреля 2014

Это случилось со мной при доступе к COM-объекту (Visual Studio 2010). Я передал GUID для другого интерфейса A в моем вызове QueryInterface, но затем я преобразовал полученный указатель как интерфейс B. Это привело к вызову функции для объекта с полностью сигнатурой, которая учитывает наличие стека (и ESP) испортил.

Передача GUID для интерфейса B устранила проблему.

1 голос
/ 27 сентября 2008

есть ли у вас прототипы функций typedef'd (например, int (* fn) (int a, int b))

если вы думаете, что вы, возможно, ошиблись в прототипе.

ESP - это ошибка при вызове функции (можете ли вы указать, какая из них в отладчике?) Имеет несоответствие параметров - т. Е. Стек восстановился до состояния, в котором он был запущен при вызове функции.

Вы также можете получить это, если загружаете функции C ++, которые должны быть объявлены, extern C - C использует cdecl, C ++ использует соглашение о вызовах stdcall по умолчанию (IIRC). Поместите несколько внешних оболочек C вокруг импортированных прототипов функций, и вы можете это исправить.

Если вы сможете запустить его в отладчике, вы сразу увидите функцию. Если нет, вы можете настроить DrWtsn32 для создания мини-дампа, который вы можете загрузить в windbg, чтобы увидеть стек вызовов во время ошибки (вам понадобятся символы или файл карты, чтобы увидеть имена функций).

1 голос
/ 27 сентября 2008

Другой случай, когда esp может быть испорчен, связан с непреднамеренным переполнением буфера, обычно из-за ошибочного использования указателей для работы за границей массива. Скажем, у вас есть функция C, которая выглядит как

int a, b[2];

Запись в b[3], вероятно, изменит a, и в любом месте после этого, вероятно, попадет сохраненный esp в стек.

1 голос
/ 28 сентября 2008

Вы получите эту ошибку, если функция вызывается с соглашением о вызовах, отличным от того, для которого она скомпилирована.

Visual Studio использует настройку соглашения о вызовах по умолчанию, которая указана в параметрах проекта. Проверьте, одинаково ли это значение в настройках оригинального проекта и в новых библиотеках. Чрезмерно амбициозный разработчик мог бы установить для этого значения _stdcall / pascal в оригинале, поскольку это уменьшает размер кода по сравнению с cdecl по умолчанию. Таким образом, базовый процесс будет использовать этот параметр, и новые библиотеки получат cdecl по умолчанию, который вызывает проблему

Поскольку вы сказали, что не используете никаких специальных соглашений о вызовах, это, вероятно, хорошая вероятность.

Также выполните diff для заголовков, чтобы увидеть, являются ли объявления / файлы, которые видит процесс, теми же, с которыми скомпилированы библиотеки.

ps: сделать предупреждение - BAAAD. основная ошибка все еще сохраняется.

0 голосов
/ 27 сентября 2008

ESP - указатель стека. Таким образом, по словам компилятора, ваш указатель стека испортился. Трудно сказать, как (или если) это могло бы произойти, не видя некоторый код.

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

0 голосов
/ 02 марта 2017

Не самый лучший ответ, но я просто перекомпилировал свой код с нуля (пересобрать в VS), и тогда проблема исчезла.

0 голосов
/ 31 октября 2014

У меня была такая же проблема здесь на работе. Я обновлял какой-то очень старый код, который вызывал указатель на функцию FARPROC. Если вы не знаете, FARPROC - это функциональные указатели с нулевым типом безопасности. Это эквивалент C указателя функции typdef, без проверки типа компилятора. Например, скажем, у вас есть функция, которая принимает 3 параметра. Вы указываете на него FARPROC, а затем вызываете его с 4 параметрами вместо 3. Дополнительный параметр помещает лишний мусор в стек, и когда он появляется, ESP теперь отличается от того, когда он запускался. Поэтому я решил эту проблему, удалив дополнительный параметр для вызова вызова функции FARPROC.

0 голосов
/ 25 мая 2012

Вот урезанная программа на C ++, которая выдает эту ошибку. Скомпилированное использование (Microsoft Visual Studio 2003) приводит к вышеупомянутой ошибке.

#include "stdafx.h"
char* blah(char *a){
  char p[1];
  strcat(p, a);
  return (char*)p;
}
int main(){
  std::cout << blah("a");
  std::cin.get();
}

ОШИБКА: «Ошибка проверки времени выполнения # 0 - значение ESP не было должным образом сохранено при вызове функции. Обычно это является результатом вызова функции, объявленной с одним соглашением о вызовах с указателем функции, объявленным с другим соглашением о вызовах».

0 голосов
/ 27 сентября 2008

Если вы используете какие-либо функции обратного вызова с Windows API, они должны быть объявлены с использованием CALLBACK и / или WINAPI. Это будет применять соответствующие декорации, чтобы компилятор генерировал код, который правильно очищает стек. Например, на компиляторе Microsoft это добавляет __stdcall.

Windows всегда использовала соглашение __stdcall, поскольку оно приводит к (немного) меньшему коду, причем очистка происходит в вызываемой функции, а не на каждом сайте вызова. Однако он несовместим с функциями varargs (поскольку только вызывающий объект знает, сколько аргументов они выдвинули).

...