VS2012 中的 sqrt 内部堆栈运行时检查失败
Stack runtime check failure with sqrt intrinsic in VS2012
在调试一些崩溃时,我遇到了一些简化为以下情况的代码:
#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(专业版 11.0.61030.00 更新 4,Express 用于 Windows 桌面版 11.0.61030.00 更新 4)在 Debug|Win32 中构建时,代码触发 run-time 检查错误function
执行结束,显示为(以随机方式):
Run-Time Check Failure #2 - Stack around the variable 'obj' was corrupted.
或
A buffer overrun has occurred in Test.exe which has corrupted the program's internal state. Press Break to debug the program or Continue to terminate the program.
我知道这通常意味着堆栈中对象的某种缓冲区 overrun/underrun。也许我忽略了一些东西,但我看不到此 C++ 代码中可能发生此类缓冲区溢出的任何地方。在对代码进行各种调整并单步执行函数生成的汇编代码后(请参阅下面的 "details" 部分),我很想说它看起来像是 Visual Studio 2012 中的错误,但也许我只是陷入太深而错过了一些东西。
是否存在此代码不满足的内在函数使用要求或其他 C++ 标准要求,这可以解释此行为?
如果不是,禁用内部函数是否是获得正确 run-time 检查行为的唯一方法(除了下面提到的 0-sqrt
之类的变通方法,它很容易丢失)?
详情
玩代码,我注意到当我通过注释掉 #pragma
行禁用 sqrt
内在函数时,run-time 检查错误消失了。
否则使用 sqrt
intrinsic pragma(或 /Oi 编译器选项):
- 使用 setter 例如
obj.setx(double x) { m[0] = x; }
,毫不奇怪也会产生 run-time 检查错误。
- 令我惊讶的是,用
obj.x() = +sqrt(2.0)
或 obj.x() = 0.0-sqrt(2.0)
替换 obj.x() = -sqrt(2.0)
不会产生 run-time 检查错误。
- 同样将
obj.x() = -sqrt(2.0)
替换为 obj.x() = -1.4142135623730951;
不会生成 run-time 检查错误。
- 将成员
double m[1];
替换为 double m;
(连同 m[0]
访问)似乎只会生成 "Run-Time Check Failure #2" 错误(即使使用 obj.x() = -sqrt(2.0)
),有时运行良好。
- 将
obj
声明为 static
实例,或在堆上分配它不会产生 run-time 检查错误。
- 将编译器警告设置为 4 级不会产生任何警告。
- 使用 VS2005 Pro 或 VS2010 Express 编译相同的代码不会生成 run-time 检查错误。
- 对于它的价值,我已经在 Windows 7(Intel Xeon CPU)和 Windows 8.1 机器(Intel Core i7 CPU).
然后我接着看生成的汇编代码。出于说明的目的,我将 "the failing version" 称为从上面提供的代码中获得的代码,而我通过简单地注释 #pragma intrinsic (sqrt)
行生成了 "working version"。生成的汇编代码的 side-by-side 差异视图如下所示,左侧是 "failing version",右侧是 "working version":
首先,我注意到 _RTC_CheckStackVars
调用是造成 "Run-Time Check Failure #2" 错误的原因,尤其是在魔术饼干 0xCCCCCCCC
在 [=27= 周围仍然完好无损时进行检查] 堆栈上的对象(恰好从相对于 ESP
的原始值的 -20 字节的偏移量开始)。在下面的屏幕截图中,我用绿色突出显示了对象位置,用红色突出显示了魔术饼干位置。在 "working version" 中函数的开头是这样的:
然后稍后在调用 _RTC_CheckStackVars
之前:
现在在 "failing version" 中,序言包含一个额外的(第 3415 行)
and esp,0FFFFFFF8h
这实际上使 obj
在 8 字节边界上对齐。具体来说,无论何时使用以 0
或 8
半字节结尾的初始值 ESP
调用函数,都会从 -24 字节的偏移量开始存储 obj
相对于 ESP
的初始值。
问题是 _RTC_CheckStackVars
仍然在相对于原始 ESP
值的相同位置寻找那些 0xCCCCCCCC
魔法饼干,如上面描述的 "working version" 所示(即偏移量-24 和 -12 字节)。在这种情况下,obj
的前 4 个字节实际上与魔术 cookie 位置之一重叠。这显示在下面 "failing version":
开头的屏幕截图中
然后稍后在调用 _RTC_CheckStackVars
之前:
我们可以顺便注意到对应于 obj.m[0]
的实际数据在 "working version" 和 "failing version" 之间是相同的("cd 3b 7f 66 9e a0 f6 bf" 或预期值-1.4142135623730951 当解释为 double
).
顺便说一句,只要 ESP
的初始值以 4
或 C
半字节结尾(在这种情况下 obj
从 -20 字节偏移量开始,就像在 "working version").
中一样
在 _RTC_CheckStackVars
检查完成后(假设它通过),还有一个额外的检查 ESP
的恢复值对应于原始值。此检查失败时,负责 fr "A buffer overrun has occurred in ..." 消息。
在 "working version" 中,原始 ESP
在序言的早期被复制到 EBP
(第 3415 行),这个值用于通过异或计算校验和___security_cookie
(第 3425 行)。在 "failing version" 中,校验和计算基于 ESP
(第 3425 行)after ESP
has been decremented by 12 while push some registers (lines 3417-3419),但是对恢复的 ESP
的相应检查是在恢复这些寄存器的同一点完成的。
所以,简而言之,除非我没有做对,否则看起来 "working version" 遵循关于堆栈处理的标准教科书和教程,而 "failing version" 搞砸了 run-time 检查。
P.S.:"Debug build" 是指来自 "Win32 Console Application" 的 "Debug" 配置的标准编译器选项集新项目模板。
正如 Hans 在评论中指出的那样,Visual Studio 2013 无法再重现该问题。
同样,官方在Microsoft connect bug report上的回答是:
we are unable to reproduce it with VS2013 Update 4 RTM. The product team itself no longer directly accepting feedback for Microsoft Visual Studio 2012 and earlier products. You can get support for issues with Visual Studio 2012 and earlier by visiting one of the resources in the link below:
http://www.visualstudio.com/support/support-overview-vs
因此,假设问题仅在具有函数内在函数(/Oi 编译器选项)、运行时检查(/RTCs 或 /RTC1 编译器选项)的 VS2012 上触发和使用一元减号运算符,摆脱任何一个(或多个)这些条件应该可以解决这个问题。
因此,似乎可用的选项是:
- 升级到最新的Visual Studio(如果您的项目允许)
- 通过用
#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)。
如果已通过 "Enable Intrinsic Functions" 项目 属性(/Oi 编译器选项)为所有文件激活内部函数,则需要停用该项目 属性。然后,您可以逐个为特定函数启用内部函数,同时检查它们是否不受错误影响(每个必需的内部函数使用 #pragma intrinsics
指令)。
- 使用
0-sqrt(2.0)
、-1*sqrt(2.0)
(删除一元减号运算符)等变通方法调整代码,试图欺骗编译器使用不同的代码生成路径。请注意,这很可能会因看似微小的代码更改而中断。
在调试一些崩溃时,我遇到了一些简化为以下情况的代码:
#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(专业版 11.0.61030.00 更新 4,Express 用于 Windows 桌面版 11.0.61030.00 更新 4)在 Debug|Win32 中构建时,代码触发 run-time 检查错误function
执行结束,显示为(以随机方式):
Run-Time Check Failure #2 - Stack around the variable 'obj' was corrupted.
或
A buffer overrun has occurred in Test.exe which has corrupted the program's internal state. Press Break to debug the program or Continue to terminate the program.
我知道这通常意味着堆栈中对象的某种缓冲区 overrun/underrun。也许我忽略了一些东西,但我看不到此 C++ 代码中可能发生此类缓冲区溢出的任何地方。在对代码进行各种调整并单步执行函数生成的汇编代码后(请参阅下面的 "details" 部分),我很想说它看起来像是 Visual Studio 2012 中的错误,但也许我只是陷入太深而错过了一些东西。
是否存在此代码不满足的内在函数使用要求或其他 C++ 标准要求,这可以解释此行为?
如果不是,禁用内部函数是否是获得正确 run-time 检查行为的唯一方法(除了下面提到的 0-sqrt
之类的变通方法,它很容易丢失)?
详情
玩代码,我注意到当我通过注释掉 #pragma
行禁用 sqrt
内在函数时,run-time 检查错误消失了。
否则使用 sqrt
intrinsic pragma(或 /Oi 编译器选项):
- 使用 setter 例如
obj.setx(double x) { m[0] = x; }
,毫不奇怪也会产生 run-time 检查错误。 - 令我惊讶的是,用
obj.x() = +sqrt(2.0)
或obj.x() = 0.0-sqrt(2.0)
替换obj.x() = -sqrt(2.0)
不会产生 run-time 检查错误。 - 同样将
obj.x() = -sqrt(2.0)
替换为obj.x() = -1.4142135623730951;
不会生成 run-time 检查错误。 - 将成员
double m[1];
替换为double m;
(连同m[0]
访问)似乎只会生成 "Run-Time Check Failure #2" 错误(即使使用obj.x() = -sqrt(2.0)
),有时运行良好。 - 将
obj
声明为static
实例,或在堆上分配它不会产生 run-time 检查错误。 - 将编译器警告设置为 4 级不会产生任何警告。
- 使用 VS2005 Pro 或 VS2010 Express 编译相同的代码不会生成 run-time 检查错误。
- 对于它的价值,我已经在 Windows 7(Intel Xeon CPU)和 Windows 8.1 机器(Intel Core i7 CPU).
然后我接着看生成的汇编代码。出于说明的目的,我将 "the failing version" 称为从上面提供的代码中获得的代码,而我通过简单地注释 #pragma intrinsic (sqrt)
行生成了 "working version"。生成的汇编代码的 side-by-side 差异视图如下所示,左侧是 "failing version",右侧是 "working version":
首先,我注意到 _RTC_CheckStackVars
调用是造成 "Run-Time Check Failure #2" 错误的原因,尤其是在魔术饼干 0xCCCCCCCC
在 [=27= 周围仍然完好无损时进行检查] 堆栈上的对象(恰好从相对于 ESP
的原始值的 -20 字节的偏移量开始)。在下面的屏幕截图中,我用绿色突出显示了对象位置,用红色突出显示了魔术饼干位置。在 "working version" 中函数的开头是这样的:
然后稍后在调用 _RTC_CheckStackVars
之前:
现在在 "failing version" 中,序言包含一个额外的(第 3415 行)
and esp,0FFFFFFF8h
这实际上使 obj
在 8 字节边界上对齐。具体来说,无论何时使用以 0
或 8
半字节结尾的初始值 ESP
调用函数,都会从 -24 字节的偏移量开始存储 obj
相对于 ESP
的初始值。
问题是 _RTC_CheckStackVars
仍然在相对于原始 ESP
值的相同位置寻找那些 0xCCCCCCCC
魔法饼干,如上面描述的 "working version" 所示(即偏移量-24 和 -12 字节)。在这种情况下,obj
的前 4 个字节实际上与魔术 cookie 位置之一重叠。这显示在下面 "failing version":
然后稍后在调用 _RTC_CheckStackVars
之前:
我们可以顺便注意到对应于 obj.m[0]
的实际数据在 "working version" 和 "failing version" 之间是相同的("cd 3b 7f 66 9e a0 f6 bf" 或预期值-1.4142135623730951 当解释为 double
).
顺便说一句,只要 ESP
的初始值以 4
或 C
半字节结尾(在这种情况下 obj
从 -20 字节偏移量开始,就像在 "working version").
在 _RTC_CheckStackVars
检查完成后(假设它通过),还有一个额外的检查 ESP
的恢复值对应于原始值。此检查失败时,负责 fr "A buffer overrun has occurred in ..." 消息。
在 "working version" 中,原始 ESP
在序言的早期被复制到 EBP
(第 3415 行),这个值用于通过异或计算校验和___security_cookie
(第 3425 行)。在 "failing version" 中,校验和计算基于 ESP
(第 3425 行)after ESP
has been decremented by 12 while push some registers (lines 3417-3419),但是对恢复的 ESP
的相应检查是在恢复这些寄存器的同一点完成的。
所以,简而言之,除非我没有做对,否则看起来 "working version" 遵循关于堆栈处理的标准教科书和教程,而 "failing version" 搞砸了 run-time 检查。
P.S.:"Debug build" 是指来自 "Win32 Console Application" 的 "Debug" 配置的标准编译器选项集新项目模板。
正如 Hans 在评论中指出的那样,Visual Studio 2013 无法再重现该问题。 同样,官方在Microsoft connect bug report上的回答是:
we are unable to reproduce it with VS2013 Update 4 RTM. The product team itself no longer directly accepting feedback for Microsoft Visual Studio 2012 and earlier products. You can get support for issues with Visual Studio 2012 and earlier by visiting one of the resources in the link below: http://www.visualstudio.com/support/support-overview-vs
因此,假设问题仅在具有函数内在函数(/Oi 编译器选项)、运行时检查(/RTCs 或 /RTC1 编译器选项)的 VS2012 上触发和使用一元减号运算符,摆脱任何一个(或多个)这些条件应该可以解决这个问题。
因此,似乎可用的选项是:
- 升级到最新的Visual Studio(如果您的项目允许)
- 通过用
#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)。
如果已通过 "Enable Intrinsic Functions" 项目 属性(/Oi 编译器选项)为所有文件激活内部函数,则需要停用该项目 属性。然后,您可以逐个为特定函数启用内部函数,同时检查它们是否不受错误影响(每个必需的内部函数使用#pragma intrinsics
指令)。 - 使用
0-sqrt(2.0)
、-1*sqrt(2.0)
(删除一元减号运算符)等变通方法调整代码,试图欺骗编译器使用不同的代码生成路径。请注意,这很可能会因看似微小的代码更改而中断。