在调试某些崩溃时,我遇到了一些代码,简化了以下情况:
#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
实例,或在堆上分配它不会产生运行时检查错误。然后我继续查看生成的汇编代码。出于说明的目的,我将参考&#34;失败的版本&#34;作为从上面提供的代码中获得的那个,而我已经生成了一个&#34;工作版本&#34;只需评论static
行即可。生成的汇编代码的并排差异视图如下所示,&#34;失败的版本&#34;在左边,&#34;工作版本&#34;在右边:
首先我注意到#pragma intrinsic (sqrt)
电话负责&#34;运行时检查失败#2&#34;错误和检查,特别是当魔术cookie _RTC_CheckStackVars
在堆栈上的0xCCCCCCCC
对象周围仍然完好无损时(恰好是相对于原始值{{1}的-20字节的偏移量开始}})。在下面的屏幕截图中,我突出显示了绿色的对象位置和红色的魔术cookie位置。在&#34;工作版本中的功能开始时#34;这就是它的样子:
然后在调用obj
之前:
现在在&#34;失败的版本&#34;中,序言包括一个额外的(第3415行)
ESP
基本上使_RTC_CheckStackVars
在8字节边界上对齐。具体来说,每当调用函数时,初始值and esp,0FFFFFFF8h
以obj
或ESP
半字节结束,0
将以-24字节的偏移量开始存储相对于8
的初始值。
问题是,obj
仍会在相对于原始ESP
值的相同位置查找那些_RTC_CheckStackVars
魔术Cookie,如同工作版本&#34;如上所述(即-24和-12字节的偏移)。在这种情况下,0xCCCCCCCC
的前4个字节实际上与魔术cookie位置之一重叠。这将在下面的屏幕截图中显示在&#34;失败的版本&#34;:
然后在调用ESP
之前:
我们可以注意到,对应于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;新项目模板。
答案 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编译器选项)和使用一元减运算符,摆脱这些条件中的任何一个(或更多)应解决问题。
因此,似乎可用的选项是:
#pragma runtime_check
围绕受影响的函数来禁用运行时检查,如下例所示: #pragma runtime_check ("s", off)
void function()
{
MyClass obj;
obj.x() = -sqrt(2.0);
}
#pragma runtime_check ("s", restore)
#pragma intrinsics (sqrt)
行并添加#pragma function (sqrt)
来停用内在函数(有关详细信息,请参阅msdn)。
#pragma intrinsics
指令)。 0-sqrt(2.0)
,-1*sqrt(2.0)
(删除一元减号运算符)等变通方法调整代码,试图欺骗编译器使用不同的代码生成路径。请注意,这很可能会因看似微小的代码更改而中断。