奇怪的 MSC 8.0 错误:“ESP 的值没有在函数调用中正确保存......”

Posted

技术标签:

【中文标题】奇怪的 MSC 8.0 错误:“ESP 的值没有在函数调用中正确保存......”【英文标题】:Weird MSC 8.0 error: "The value of ESP was not properly saved across a function call..." 【发布时间】:2010-09-13 15:31:32 【问题描述】:

我们最近尝试将我们的一些 Visual Studio 项目拆分为库,并且在一个测试项目中似乎一切都可以正常编译和构建,其中一个库项目作为依赖项。但是,尝试运行该应用程序给我们带来了以下令人讨厌的运行时错误消息:

运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是调用使用不同调用约定声明的函数指针的结果。

我们甚至从未为我们的函数指定调用约定(__cdecl 等),将所有编译器开关保留为默认值。我检查了项目设置对于跨库和测试项目的调用约定是一致的。

更新:我们的一位开发人员将“基本运行时检查”项目设置从“两者(/RTC1,等效于 /RTCsu)”更改为“默认”,运行时消失,程序运行明显正确。我完全不相信这一点。这是一个适当的解决方案,还是一个危险的黑客攻击?

【问题讨论】:

非常高兴运行时为您捕捉到了这一点。如果没有,计算机接下来要做的就是粉碎堆栈内容并以可怕的方式崩溃。 (调试堆栈损坏不适合胆小的人。) RE 您的更新:不,这不是一个合适的解决方案。您所做的只是禁用检查。这就像把头埋在沙子里一样。问题仍然存在,而且毫无疑问会在以后出现在你面前,届时将更难追查。 【参考方案1】:

ESP 是堆栈指针。因此,根据编译器的说法,您的堆栈指针变得混乱了。如果没有看到一些代码,很难说这是如何(或是否)发生的。

重现此代码的最小代码段是多少?

【讨论】:

【参考方案2】:

使检查静音不是正确的解决方案。你必须弄清楚你的调用约定出了什么问题。

有很多方法可以在不明确指定的情况下更改函数的调用对流。 extern "C" 会这样做,STDMETHODIMP/IFACEMETHODIMP 也会这样做,其他宏也可能会这样做。

我相信如果在 WinDBG (http://www.microsoft.com/whdc/devtools/debugging/default.mspx) 下运行您的程序,运行时应该会在您遇到该问题时中断。您可以查看调用堆栈并找出哪个函数有问题,然后查看其定义和调用者使用的声明。

【讨论】:

【参考方案3】:

您是在创建静态库还是 DLL?如果是 DLL,如何定义导出;导入库是如何创建的?

库中函数的原型完全是否与定义函数的函数声明相同?

【讨论】:

【参考方案4】:

你有任何类型定义的函数原型(例如 int (*fn)(int a, int b) )

如果你这样做,你可能弄错了原型。

ESP 是调用函数时的错误(你能分辨出调试器中的哪个函数吗?),它的参数不匹配 - 即堆栈已恢复到调用函数时开始的状态。

如果您正在加载需要声明的 C++ 函数 extern C - C 使用 cdecl,C++ 默认使用 stdcall 调用约定 (IIRC),您也可以得到这个。在导入的函数原型周围放置一些 extern C 包装器,您可以修复它。

如果您可以在调试器中运行它,您将立即看到该函数。如果没有,您可以将 DrWtsn32 设置为创建一个小型转储,您可以将其加载到 windbg 以查看错误发生时的调用堆栈(不过,您需要符号或映射文件才能查看函数名称)。

【讨论】:

【参考方案5】:

esp 可能会搞砸的另一种情况是无意的缓冲区溢出,通常是由于错误地使用指针超出了数组的边界。假设你有一些 C 函数,看起来像

int a, b[2];

写入b[3] 可能会更改a,并且任何超过此时间的地方都可能会将已保存的esp 写入堆栈。

【讨论】:

【参考方案6】:

如果您在 Windows API 中使用任何回调函数,则必须使用 CALLBACK 和/或 WINAPI 声明它们。这将应用适当的修饰以使编译器生成正确清理堆栈的代码。例如,在微软的编译器上,它添加了__stdcall

Windows 一直使用__stdcall 约定,因为它导致(稍微)更小的代码,清理发生在被调用的函数中,而不是在每个调用站点。但它与可变参数函数不兼容(因为只有调用者知道他们推送了多少参数)。

【讨论】:

【参考方案7】:

如果函数被调用的调用约定不是它所编译的约定,你会得到这个错误。

Visual Studio 使用项目选项中标明的默认调用约定设置。检查此值在原始项目设置和新库中是否相同。过于雄心勃勃的开发人员可能会在原始版本中将其设置为 _stdcall/pascal,因为与默认的 cdecl 相比,它减少了代码大小。因此,基本进程将使用此设置,并且新库会获得导致问题的默认 cdecl

既然你说你不使用任何特殊的调用约定,这似乎是一个很好的可能性。

还要对标头进行比较,以查看进程看到的声明/文件是否与编译库时使用的相同。

ps:让警告消失是 BAAAD。潜在的错误仍然存​​在。

【讨论】:

【参考方案8】:

当代码尝试在不属于预期类型的​​对象上调用函数时,我看到了这个错误。

所以,类层次结构:父与子:Child1 和 Child2

Child1* pMyChild = 0;
...
pMyChild = pSomeClass->GetTheObj();// This call actually returned a Child2 object
pMyChild->SomeFunction();          // "...value of ESP..." error occurs here

【讨论】:

【参考方案9】:

这个调试错误的意思是函数调用后栈指针寄存器没有返回到原来的值,即函数调用前push的次数后面跟在通话后弹出。

据我所知,有两个原因(都是动态加载的库)。 #1 是 VC++ 在错误消息中描述的内容,但我认为这不是最常见的错误原因(参见 #2)。

1) 调用约定不匹配:

调用者和被调用者对于谁将做什么没有适当的协议。例如,如果您正在调用一个名为_stdcall 的DLL 函数,但由于某种原因,您在调用中将其声明为_cdecl(VC++ 中的默认值)。如果您在不同的模块等中使用不同的语言,这种情况会发生很多。

您必须检查有问题的函数的声明,并确保它没有被声明两次,而且是不同的。

2) 类型不匹配:

调用者和被调用者的编译类型不同。例如,一个公共标头定义了 API 中的类型并且最近发生了变化,一个模块被重新编译,但另一个没有——即某些类型在调用者和被调用者中的大小可能不同。

在这种情况下,调用者推送一个大小的参数,但被调用者(如果您使用_stdcall ,被调用者清理堆栈)弹出不同大小。因此,ESP 没有返回到正确的值。

(当然,这些参数以及它们下面的其他参数在被调用函数中看起来会出现乱码,但有时您可以在没有明显崩溃的情况下幸存下来。)

如果您可以访问所有代码,只需重新编译即可。

【讨论】:

+1 很好的解释,如果你放一些代码示例来指导他就完美了 我有同样的例外,但以上都不是。我与它斗争了几个小时,直到我最终将问题缩小到一个函数,它的参数是指向其他类的成员函数的指针。调用此函数导致堆栈损坏。此类问题的解决方案可以在这里找到:***.com/questions/8676879/… 可能性 3 - 获取函数指针时名称不匹配(可能通过调用 getProcAddress("theWrongFuntionName")。这就是我所做的!发生了什么:我将指向命名函数的指针绑定到函数指针原型(通过 typedef)。一切看起来都是正确的 - 没有编译错误,但是你在运行时调用了错误的函数。我想你必须很不幸地错误输入了一个实际存在于你的 dll 中的名称,但不是'不是你想要的,否则你将被保存并从 getProcAddress() 返回 null。【参考方案10】:

我在其他论坛读到过这篇文章

我遇到了同样的问题,但我刚刚修复了它。我从以下代码中得到了同样的错误:

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 关键字” - 这不是关键字。它是一个预处理器符号,可扩展为调用约定。关于不匹配调用约定的问题至少应包含术语“调用约定” 这正是我刚刚遇到的复合类型或其实际名称的问题。我不知道将 WINAPI 放在哪里,所以在显式加载 dll 以获取 D3D12GetDebugInterface() 时将其忽略。我已经搞砸了争论,但正如你对 winapi 所说的那样。【参考方案11】:

这是一个产生该错误的精简 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)。有多种方法可以触发此运行时检查。发布随机错误代码(有时)根本没有帮助,抱歉。【参考方案12】:

我从 VC++ 程序调用的 AutoIt API 遇到了类似的错误。

    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);

【讨论】:

【参考方案13】:

这发生在我访问 COM 对象 (Visual Studio 2010) 时。我在对 QueryInterface 的调用中传递了另一个接口 A 的 GUID,但随后我将检索到的指针转换为接口 B。这导致对具有完全签名的接口进行函数调用,这说明了堆栈(和 ESP)是搞砸了。

为接口 B 传递 GUID 解决了问题。

【讨论】:

【参考方案14】:

我在调用 DLL 中的函数时遇到此错误,该 DLL 是使用 2005 年之前版本的 Visual C++ 从较新版本的 VC (2008) 编译的。 该函数具有以下签名:

LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );

问题是 time_t 的大小在 2005 之前的版本中是 32 位,但从 VS2005 开始是 64 位(定义为 _time64_t)。函数的调用需要一个 32 位变量,但从 VC >= 2005 调用时会得到一个 64 位变量。由于在使用 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

【讨论】:

【参考方案15】:

我在工作中遇到了同样的问题。我正在更新一些调用 FARPROC 函数指针的非常旧的代码。如果您不知道,FARPROC 是具有零类型安全性的函数指针。它是 typdef 函数指针的 C 等价物,没有编译器类型检查。 例如,假设您有一个带有 3 个参数的函数。您将 FARPROC 指向它,然后使用 4 个参数而不是 3 个参数调用它。额外的参数将额外的垃圾推入堆栈,当它弹出时,ESP 现在与启动时不同。所以我通过删除调用 FARPROC 函数调用的额外参数来解决它。

【讨论】:

【参考方案16】:

在将函数移动到 dll 并使用 LoadLibrary 和 GetProcAddress 动态加载 dll 后,我遇到了同样的错误。由于装饰,我已经为 dll 中的函数声明了 extern "C"。所以这也将调用约定更改为 __cdecl。我在加载代码中将函数指针声明为 __stdcall。一旦我在加载代码中将函数指针从 __stdcall 更改为 __cdecl,运行时错误就消失了。

【讨论】:

【参考方案17】:

在我的 MFC C++ 应用程序中,我遇到了与 Weird MSC 8.0 error: “The value of ESP was not properly saved across a function call…” 中报告的相同的问题。该帖子有超过 42K 的浏览量和 16 个答案/cmets,其中没有一个将问题归咎于编译器。至少在我的情况下,我可以证明 VS2015 编译器有问题。

我的开发和测试设置如下:我有 3 台 PC,它们都运行 Win10 版本 10.0.10586。都是用 VS2015 编译的,但这里有区别。其中两个 VS2015 具有更新 2,而另一个应用了更新 3。带有 Update 3 的 PC 可以工作,但是带有 Update 2 的另外两台 PC 失败,并出现与上述帖子中报告的错误相同的错误。我的 MFC C++ 应用程序代码在所有三台 PC 上完全相同。

结论:至少就我的应用而言,编译器版本(更新 2)包含破坏我的代码的错误。我的应用大量使用了 std::packaged_task,所以我预计问题出在那个相当新的编译器代码中。

【讨论】:

草率下结论,嗯?你有没有想过,也许,只是也许,你的代码中存在一个错误,这对于库更新来实施修复是很常见的?如果没有minimal reproducible example 和对生成的目标代码的彻底分析,这只是猜测。 @IInspectable 今天一个受人尊敬的编译器供应商会改变他们的代码来修复编译器用户行为不端的代码的想法是没有价值的。另一方面,如果你能在我的自然 3-PC 实验中找到缺陷或弱点,我想知道。 “如果你能在我的自然 3-PC 实验中找到缺陷或弱点,我想知道” - 嗯……很简单。 你的代码中的未定义行为,恰好以可重现的方式表现出来,具有可重现的可观察行为。如果您不接受编译器供应商更改其支持库以解决常见错误的想法,那将是一个明显的解释。但是,如果我们看不到您的minimal reproducible example,那么这些都不是很有帮助,这说明了这个问题。像this 这样的东西就可以了。【参考方案18】:

不是最佳答案,但我只是从头开始重新编译我的代码(在 VS 中重新构建),然后问题就消失了。

【讨论】:

【参考方案19】:

值得指出的是,这也可能是 Visual Studio 的错误。

我在 VS2017、Win10 x64 上遇到了这个问题。起初它是有道理的,因为我正在做一些奇怪的事情,将其转换为派生类型并将其包装在 lambda 中。但是,我将代码恢复为以前的提交,但仍然收到错误,即使它以前不存在。

我尝试重新启动然后重建项目,然后错误消失了。

【讨论】:

同意这张海报。当您从甚至没有处理的代码中遇到奇怪的意外错误时,请始终尝试完全重建您的项目。当您使用增量链接和 VS 的所有其他实用程序构建大型项目时,这种事情经常发生。有时它会弄乱链接,你会得到像这样的随机错误。

以上是关于奇怪的 MSC 8.0 错误:“ESP 的值没有在函数调用中正确保存......”的主要内容,如果未能解决你的问题,请参考以下文章

sql developer,奇怪的错误

CMake区分MSVC版本

Xcode 8.0 beta 3 在从通知和计时器访问功能时出现非常有趣的错误

MinGW中预处理器g ++的奇怪行为

错误 [org.jboss.msc.service.fail](MSC 服务线程 1-3)MSC000001:无法启动服务 jboss.deployment.unit。 .POST_MODULE

MS C++ 2010 和 mspdb100.dll