Недавно мы попытались разбить некоторые из наших проектов Visual Studio на библиотеки, и казалось, что все отлично компилируется и строится в тестовом проекте с одним из проектов библиотеки в качестве зависимости. Однако попытка запустить приложение дала нам следующее неприятное сообщение об ошибке во время выполнения:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function pointer declared with a different calling convention.
Мы даже не указали соглашения о вызовах (__cdecl и т. д.) Для наших функций, оставив все переключатели компилятора по умолчанию. Я проверил, и настройки проекта согласованы для соглашения о вызовах в библиотеке и тестовых проектах.
Обновление: один из наших разработчиков изменил настройку проекта «Основные проверки времени выполнения» с «Оба (/ RTC1, эквивалент на / RTCsu)» на «По умолчанию», и время выполнения исчезло, оставив программу, очевидно, работающей правильно. Я этому совершенно не верю. Было ли это правильным решением или опасным взломом?
ПОВТОРИТЕ свое обновление: Нет, это неправильное решение. Все, что вы сделали, это отключили проверки. Это все равно, что зарыться головой в песок. Проблема все еще существует и, несомненно, взорвется вам в лицо позже, когда ее будет еще труднее отследить.





ESP - указатель стека. Итак, согласно компилятору, ваш указатель стека испорчен. Трудно сказать, как (или если) это могло произойти, не видя кода.
Какой наименьший сегмент кода вы можете получить, чтобы воспроизвести это?
Отключение чека - неправильное решение. Вы должны выяснить, что не так с вашими соглашениями о вызовах.
Есть довольно много способов изменить передачу вызова функции без ее явного указания. extern "C" сделает это, STDMETHODIMP / IFACEMETHODIMP также сделает это, другие макросы тоже могут это сделать.
Я считаю, что если запустить вашу программу под WinDBG (http://www.microsoft.com/whdc/devtools/debugging/default.mspx), среда выполнения должна прерваться в точке, где вы столкнетесь с этой проблемой. Вы можете посмотреть стек вызовов и выяснить, в какой функции возникла проблема, а затем посмотреть ее определение и объявление, которое использует вызывающий.
Вы создаете статические библиотеки или библиотеки DLL? Если библиотеки DLL, как определяется экспорт; как создаются библиотеки импорта?
Являются ли прототипы функций в библиотеках точно такими же, как объявления функций, в которых они определены?
есть ли у вас какие-либо прототипы функций typedef (например, int (* fn) (int a, int b))
если вы думаете, вы могли ошибиться с прототипом.
ESP - это ошибка при вызове функции (вы можете сказать, какая из них в отладчике?), Которая имеет несоответствие в параметрах, то есть стек вернулся в то состояние, в котором он был запущен, когда вы вызывали функцию.
Вы также можете получить это, если загружаете функции C++, которые необходимо объявить extern. C - C использует cdecl, C++ по умолчанию использует соглашение о вызовах stdcall (IIRC). Поместите некоторые внешние оболочки C вокруг импортированных прототипов функций, и вы можете это исправить.
Если вы можете запустить его в отладчике, вы сразу увидите функцию. Если нет, вы можете настроить DrWtsn32 для создания минидампа, который вы можете загрузить в windbg, чтобы увидеть стек вызовов во время ошибки (хотя вам понадобятся символы или файл карты, чтобы увидеть имена функций).
Другой случай, когда esp может испортиться, - это непреднамеренное переполнение буфера, обычно из-за ошибочного использования указателей для работы за границей массива. Скажем, у вас есть функция C, которая выглядит как
int a, b[2];
Запись в b[3], вероятно, изменит a, и в любом другом случае, вероятно, сохраненный esp попадет в стек.
Если вы используете какие-либо функции обратного вызова с Windows API, они должны быть объявлены с использованием CALLBACK и / или WINAPI. Это применит соответствующие декорации, чтобы компилятор сгенерировал код, который правильно очищает стек. Например, в компиляторе Microsoft он добавляет __stdcall.
Windows всегда использовала соглашение __stdcall, поскольку оно приводит к (немного) меньшему размеру кода, при этом очистка происходит в вызываемой функции, а не на каждом сайте вызова. Однако он несовместим с функциями varargs (потому что только вызывающий знает, сколько аргументов они отправили).
Вы получите эту ошибку, если функция вызывается с соглашением о вызовах, отличным от того, для которого она скомпилирована.
Visual Studio использует настройку соглашения о вызовах по умолчанию, которая помечена в параметрах проекта. Проверьте, совпадает ли это значение в исходных настройках проекта и в новых библиотеках. Чрезмерно амбициозный разработчик мог бы установить для него значение _stdcall / pascal в оригинале, поскольку он уменьшает размер кода по сравнению с cdecl по умолчанию. Таким образом, базовый процесс будет использовать этот параметр, а новые библиотеки получат cdecl по умолчанию, что вызывает проблему.
Поскольку вы сказали, что не используете никаких специальных соглашений о вызовах, это кажется хорошей вероятностью.
Также выполните сравнение заголовков, чтобы увидеть, совпадают ли объявления / файлы, которые видит процесс, с теми, с которыми скомпилированы библиотеки.
ps: Убрать предупреждение - это BAAAD. основная ошибка все еще сохраняется.
Я видел эту ошибку, когда код пытался вызвать функцию для объекта, не относящегося к ожидаемому типу.
Итак, иерархия классов: Родитель с детьми: Ребенок1 и Ребенок2
Child1* pMyChild = 0;
...
pMyChild = pSomeClass->GetTheObj();// This call actually returned a Child2 object
pMyChild->SomeFunction(); // "...value of ESP..." error occurs here
Эта ошибка отладки означает, что регистр указателя стека не возвращается к своему исходному значению после вызова функции, т.е. что за числом толкает до вызова функции не следовало такое же количество хлопает после вызова.
Я знаю 2 причины этого (обе с динамически загружаемыми библиотеками). №1 - это то, что VC++ описывает в сообщении об ошибке, но я не думаю, что это наиболее частая причина ошибки (см. №2).
1) Несоответствующие соглашения о вызовах:
У вызывающего и вызываемого нет надлежащего соглашения о том, кто и что будет делать. Например, если вы вызываете функцию DLL, которая называется _stdcall, но по какой-то причине она была объявлена в вашем вызове как _cdecl (по умолчанию в VC++). Это могло бы случиться часто, если вы используете разные языки в разных модулях и т. д.
Вам нужно будет проверить объявление функции-нарушителя и убедиться, что она не объявляется дважды и по-разному.
2) Несоответствующие типы:
Вызывающий и вызываемый объект не скомпилированы с одинаковыми типами. Например, общий заголовок определяет типы в API и недавно был изменен, и один модуль был перекомпилирован, а другой - нет, т.е. некоторые типы могут иметь разный размер у вызывающего и вызываемого абонентов.
В этом случае вызывающий передает аргументы одного размера, но вызываемый объект (если вы используете _stdcall, где вызываемый объект очищает стек) выдвигает другой размер. Таким образом, ESP не возвращается к правильному значению.
(Конечно, эти и другие аргументы ниже них могут показаться искаженными в вызываемой функции, но иногда вы можете пережить это без видимого сбоя.)
Если у вас есть доступ ко всему коду, просто перекомпилируйте его.
+1 хорошее объяснение, было бы идеально, если бы вы поместили несколько примеров кода, чтобы помочь ему
У меня было такое же исключение, но ни один из вышеперечисленных случаев не подходил. Я боролся с этим несколько часов, пока наконец не сузил проблему до функции, аргумент которой является указателем на функцию-член другого класса. Вызов этой функции вызвал повреждение стека. Решение этой проблемы можно найти здесь: stackoverflow.com/questions/8676879/…
возможность 3 - несовпадающие имена при получении указателя функции (возможно, через вызов getProcAddress ("theWrongFuntionName"). Это то, что я сделал! Что произошло: я привязал указатель на названную функцию к прототипу указателя функции (через typedef Все выглядит правильно - ошибок компиляции нет, но вы вызываете неправильную функцию во время выполнения. Я думаю, вам должно не повезти, чтобы неправильно ввести имя, которое действительно существует в вашей dll, но не то, что вам нужно, иначе вы будете спасены и получите null от getProcAddress ().
Я читал это на другом форуме
У меня была такая же проблема, но я ее только ИСПРАВИЛ. Я получал ту же ошибку из следующего кода:
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).
Надеюсь, что это поможет любым будущим людям! :-)
Ваше здоровье.
это была именно та проблема, с которой я только что столкнулся с составным типом или каким бы то ни было его фактическое имя. Я не знал, где разместить WINAPI, поэтому просто оставил его при явной загрузке dll, чтобы получить D3D12GetDebugInterface(). Я возился с аргументами, но все было именно так, как вы сказали с winapi.
Вот урезанная программа на 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 не было должным образом сохранено при вызове функции. Обычно это результат вызова функции, объявленной с одним соглашением о вызовах, с указателем функции, объявленным с другим соглашением о вызовах».
Этот код демонстрирует неопределенное поведение. Есть как минимум 3 фатальных ошибки: 1 Доступ к неинициализированному массиву (p). 2 записывает за конец массива (strcat). 3 Возвращает адрес локального (return p). Есть множество способов запустить эту проверку во время выполнения. Публикация случайного ошибочного кода, который (иногда) вообще не помогает, извините.
Я получал аналогичную ошибку для API AutoIt, которые я вызывал из программы VC++.
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);
}
Это случилось со мной при доступе к COM-объекту (Visual Studio 2010). Я передал GUID для другого интерфейса A for в моем вызове QueryInterface, но затем я привел полученный указатель как интерфейс B. Это привело к вызову функции с полностью подписью, которая учитывает, что стек (и ESP) является облажался.
Передача GUID для интерфейса B устранила проблему.
Я получал эту ошибку при вызове функции в DLL, которая была скомпилирована с версией Visual C++ до 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 в вызывающей программе.
У меня была такая же проблема здесь, на работе. Я обновлял очень старый код, который вызывал указатель на функцию FARPROC. Если вы не знаете, FARPROC - это указатели на функции с безопасностью типа ZERO. Это эквивалент указателя на функцию в языке C без проверки типа компилятором. Так, например, скажем, у вас есть функция, которая принимает 3 параметра. Вы указываете на него FARPROC, а затем вызываете его с 4 параметрами вместо 3. Дополнительный параметр помещает лишний мусор в стек, и когда он выскакивает, ESP теперь отличается от того, когда он был запущен. Я решил это, удалив лишний параметр для вызова функции FARPROC.
У меня была такая же ошибка после перемещения функций в dll и динамической загрузки dll с помощью LoadLibrary и GetProcAddress. Я объявил extern "C" для функции в dll из-за украшения. Это также изменило соглашение о вызовах на __cdecl. Я объявлял указатели на функции как __stdcall в коде загрузки. Как только я изменил указатель функции с __stdcall на __cdecl в коде загрузки, ошибка времени выполнения исчезла.
В моем приложении MFC C++ я столкнулся с той же проблемой, что и в Странная ошибка MSC 8.0: «Значение ESP не было должным образом сохранено при вызове функции…». Публикация имеет более 42 тысяч просмотров и 16 ответов / комментариев, ни один из которых не назвал компилятор проблемой. По крайней мере, в моем случае я могу показать, что компилятор VS2015 виноват.
Моя установка для разработки и тестирования следующая: у меня есть 3 ПК, на каждом из которых установлена Win10 версии 10.0.10586. Все компилируются с VS2015, но вот разница. У двух VS2015 установлено обновление 2, а к другому применено обновление 3. ПК с обновлением 3 работает, но два других с обновлением 2 выходят из строя с той же ошибкой, что и в сообщении выше. Код моего приложения MFC C++ одинаков на всех трех компьютерах.
Вывод: по крайней мере, в моем случае для моего приложения версия компилятора (обновление 2) содержала ошибку, которая нарушила мой код. Мое приложение интенсивно использует std :: packaged_task, поэтому я полагаю, что проблема была в этом довольно новом коде компилятора.
Поспешные выводы, а? Вам приходило в голову, что, может быть, просто возможно, есть ошибка в коде ваш, которая достаточно распространена для обновления библиотеки, чтобы реализовать исправление? Без минимальный воспроизводимый пример и тщательного анализа сгенерированного объектного кода это всего лишь предположение.
@IInspectable Утверждение, что уважаемый поставщик компилятора сегодня изменит свой код, чтобы исправить некорректное поведение пользователя компилятора, не имеет смысла. С другой стороны, если вы найдете недостаток или слабость в моем естественном эксперименте с тремя компьютерами, я хотел бы знать.
Не лучший ответ, но я просто перекомпилировал свой код с нуля (перестроил в VS), а затем проблема исчезла.
Стоит отметить, что это также может быть ошибкой Visual Studio.
У меня эта проблема возникла на VS2017, Win10 x64. Сначала это имело смысл, так как я делал странные вещи, приводя это к производному типу и заключая его в лямбду. Однако я вернул код к предыдущей фиксации и все равно получил ошибку, хотя раньше ее не было.
Я попытался перезапустить, а затем перестроить проект, после чего ошибка исчезла.
Будьте счастливы, что среда выполнения поймала это за вас. Если этого не произойдет, следующее, что сделает компьютер, - это уничтожит содержимое стека и выйдет из строя ужасным образом. (Повреждение стека отладки не для слабонервных.)