Sle*_*Eye 15 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;
}
Run Code Online (Sandbox Code Playgroud)
当使用VS2012(Pro Version 11.0.61030.00 Update 4和Express for Windows Desktop版本11.0.61030.00 Update 4)在Debug | Win32中内置时,代码会在结束时触发运行时检查错误. function
执行显示为(以随机方式):
运行时检查失败#2 - 变量'obj'周围的堆栈已损坏.
要么
Test.exe中发生缓冲区溢出,该程序已损坏程序的内部状态.按Break可调试程序或继续终止程序.
据我所知,这通常意味着堆栈上的对象存在某种缓冲区溢出/欠载.也许我忽略了一些东西,但是我无法在这个C++代码中看到任何可能发生这种缓冲区溢出的代码.在对代码进行各种调整并逐步执行函数生成的汇编代码之后(参见下面的"详细信息"部分),我很想说它看起来像Visual Studio 2012中的一个错误,但也许我是只是在太深,缺少一些东西.
是否有内部函数使用要求或此代码不符合的其他C++标准要求,这可以解释这种行为?
如果没有,禁用函数内在是获得正确的运行时检查行为的唯一方法(除了0-sqrt
下面提到的可能很容易丢失的解决方法)?
细节
围绕代码,我注意到当我sqrt
通过注释掉#pragma
行来禁用内在函数时,运行时检查错误消失了.
否则使用sqrt
内部编译指示(或/ Oi编译器选项):
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;
(以及m[0]
访问)替换成员只会产生"运行时检查失败#2"错误(即使有obj.x() = -sqrt(2.0)
),有时运行正常.obj
为static
实例或在堆上分配它不会生成运行时检查错误.然后我继续查看生成的汇编代码.出于说明的目的,我将把"失败的版本"称为从上面提供的代码中获得的版本,而我通过简单地注释该#pragma intrinsic (sqrt)
行来生成"工作版本" .生成的汇编代码的并排差异视图如下所示,左侧是"失败版本",右侧是"工作版本":
首先,我注意到该_RTC_CheckStackVars
调用负责"运行时检查失败#2"错误,并且特别是当魔术cookie 在堆栈上的对象0xCCCCCCCC
周围仍然完好无损时进行检查obj
(这恰好是从 - 的偏移开始 -相对于原始值的20个字节ESP
).在下面的屏幕截图中,我突出显示了绿色的对象位置和红色的魔术cookie位置.在"工作版"中的函数开始时,它是这样的:
然后在电话会议之前_RTC_CheckStackVars
:
现在在"失败的版本"中,序言包括一个额外的(第3415行)
and esp,0FFFFFFF8h
Run Code Online (Sandbox Code Playgroud)
它基本上obj
在8字节边界上对齐.具体来说,每当调用函数时,初始值ESP
以a 0
或8
半字节结束obj
,则从相对于初始值的-24字节的偏移量开始存储ESP
.问题是_RTC_CheckStackVars
仍然0xCCCCCCCC
在相对于原始ESP
值的相同位置寻找那些神奇的cookie,如上面描述的"工作版本"(即-24和-12字节的偏移).在这种情况下,obj
前4个字节实际上与魔术cookie位置之一重叠.这在"失败版本"开头的下面的屏幕截图中显示:
然后在电话会议之前_RTC_CheckStackVars
:
我们可以注意到,对应的实际数据obj.m[0]
在"工作版本"和"失败版本"之间是相同的("cd 3b 7f 66 9e a0 f6 bf",或者当解释为a时预期值-1.4142135623730951 double
) .
顺便说一下,_RTC_CheckStackVars
只要初始值ESP
以a 4
或C
半字节结束(在这种情况下obj
从-20字节偏移开始,就像在"工作版本"中那样),检查实际上就会通过.
后_RTC_CheckStackVars
完成检查(假设它通过),有一个附加的检查的恢复值ESP
对应于原始值.该检查在失败时,负责"在......中发生缓冲区溢出"消息.
在"工作版本"中,原始文件ESP
被复制到EBP
前导码的早期(第3415行),并且它是用于通过xoring ___security_cookie
(第3425行)计算校验和的值.在"失败的版本"中,校验和计算基于ESP
(行3425)在 ESP
推送一些寄存器(行3417-3419)之后递减12,但是与恢复的相应检查ESP
是在那些寄存器的相同点处完成的.已经恢复.
所以,简而言之,除非我没有做到这一点,看起来"工作版本"遵循标准教科书和堆栈处理教程,而"失败版本"会混淆运行时检查.
PS:"Debug build"是指"Win32 Console Application"新项目模板中"Debug"配置的标准编译器选项集.
正如 Hans 在评论中指出的那样,该问题无法再使用 Visual Studio 2013 重现。同样,Microsoft connect bug 报告的官方答案是:
我们无法使用 VS2013 Update 4 RTM 重现它。产品团队本身不再直接接受对 Microsoft Visual Studio 2012 及早期产品的反馈。您可以通过访问以下链接中的资源之一来获取有关 Visual Studio 2012 及更早版本的问题的支持: http ://www.visualstudio.com/support/support-overview-vs
因此,鉴于该问题仅在 VS2012 上通过函数内在函数(/Oi 编译器选项)、运行时检查(/RTCs 或 /RTC1 编译器选项)和一元减运算符的使用触发,请删除任何一个(或多个)这些条件应该可以解决该问题。
因此,可用的选项似乎是:
#pragma runtime_check
通过使用以下示例中的内容包围受影响的函数,禁用对受影响函数的运行时检查: #pragma runtime_check ("s", off)
void function()
{
MyClass obj;
obj.x() = -sqrt(2.0);
}
#pragma runtime_check ("s", restore)
Run Code Online (Sandbox Code Playgroud)
#pragma intrinsics (sqrt)
通过删除该行并添加来禁用内在函数#pragma function (sqrt)
(有关详细信息,
请参阅msdn )。#pragma intrinsics
每个所需内在函数的指令)。0-sqrt(2.0)
, -1*sqrt(2.0)
(删除一元减号运算符)之类的变通方法来调整代码,试图欺骗编译器使用不同的代码生成路径。请注意,这很可能会因看似微小的代码更改而中断。 归档时间: |
|
查看次数: |
588 次 |
最近记录: |