Странная ошибка MSC 8.0: «Значение ESP не было должным образом сохранено при вызове функции ...»

Недавно мы попытались разбить некоторые из наших проектов 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)» на «По умолчанию», и время выполнения исчезло, оставив программу, очевидно, работающей правильно. Я этому совершенно не верю. Было ли это правильным решением или опасным взломом?

Будьте счастливы, что среда выполнения поймала это за вас. Если этого не произойдет, следующее, что сделает компьютер, - это уничтожит содержимое стека и выйдет из строя ужасным образом. (Повреждение стека отладки не для слабонервных.)

Donal Fellows 26.07.2010 12:39

ПОВТОРИТЕ свое обновление: Нет, это неправильное решение. Все, что вы сделали, это отключили проверки. Это все равно, что зарыться головой в песок. Проблема все еще существует и, несомненно, взорвется вам в лицо позже, когда ее будет еще труднее отследить.

Cody Gray 30.12.2016 16:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
51
2
67 822
19

Ответы 19

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 хорошее объяснение, было бы идеально, если бы вы поместили несколько примеров кода, чтобы помочь ему

jyz 23.07.2013 07:37

У меня было такое же исключение, но ни один из вышеперечисленных случаев не подходил. Я боролся с этим несколько часов, пока наконец не сузил проблему до функции, аргумент которой является указателем на функцию-член другого класса. Вызов этой функции вызвал повреждение стека. Решение этой проблемы можно найти здесь: stackoverflow.com/questions/8676879/…

Sushi271 31.12.2014 04:36

возможность 3 - несовпадающие имена при получении указателя функции (возможно, через вызов getProcAddress ("theWrongFuntionName"). Это то, что я сделал! Что произошло: я привязал указатель на названную функцию к прототипу указателя функции (через typedef Все выглядит правильно - ошибок компиляции нет, но вы вызываете неправильную функцию во время выполнения. Я думаю, вам должно не повезти, чтобы неправильно ввести имя, которое действительно существует в вашей dll, но не то, что вам нужно, иначе вы будете спасены и получите null от getProcAddress ().

Freya301 25.11.2015 23:11

Я читал это на другом форуме

У меня была такая же проблема, но я ее только ИСПРАВИЛ. Я получал ту же ошибку из следующего кода:

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" - That's not a keyword. It's a preprocessor symbol, that expands to the calling convention. A question on mismatched calling conventions should at least contain the term "соглашение о вызовах".
IInspectable 18.06.2016 14:44

это была именно та проблема, с которой я только что столкнулся с составным типом или каким бы то ни было его фактическое имя. Я не знал, где разместить WINAPI, поэтому просто оставил его при явной загрузке dll, чтобы получить D3D12GetDebugInterface(). Я возился с аргументами, но все было именно так, как вы сказали с winapi.

marshal craft 10.11.2016 21:59

Вот урезанная программа на 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). Есть множество способов запустить эту проверку во время выполнения. Публикация случайного ошибочного кода, который (иногда) вообще не помогает, извините.

IInspectable 22.09.2016 12:31

Я получал аналогичную ошибку для 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 22.09.2016 12:26

@IInspectable Утверждение, что уважаемый поставщик компилятора сегодня изменит свой код, чтобы исправить некорректное поведение пользователя компилятора, не имеет смысла. С другой стороны, если вы найдете недостаток или слабость в моем естественном эксперименте с тремя компьютерами, я хотел бы знать.

rtischer8277 23.09.2016 16:55
"Если вы можете найти недостаток или слабость в моем естественном эксперименте с тремя компьютерами, я хотел бы знать" - Uhm... easy. Undefined behavior, in ваш code, that happens to manifest itself in a reproducible way, with reproducible observable behavior. That would be один obvious explanation, if you don't buy the notion of a compiler vendor changing their support libraries, to address common bugs. None of that is very helpful, though, if we cannot see your минимальный воспроизводимый пример, that demonstrates the issue. Something like это would do.
IInspectable 23.09.2016 17:08

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

Стоит отметить, что это также может быть ошибкой Visual Studio.

У меня эта проблема возникла на VS2017, Win10 x64. Сначала это имело смысл, так как я делал странные вещи, приводя это к производному типу и заключая его в лямбду. Однако я вернул код к предыдущей фиксации и все равно получил ошибку, хотя раньше ее не было.

Я попытался перезапустить, а затем перестроить проект, после чего ошибка исчезла.

Другие вопросы по теме