Ошибка Weird MSC 8.0: "Значение ESP не было должным образом сохранено в вызове функции..."
Недавно мы попытались разбить некоторые из наших проектов Visual Studio на библиотеки, и все, казалось, скомпилировалось и построилось в тестовом проекте с одним из проектов библиотеки как зависимость. Однако попытка запустить приложение дала нам следующее неприятное сообщение об ошибке во время выполнения:
Ошибка проверки времени выполнения # 0 - значение ESP не было должным образом сохранено во время вызова функции. Обычно это результат вызова указателя функции, объявленного с другим соглашением о вызовах.
Мы даже не указали соглашения о вызовах (__cdecl и т.д.) для наших функций, оставив все параметры компилятора по умолчанию. Я проверил, и параметры проекта согласованы для вызова соглашения по библиотеке и тестовым проектам.
Обновление. Один из наших разработчиков изменил параметр проекта "Основные проверки выполнения" с "Both (/RTC1, equiv. to/RTCsu)" на "Default", и время выполнения исчезло, оставив программу явно корректной. Я не доверяю этому вообще. Было ли это правильным решением или опасным взломом?
Ответы
Ответ 1
Эта ошибка отладки означает, что после вызова функции регистр указателя стека не возвращается к его исходному значению, т.е. количество нажатий перед вызовом функции не сопровождалось равным количеством всплывающих окон после вызова.
Есть две причины для этого, которые я знаю (оба с динамически загруженными библиотеками). # 1 - то, что VС++ описывает в сообщении об ошибке, но я не думаю, что это чаще всего является причиной ошибки (см. # 2).
1) Несоответствующие условные соглашения:
У вызывающего и вызываемого абонента нет надлежащего соглашения о том, кто собирается что-то делать. Например, если вы вызываете DLL-функцию, которая является _stdcall
, но вы по какой-то причине объявили ее как _cdecl
(по умолчанию в VС++) в вашем вызове. Это может произойти много, если вы используете разные языки в разных модулях и т.д.
Вам нужно будет проверить объявление оскорбительной функции и убедиться, что она не объявлена дважды, и по-разному.
2) Несоответствующие типы:
Вызывающий и вызываемый не скомпилированы с одинаковыми типами. Например, общий заголовок определяет типы в API и недавно изменился, и один модуль был перекомпилирован, а другой - нет. некоторые типы могут иметь разный размер в вызывающем и вызываемом абонентах.
В этом случае вызывающий нажимает аргументы одного размера, но вызываемый (если вы используете _stdcall
, где вызывающий очищает стек) выдает разный размер. Таким образом, ESP не возвращается к правильному значению.
(Конечно, эти аргументы и другие под ними, казалось бы, искажены в вызываемой функции, но иногда вы можете выжить без видимого сбоя.)
Если у вас есть доступ ко всему коду, просто перекомпилируйте его.
Ответ 2
Я читал это на другом форуме
У меня была одна и та же проблема, но я просто исправил ее. Я получал ту же ошибку из следующего кода:
HMODULE hPowerFunctions = LoadLibrary("Powrprof.dll");
typedef bool (*tSetSuspendStateSig)(BOOL, BOOL, BOOL);
tSetSuspendState SetSuspendState = (tSuspendStateSig)GetProcAddress(hPowerfunctions, "SetSuspendState");
result = SetSuspendState(false, false, false); <---- This line was where the error popped up.
После некоторого исследования я сменил одну из строк на:
typedef bool (WINAPI*tSetSuspendStateSig)(BOOL, BOOL, BOOL);
который решил проблему. Если вы посмотрите в файл заголовка, где найден SetSuspendState (powrprof.h, часть SDK), вы увидите, что прототип функции определяется как:
BOOLEAN WINAPI SetSuspendState(BOOLEAN, BOOLEAN, BOOLEAN);
Итак, у вас, ребята, есть аналогичная проблема. Когда вы вызываете данную функцию из .dll, ее подпись, вероятно, выключена. (В моем случае это отсутствовало ключевое слово WINAPI).
Надеюсь, что это поможет любому будущему людям!: -)
Приветствия.
Ответ 3
Отключение проверки не является правильным решением. Вы должны выяснить, что перепутано с вашими соглашениями.
Существует несколько способов изменить вызывающую конвецию функции без явного указания ее. extern "C" сделает это, STDMETHODIMP/IFACEMETHODIMP также это сделает, другие макросы тоже могут это сделать.
Я полагаю, что если вы запускаете свою программу под WinDBG (http://www.microsoft.com/whdc/devtools/debugging/default.mspx), время выполнения должно прерываться в тот момент, когда вы нажимаете эту проблему. Вы можете посмотреть стек вызовов и выяснить, какая функция имеет проблему, а затем посмотреть ее определение и объявление, которое использует вызывающий.
Ответ 4
Я видел эту ошибку, когда код пытался вызвать функцию на объекте, который не был ожидаемого типа.
Итак, иерархия классов: родительский с детьми: Child1 и Child2
Child1* pMyChild = 0;
...
pMyChild = pSomeClass->GetTheObj();// This call actually returned a Child2 object
pMyChild->SomeFunction(); // "...value of ESP..." error occurs here
Ответ 5
Я получал аналогичную ошибку для API AutoIt, которую я вызывал из программы VС++.
typedef long (*AU3_RunFn)(LPCWSTR, LPCWSTR);
Однако, когда я изменил объявление, которое включает WINAPI, как было предложено ранее в потоке, проблема исчезла.
Код без какой-либо ошибки выглядит следующим образом:
typedef long (WINAPI *AU3_RunFn)(LPCWSTR, LPCWSTR);
AU3_RunFn _AU3_RunFn;
HINSTANCE hInstLibrary = LoadLibrary("AutoItX3.dll");
if (hInstLibrary)
{
_AU3_RunFn = (AU3_RunFn)GetProcAddress(hInstLibrary, "AU3_WinActivate");
if (_AU3_RunFn)
_AU3_RunFn(L"Untitled - Notepad",L"");
FreeLibrary(hInstLibrary);
}
Ответ 6
Я получал эту ошибку, вызывая функцию в DLL, которая была скомпилирована с версией Visual С++ до 2005 года из новой версии VC (2008).
Функция имела такую подпись:
LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );
Проблема заключалась в том, что размер time_t
составляет 32 бита в версии до 2005 года, но 64 бит с VS2005 (определяется как _time64_t
). Вызов функции ожидает 32-битную переменную, но получает 64-битную переменную при вызове из VC >= 2005. Поскольку параметры функций передаются через стек при использовании соглашения о вызове WINAPI
, это развращает стек и генерирует вышеупомянутые сообщение об ошибке ( "Ошибка проверки времени выполнения # 0..." ).
Чтобы исправить это, можно
#define _USE_32BIT_TIME_T
перед включением файла заголовка DLL или - лучше - изменить подпись функции в файле заголовка в зависимости от версии VS (версии до 2005 года не знают _time32_t
!):
#if _MSC_VER >= 1400
LONG WINAPI myFunc( _time32_t, SYSTEMTIME*, BOOL* );
#else
LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );
#endif
Обратите внимание, что вам нужно использовать _time32_t
вместо time_t
в вызывающей программе, конечно.
Ответ 7
Создаете ли вы статические библиотеки или библиотеки DLL? Если DLL, как определяется экспорт; как создаются библиотеки импорта?
Являются ли прототипы для функций в libs точно такими же, как объявления функций, где определены функции?
Ответ 8
Есть ли у вас какие-либо прототипы функции typedef'd (например, int (* fn) (int a, int b))
Если вы доминируете, возможно, вы ошиблись в прототипе.
ESP - это ошибка при вызове функции (можете ли вы указать, какой из них в отладчике?), который имеет несоответствие в параметрах, то есть стек восстановился до состояния, в котором он был запущен при вызове функции.
Вы также можете получить это, если вы загружаете функции С++, которые должны быть объявлены extern. C - C использует cdecl, С++ использует стандартное условное соглашение stdcall по умолчанию (IIRC). Поместите некоторые внешние оболочки C вокруг импортированных прототипов функций, и вы можете исправить их.
Если вы можете запустить его в отладчике, вы увидите функцию незамедлительно. Если нет, вы можете установить DrWtsn32 для создания minidump, который вы можете загрузить в windbg, чтобы увидеть столбец во время ошибки (вам понадобятся символы или файл карты, чтобы видеть имена функций).
Ответ 9
Другим случаем, когда esp
может перепутаться, является переполнение непреднамеренного буфера, обычно через ошибочное использование указателей для работы за границей массива. Скажем, у вас есть функция C, которая выглядит как
int a, b[2];
Запись в b[3]
, вероятно, изменит a
и где-нибудь в прошлом, что, вероятно, будет вставлять сохраненный esp
в стек.
Ответ 10
Вы получите эту ошибку, если функция вызывается с помощью соглашения о вызове, отличного от того, с которым он скомпилирован.
Visual Studio использует настройку условного соглашения по умолчанию, которая декалируется в параметрах проекта. Проверьте, одинаково ли это значение в настройках проекта orignal и в новых библиотеках. Более амбициозный разработчик мог бы установить это в _stdcall/pascal в оригинале, так как он уменьшает размер кода по сравнению с Cdecl по умолчанию. Таким образом, базовый процесс будет использовать этот параметр, а новые библиотеки получат cdecl по умолчанию, который вызывает проблему.
Поскольку вы сказали, что не используете специальные соглашения о вызовах, это кажется хорошей вероятностью.
Также сделайте разницу в заголовках, чтобы увидеть, являются ли декларации/файлы, которые видит процесс, теми же, что и библиотеки, скомпилированы с помощью.
ps: Устранение предупреждения - BAAAD. основная ошибка сохраняется.
Ответ 11
Это произошло со мной при доступе к COM-объекту (Visual Studio 2010). Я передал GUID для другого интерфейса A в моем вызове QueryInterface, но затем я передал извлеченный указатель как интерфейс B. Это привело к вызову функции для одного с полной сигнатурой, которая учитывает стек (и ESP), который испорченный.
Передача GUID для интерфейса B устраняет проблему.
Ответ 12
У меня была такая же ошибка после перемещения функций в dll и динамической загрузки DLL с помощью LoadLibrary и GetProcAddress. Я признал extern "C" для функции в dll из-за украшения. Так что изменилось соглашение о вызове на __cdecl. Я указывал, что указатели функций являются __stdcall в коде загрузки. Как только я изменил указатель функции от __stdcall to__cdecl в коде загрузки, ошибка времени выполнения исчезла.
Ответ 13
ESP - указатель стека. Поэтому, согласно компилятору, ваш указатель стека становится испорченным. Трудно сказать, как (или если) это могло произойти, не видя какого-либо кода.
Каков самый маленький сегмент кода, который вы можете воспроизвести для этого?
Ответ 14
Если вы используете какие-либо функции обратного вызова в Windows API, они должны быть объявлены с помощью CALLBACK
и/или WINAPI
. Это применит соответствующие декорации, чтобы компилятор сгенерировал код, который правильно очищает стек. Например, в компиляторе Microsoft он добавляет __stdcall
.
Windows всегда использовала соглашение __stdcall
, поскольку она приводит к (слегка) меньшему коду, причем очистка происходит в вызываемой функции, а не на каждом сайте вызова. Однако он несовместим с функциями varargs (потому что только вызывающий абонент знает, сколько аргументов они нажали).
Ответ 15
Здесь урезанная программа на С++, которая производит эту ошибку. Скомпилированная с использованием (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 не было должным образом сохранено в вызове функции. Обычно это результат вызова функции, объявленной с одним вызовом, с указателем функции, объявленным с другим соглашением о вызове".
Ответ 16
У меня была такая же проблема здесь, на работе. Я обновлял очень старый код, который вызывал указатель на функцию FARPROC. Если вы не знаете, FARPROC - это указатели на функции с безопасностью типа ZERO. Это C-эквивалент указателя функции typdef'd, без проверки типа компилятора.
Например, скажем, у вас есть функция, которая принимает 3 параметра. Вы указываете на него FARPROC, а затем вызываете его с 4 параметрами вместо 3. Дополнительный параметр выталкивает лишний мусор в стек, а когда он всплывает, ESP теперь отличается от того, когда он запускался. Поэтому я решил это, удалив дополнительный параметр для вызова вызова функции FARPROC.
Ответ 17
В моем приложении MFC С++ я испытываю ту же проблему, что и в Weird MSC 8.0: "Значение ESP не было должным образом сохранено в вызове функции..." . Сообщение имеет более 42K просмотров и 16 ответов/комментариев, ни одна из которых не обвинила компилятор в качестве проблемы. По крайней мере, в моем случае я могу показать, что компилятор VS2015 неисправен.
Моя установка dev и test следующая: у меня есть 3 компьютера, все из которых запускают Win10 версии 10.0.10586. Все компилируются с VS2015, но вот разница. Два из VS2015 имеют обновление 2, а второе - обновление 3. ПК с обновлением 3 работает, а два других с обновлением 2 с той же ошибкой, о которых сообщалось в проводке выше. Мой код приложения MFC С++ точно такой же на всех трех компьютерах.
Заключение: по крайней мере, в моем случае для моего приложения версия компилятора (обновление 2) содержала ошибку, которая нарушила мой код. Мое приложение сильно использует std:: packaged_task, поэтому я ожидаю, что проблема была в этом довольно новом коде компилятора.
Ответ 18
Не лучший ответ, но я просто перекомпилировал свой код с нуля (перестроил в VS), а затем проблема исчезла.