为什么pow()计算错误时,Webkit正在运行
Why does pow() calculate wrong when Webkit is running?
我有一个Qt c++应用程序,其中有一个GUI线程,其中一些浮点计算发生。它还打开QWebView
,其中有一个带有一些视频的flash播放器。
很明显,关闭QWebView会干扰下一个浮点操作。所以pow(double, double)
返回确定但不正确的值。
在一种情况下,它返回的值是正确值的1000
倍。另一次它返回1。#inf
与参数pow(10.0, 2.0)
一起使用。
我必须提到,它是在不同的计算机上测试的,并不特定于特定的CPU。
你有什么建议,如何找到在Webkit的地方,做了一些错误的协处理器和如何防止它?
示例(仅限x64)
环境:Qt 4.7.4, c++, HTML和flowplayer
cpp
wrongpow::wrongpow(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
QVBoxLayout* layout = new QVBoxLayout(0);
m_view = new QWebView(this);
m_view->setMinimumSize(400, 400);
m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
m_view->settings()->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
layout->addWidget(m_view);
QDir dir(QApplication::applicationDirPath());
dir.cd("media");
m_view->load(QUrl(QFileInfo(dir, "index.html").absoluteFilePath()));
QPushButton* button = new QPushButton(QLatin1String("Click on video start"), this);
layout->addWidget(button);
Q_ASSERT(connect(button, SIGNAL(clicked()), this, SLOT(closeView())));
setLayout(layout);
adjustSize();
}
Q_SLOT void wrongpow::closeView()
{
delete m_view;
m_view = NULL;
double wrongResult = pow(10.0, 2.0);
Q_ASSERT(wrongResult == 100.0);
}
html <div id='player' style='width:100%; height:100%;'>
<object width='100%' height='100%' id='_494187117' name='_494187117' data='js/plugins/flowplayer-3.2.18.swf' type='application/x-shockwave-flash'>
<param name='wmode' value='opaque'>
<param name='flashvars' value='config={"clip":{"url":"mp4:vod/demo.flowplayer/buffalo_soldiers.mp4","scaling":"fit","provider":"hddn","live":true},"plugins":{"hddn":{"url":"js/plugins/flowplayer.rtmp-3.2.13.swf","netConnectionUrl":"rtmp://r.demo.flowplayer.netdna-cdn.com/play"}},"canvas":{"backgroundGradient":"none"}}'>
</object>
</div>
这是一个完整的工作程序和源代码:
pow
结果不正确的原因是x87寄存器ST0-ST3变成了1#SNAN
,所以TAGS = 0xFF。它发生在销毁包含视频flash对象的QWebView期间。x87和SSE控制字包含正确的值。已测试的Adove Flash库:NPSWF64_14_0_0_125.dll
当WebCore::PluginView::stop
方法调用adobeflash插件对象的析构函数时发生。
NPError npErr = m_plugin->pluginFuncs()->destroy(m_instance, &savedData);
下面是破坏寄存器的过程(NPSWF64.dll)(实际上它使用与x87寄存器关联的MMX寄存器):
mov qword ptr [rsp+8],rcx
mov qword ptr [rsp+10h],rdx
mov qword ptr [rsp+18h],r8
push rsi
push rdi
push rbx
mov rdi,qword ptr [rsp+20h]
mov rsi,qword ptr [rsp+28h]
mov rcx,qword ptr [rsp+30h]
cmp rcx,20h
jle 000000002F9D8A2D
sub rcx,20h
// writes wrong values to the registers:
movq mm0,mmword ptr [rsi]
movq mm1,mmword ptr [rsi+8]
movq mm2,mmword ptr [rsi+10h]
movq mm3,mmword ptr [rsi+18h]
add rsi,20h
movq mmword ptr [rdi],mm0
movq mmword ptr [rdi+8],mm1
movq mmword ptr [rdi+10h],mm2
movq mmword ptr [rdi+18h],mm3
add rdi,20h
sub rcx,20h
jge 000000002F9D89F1
add rcx,20h
rep movs byte ptr [rdi],byte ptr [rsi]
pop rbx
pop rdi
pop rsi
ret
为了防止pow
计算错误,需要恢复寄存器值。我用最简单的方法来做。当插件被破坏时,我用一些参数调用pow
,它恢复寄存器。下一个电话是正确的。
还有一种更复杂(但可能正确)的方法,通过使用float.h
库的方法向寄存器写入新值。
稍微补充一下,我想我遇到过同样的问题(在一个64位c#应用程序中,调用Math。在。net Windows Forms WebBrowser控件中显示Flash视频)。
同样,在我的情况下,似乎64位Flash OCX(16.0.0.305)中的以下代码在MM0, MM1, MM2, MM3寄存器中留下了无效值(此代码似乎是某种快速内存副本):
C:WindowsSystem32MacromedFlashFlash64_16_0_0_305.ocx
00000000`2f9833c0 48894c2408 mov qword ptr [rsp+8],rcx
00000000`2f9833c5 4889542410 mov qword ptr [rsp+10h],rdx
00000000`2f9833ca 4c89442418 mov qword ptr [rsp+18h],r8
00000000`2f9833cf 56 push rsi
00000000`2f9833d0 57 push rdi
00000000`2f9833d1 53 push rbx
00000000`2f9833d2 488b7c2420 mov rdi,qword ptr [rsp+20h]
00000000`2f9833d7 488b742428 mov rsi,qword ptr [rsp+28h]
00000000`2f9833dc 488b4c2430 mov rcx,qword ptr [rsp+30h]
00000000`2f9833e1 4881f920000000 cmp rcx,20h
00000000`2f9833e8 7e43 jle Flash64_16_0_0_305!DllUnregisterServer+0x3c2a2d (00000000`2f98342d)
00000000`2f9833ea 4881e920000000 sub rcx,20h
00000000`2f9833f1 0f6f06 movq mm0,mmword ptr [rsi]
00000000`2f9833f4 0f6f4e08 movq mm1,mmword ptr [rsi+8]
00000000`2f9833f8 0f6f5610 movq mm2,mmword ptr [rsi+10h]
00000000`2f9833fc 0f6f5e18 movq mm3,mmword ptr [rsi+18h]
00000000`2f983400 4881c620000000 add rsi,20h
00000000`2f983407 0f7f07 movq mmword ptr [rdi],mm0
00000000`2f98340a 0f7f4f08 movq mmword ptr [rdi+8],mm1
00000000`2f98340e 0f7f5710 movq mmword ptr [rdi+10h],mm2
00000000`2f983412 0f7f5f18 movq mmword ptr [rdi+18h],mm3
00000000`2f983416 4881c720000000 add rdi,20h
00000000`2f98341d 4881e920000000 sub rcx,20h
00000000`2f983424 7dcb jge Flash64_16_0_0_305!DllUnregisterServer+0x3c29f1 (00000000`2f9833f1)
00000000`2f983426 4881c120000000 add rcx,20h
00000000`2f98342d f3a4 rep movs byte ptr [rdi],byte ptr [rsi]
00000000`2f98342f 5b pop rbx
00000000`2f983430 5f pop rdi
00000000`2f983431 5e pop rsi
00000000`2f983432 c3 ret
Flash OCX中的上述代码在web浏览器从显示Flash视频的页面导航离开时执行。
在此代码执行之前,浮点寄存器如下(使用WinDbg.exe检查):
fpcw=027f fpsw=3820 fptw=0080
st0= 6.00000000000000000000000...0e+0001 (0:4004:f000000000000000)
st1= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st2= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st3= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st4= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st5= 1.00000000000000000000000...0e+0000 (0:3fff:8000000000000000)
st6= 8.94231504669236176852000...0e-0001 (0:3ffe:e4ec5b1b9b742000)
st7= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
mxcsr=00001fa4
执行上述代码后,浮点寄存器如下:
fpcw=027f fpsw=0020 fptw=00ff
st0=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st1=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st2=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st3=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st4= 1.00000000000000000000000...0e+0000 (0:3fff:8000000000000000)
st5= 8.94231504669236176852000...0e-0001 (0:3ffe:e4ec5b1b9b742000)
st6= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st7= 6.00000000000000000000000...0e+0001 (0:4004:f000000000000000)
mxcsr=00001fa4
此时,浮点寄存器似乎处于"损坏"状态。
之后,在c#程序中,当执行Math时。因为字段操作码失败,因为它试图将值2.0推到浮点堆栈(即。当它试图准备计算cos(2.0)时:
COMDouble::Cos:
000007FE`E1D01570 movsd mmword ptr [rsp+8],xmm0
000007FE`E1D01576 fld qword ptr [rsp+8]
000007FE`E1D0157A fcos
000007FE`E1D0157C fstp qword ptr [rsp+8]
000007FE`E1D01580 movsd xmm0,mmword ptr [rsp+8]
000007FE`E1D01586 ret
在执行字段操作码之前,浮点寄存器与Flash OCX留下的寄存器完全相同。如上所述):
fpcw=027f fpsw=0020 fptw=00ff
st0=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st1=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st2=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st3=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st4= 1.00000000000000000000000...0e+0000 (0:3fff:8000000000000000)
st5= 8.94231504669236176852000...0e-0001 (0:3ffe:e4ec5b1b9b742000)
st6= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
st7= 6.00000000000000000000000...0e+0001 (0:4004:f000000000000000)
mxcsr=00001fa4
在执行field操作码之后,浮点寄存器如下所示(注意,st0是#IND,而不是预期值2.00000000000000000000000…0e+0000):
fpcw=027f fpsw=3a61 fptw=00ff
st0=-1.#IND0000000000000000000...0e+0000 (1:7fff:c000000000000000)
st1=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st2=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st3=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st4=-1.#SNAN000000000000000000...0e+0000 (1:7fff:0000000000000000)
st5= 1.00000000000000000000000...0e+0000 (0:3fff:8000000000000000)
st6= 8.94231504669236176852000...0e-0001 (0:3ffe:e4ec5b1b9b742000)
st7= 0.00000000000000000000000...0e+0000 (0:0000:0000000000000000)
mxcsr=00001fa4
这意味着fcos操作码试图计算#IND的余弦值。所以结果是Math的返回值。c#中的Cos是Double。NaN而不是预期的-0.416146836547142.
解析上述0x3a61的浮点状态字(FPSW)值表明,问题是"试图将值加载到非空闲的寄存器中":
3a61: 0011 1010 0110 0001
TOP (13,12,11): 111
C3,C2,C1,C0 (14,10,9,8): 0 010 (ie. C1 is 1) <-- loading a value into a register which is not free
IR (7): 0 Interrupt Request
SF (6): 1 Stack Fault <-- loading a value into a register which is not free
P (5): 1 Precision
U (4): 0 Underflow
O (3): 0 Overflow
Z (2): 0 Zero Divide
D (1): 0 Denormalised
I (0): 1 Invalid Operation
注意,这个问题只在第一次尝试Math.Cos(2)时发生(即。导航后很快远离Flash视频页面)。第二次及后续尝试计算Math.Cos(2)成功。
当Flash视频正在播放(或暂停)时,WebBrowser控件也会出现这个问题。
所以一些可能的变通方法是:
执行一个虚拟数学计算,忽略结果
编写一个64位的DLL,导出一个执行finit或emms操作码的函数(重置浮点寄存器的状态)。从c#中调用该函数
作为32位进程运行
注意:在c#中抛出一个虚拟异常,然后捕获该异常并没有帮助(这是针对其他浮点损坏问题的建议)。
下面是一些重现这个问题的c#代码(确保它以64位编译和运行;视频开始播放后,点击计算按钮)。
using System;
using System.IO;
using System.Windows.Forms;
namespace FlashTest
{
public partial class TestForm : Form
{
public TestForm()
{
// Windows 7 SP1 64 bit
// Internet Explorer 11 (11.0.9600.17633)
// Flash Player 16.0.0.305
// Visual Studio 2013 (12.0.31101.00)
// .NET 4.5 (4.0.30319.34209)
InitializeComponent();
addressTextBox.Text = "http://www.youtube.com/v/JVGdyC9CvFQ?autoplay=1";
GoButtonClickHandler(this, EventArgs.Empty);
}
private void GoButtonClickHandler(object sender, EventArgs e)
{
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".html");
File.WriteAllText(path, string.Format(@"<html><body>
<object classid=""clsid:D27CDB6E-AE6D-11CF-96B8-444553540000"" width=""100%"" height=""100%"" id=""youtubeviewer"">
<param name=""movie"" value=""{0}"">
</object></body></html>", addressTextBox.Text));
webBrowser.Navigate(path);
}
private void CalculateButtonClickHandler(object sender, EventArgs e)
{
webBrowser.DocumentCompleted += DocumentCompletedHandler;
webBrowser.Navigate("about:blank");
}
private void DocumentCompletedHandler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
webBrowser.DocumentCompleted -= DocumentCompletedHandler;
MessageBox.Show("Math.Cos(2) returned " + Math.Cos(2));
}
}
}
namespace FlashTest
{
partial class TestForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.webBrowser = new System.Windows.Forms.WebBrowser();
this.addressTextBox = new System.Windows.Forms.TextBox();
this.goButton = new System.Windows.Forms.Button();
this.calculateButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// webBrowser
//
this.webBrowser.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.webBrowser.Location = new System.Drawing.Point(12, 41);
this.webBrowser.MinimumSize = new System.Drawing.Size(20, 20);
this.webBrowser.Name = "webBrowser";
this.webBrowser.Size = new System.Drawing.Size(560, 309);
this.webBrowser.TabIndex = 3;
//
// addressTextBox
//
this.addressTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.addressTextBox.Location = new System.Drawing.Point(12, 14);
this.addressTextBox.Name = "addressTextBox";
this.addressTextBox.Size = new System.Drawing.Size(398, 20);
this.addressTextBox.TabIndex = 0;
//
// goButton
//
this.goButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.goButton.Location = new System.Drawing.Point(416, 12);
this.goButton.Name = "goButton";
this.goButton.Size = new System.Drawing.Size(75, 23);
this.goButton.TabIndex = 1;
this.goButton.Text = "&Go";
this.goButton.UseVisualStyleBackColor = true;
this.goButton.Click += new System.EventHandler(this.GoButtonClickHandler);
//
// calculateButton
//
this.calculateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.calculateButton.Location = new System.Drawing.Point(497, 12);
this.calculateButton.Name = "calculateButton";
this.calculateButton.Size = new System.Drawing.Size(75, 23);
this.calculateButton.TabIndex = 2;
this.calculateButton.Text = "&Calculate";
this.calculateButton.UseVisualStyleBackColor = true;
this.calculateButton.Click += new System.EventHandler(this.CalculateButtonClickHandler);
//
// TestForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(584, 362);
this.Controls.Add(this.webBrowser);
this.Controls.Add(this.goButton);
this.Controls.Add(this.addressTextBox);
this.Controls.Add(this.calculateButton);
this.Name = "TestForm";
this.Text = "Adobe Flash Test";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.WebBrowser webBrowser;
private System.Windows.Forms.TextBox addressTextBox;
private System.Windows.Forms.Button goButton;
private System.Windows.Forms.Button calculateButton;
}
}
显然你在Qt的webkit版本中遇到了一个bug,我无法在Qt 5.3中使用MSVC 13 x64构建QtCreator,在发布和调试中进行复制。
已经有一些关于QtWebKit使用浮点的bug报告:
- https://bugreports.qt - project.org/browse/qtwebkit - 266
- 运行同一解决方案的另一个项目的项目
- CMake-按正确顺序将项目与C运行时对象文件链接
- 如何运行位于boost/libs/python/example/tutorial目录中的hello.cpp和Jamfil
- 代码在main()中运行,但在函数中出现错误
- 我在c++代码中生成了一个运行时#3异常
- 如何在linux终端中同时编译和运行c++代码
- 为什么在运行时没有向我们提供有关分段错误的更多信息?
- 如何在运行中期切换GTK CSS style_context
- 如何在MS Visual Studio 2019中运行QT UI
- 如何通过cpp程序运行shell脚本
- IPC使用多个管道和分支进程来运行Python程序
- 删除指向指针的指针是运行时错误吗
- 如何用参数值调用函数(仅在运行时已知)
- 为什么即使使用-cudart-static进行编译,库用户仍然需要链接到cuda运行时
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- c++中的指针和运行时错误
- 在C应用程序中运行C++(带有STL)函数
- 运行程序时出现问题
- 控制允许动态运行c++的并发操作数
- 为什么pow()计算错误时,Webkit正在运行