在VS2012中使用sqrt内部堆栈运行时检查失败

时间:2015-01-16 15:33:51

标签: c++ visual-studio-2012

在调试某些崩溃时,我遇到了一些代码,简化了以下情况:

#include <cmath>
#pragma intrinsic (sqrt) 

class MyClass
{
public:
  MyClass() { m[0] = 0; }
  double& x() { return m[0]; }
private:
  double m[1];
};
void function()
{
  MyClass obj;
  obj.x() = -sqrt(2.0);
}

int main()
{
  function();
  return 0;
}

使用VS2012(Pro Version 11.0.61030.00 Update 4和Express for Windows Desktop版本11.0.61030.00 Update 4)在Debug | Win32中构建时,代码会在{{1}结束时触发运行时检查错误执行,显示为(以随机方式):

  

运行时检查失败#2 - 围绕变量&#39; obj&#39;已经腐败了。

  

Test.exe中发生缓冲区溢出,破坏了程序的内部状态。按Break打开调试程序或继续终止程序。

据我所知,这通常意味着堆栈上的对象存在某种缓冲区溢出/欠载。也许我忽略了一些东西,但我无法在这个C ++代码中看到任何可能发生这种缓冲区溢出的代码。在对代码进行各种调整并逐步完成函数的生成汇编代码后(参见&#34;详细信息&#34;以下部分),我很想说它看起来像Visual Studio中的一个错误2012年,但也许我只是在太深,缺少一些东西。

是否存在此代码不符合的内在函数使用要求或其他C ++标准要求,这可以解释这种行为?

如果没有,禁用函数内在是获得正确的运行时检查行为的唯一方法(除了下面提到的function之类的解决方法,这很容易丢失)?

详情

围绕代码,我已经注意到,当我通过注释掉0-sqrt行来禁用sqrt内在函数时,运行时检查错误就会消失。

否则使用#pragma内在编译指示(或/ Oi编译器选项):

  • 使用诸如sqrt之类的setter,也不会产生运行时检查错误。
  • obj.setx(double x) { m[0] = x; }obj.x() = -sqrt(2.0)取代obj.x() = +sqrt(2.0)令我意外,不会产生运行时检查错误。
  • 同样,将obj.x() = 0.0-sqrt(2.0)替换为obj.x() = -sqrt(2.0)不会产生运行时检查错误。
  • 使用obj.x() = -1.4142135623730951;(以及double m[1];次访问)替换成员double m;似乎只会生成&#34;运行时检查失败#2&#34;错误(即使使用m[0]),有时运行正常。
  • obj.x() = -sqrt(2.0)声明为obj实例,或在堆上分配它不会产生运行时检查错误。
  • 将编译器警告设置为级别4不会产生任何警告。
  • 使用VS2005 Pro或VS2010 Express编译相同的代码不会产生运行时检查错误。
  • 为了它的价值,我已经注意到Windows 7(带有Intel Xeon CPU)和Windows 8.1机器(带有Intel Core i7 CPU)的问题。

然后我继续查看生成的汇编代码。出于说明的目的,我将参考&#34;失败的版本&#34;作为从上面提供的代码中获得的那个,而我已经生成了一个&#34;工作版本&#34;只需评论static行即可。生成的汇编代码的并排差异视图如下所示,&#34;失败的版本&#34;在左边,&#34;工作版本&#34;在右边: Diff

首先我注意到#pragma intrinsic (sqrt)电话负责&#34;运行时检查失败#2&#34;错误和检查,特别是当魔术cookie _RTC_CheckStackVars在堆栈上的0xCCCCCCCC对象周围仍然完好无损时(恰好是相对于原始值{{1}的-20字节的偏移量开始}})。在下面的屏幕截图中,我突出显示了绿色的对象位置和红色的魔术cookie位置。在&#34;工作版本中的功能开始时#34;这就是它的样子:

RTC-ok-start-of-function

然后在调用obj之前:

RTC-ok-right-before-RTC_CheckStackVars

现在在&#34;失败的版本&#34;中,序言包括一个额外的(第3415行)

ESP

基本上使_RTC_CheckStackVars在8字节边界上对齐。具体来说,每当调用函数时,初始值and esp,0FFFFFFF8h objESP半字节结束,0将以-24字节的偏移量开始存储相对于8的初始值。 问题是,obj仍会在相对于原始ESP值的相同位置查找那些_RTC_CheckStackVars魔术Cookie,如同工作版本&#34;如上所述(即-24和-12字节的偏移)。在这种情况下,0xCCCCCCCC的前4个字节实际上与魔术cookie位置之一重叠。这将在下面的屏幕截图中显示在&#34;失败的版本&#34;:

RTC-fail-start-of-function

然后在调用ESP之前:

RTC-fail-right-before-RTC_CheckStackVars

我们可以注意到,对应于obj的实际数据在&#34;工作版本&#34;之间是相同的。 &#34;失败的版本&#34; (&#34; cd 3b 7f 66 9e a0 f6 bf&#34;,或当被解释为_RTC_CheckStackVars时预期值-1.4142135623730951。)

顺便提一下,只要obj.m[0]的初始值以double_RTC_CheckStackVars半字节结束,ESP检查实际就会通过{在此情况下4开始一个-20字节的偏移量,就像在&#34;工作版本&#34;)中一样。

C检查完成后(假设它通过),还会检查恢复的obj值是否与原始值相对应。该检查在失败时负责&#34; A缓冲区溢出发生在...&#34;消息。

在&#34;工作版本&#34;中,原始_RTC_CheckStackVars在序言的早期(第3415行)被复制到ESP,并且它的这个值用于通过使用ESP进行xoring来计算校验和(第3425行)。在&#34;失败版本&#34;中,校验和计算基于EBP(第3425行) ___security_cookie在推送​​一些寄存器时递减12(第3417-3419行,但是恢复的ESP的相应检查是在恢复这些寄存器的同一点完成的。

因此,简而言之,除非我没有做到这一点,否则它看起来就像#34;工作版本&#34;遵循有关堆栈处理的标准教科书和教程,而&#34;失败的版本&#34;弄乱了运行时检查。

P.S。:&#34; Debug build&#34;是指&#34; Debug&#34;的标准编译器选项集。来自&#34; Win32控制台应用程序的配置&#34;新项目模板。

1 个答案:

答案 0 :(得分:2)

正如Hans在评论中指出的那样,Visual Studio 2013无法再重现该问题。 同样,Microsoft connect bug report上的官方答案是:

  

我们无法使用VS2013 Update 4 RTM重现它。产品团队本身不再直接接受Microsoft Visual Studio 2012和早期产品的反馈。您可以通过访问以下链接中的一个资源来获得对Visual Studio 2012及更早版本问题的支持:   http://www.visualstudio.com/support/support-overview-vs

因此,假设仅在具有函数内在函数(/ Oi编译器选项)的VS2012上触发问题,运行时检查(/ RTC或/ RTC1编译器选项)使用一元减运算符,摆脱这些条件中的任何一个(或更多)应解决问题。

因此,似乎可用的选项是:

  1. 升级到最新的Visual Studio(如果您的项目允许)
  2. 通过使用#pragma runtime_check围绕受影响的函数来禁用运行时检查,如下例所示:
  3.     #pragma runtime_check ("s", off)
        void function()
        {
          MyClass obj;
          obj.x() = -sqrt(2.0);
        }
        #pragma runtime_check ("s", restore)
    
    1. 通过删除#pragma intrinsics (sqrt)行并添加#pragma function (sqrt)来停用内在函数(有关详细信息,请参阅msdn)。
      如果通过&#34;启用内部函数&#34;为所有文件激活了内在函数。在项目属性(/ Oi编译器选项)中,您需要停用该项目属性。然后,您可以针对特定函数逐个启用内在函数,同时检查它们是否不受错误的影响(对于每个必需的内部函数都使用#pragma intrinsics指令)。
    2. 使用0-sqrt(2.0)-1*sqrt(2.0)(删除一元减号运算符)等变通方法调整代码,试图欺骗编译器使用不同的代码生成路径。请注意,这很可能会因看似微小的代码更改而中断。