【问题标题】:Why does pow() calculate wrong when Webkit is running?为什么 Webkit 运行时 pow() 计算错误?
【发布时间】:2014-07-17 06:02:34
【问题描述】:

我有一个 Qt C++ 应用程序,其中有一个 GUI 线程,其中会发生一些浮点计算。它还会打开QWebView,其中有一个带有一些视频的 Flash 播放器。

很明显,关闭 QWebView 会干扰新的下一个浮点操作。 所以pow(double, double) 返回确定但不正确的值。

在一种情况下,它返回的值1000 是正确值的两倍。另一次它在与参数pow(10.0, 2.0) 一起使用时返回 1.#inf

我不得不提到它是在不同的计算机上测试的,并不特定于特定的 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={&quot;clip&quot;:{&quot;url&quot;:&quot;mp4:vod/demo.flowplayer/buffalo_soldiers.mp4&quot;,&quot;scaling&quot;:&quot;fit&quot;,&quot;provider&quot;:&quot;hddn&quot;,&quot;live&quot;:true},&quot;plugins&quot;:{&quot;hddn&quot;:{&quot;url&quot;:&quot;js/plugins/flowplayer.rtmp-3.2.13.swf&quot;,&quot;netConnectionUrl&quot;:&quot;rtmp://r.demo.flowplayer.netdna-cdn.com/play&quot;}},&quot;canvas&quot;:{&quot;backgroundGradient&quot;:&quot;none&quot;}}'>
    </object>
</div>  

这是一个完整工作的程序,其中包含源代码: Download 15MB

【问题讨论】:

  • 请添加重现问题的代码。你认为 webkit 是如何干扰 pow() 函数的?
  • @sebastian-lange 它确实以某种方式干扰。看这里:Screenshot 我发现只有第一次调用 pow 返回错误的结果。另外,我会尽力制作一个工作示例,因为实际应用程序中有数千行代码。
  • @Ezee 你的重现步骤是XCode中两行代码的截图?你完全误解了这些是干什么用的。我们相信你。我们不要求提供它发生的证明。我们需要重现问题以便给您正确的解释。我们应该如何从带有两行代码的屏幕截图到那里? (请注意,“我们”是 StackOverflow,而不是我。)
  • @Ezee 这是一个绝妙的问题,如果在 48 小时内没有回答,我会悬赏。一般的评论是浮点数学是在具有状态(特别是当前舍入模式)的 FPU 内实现的。状态以命令式的方式改变(计算2.0 + 3.0向上取整,指令序列是“改变到‘向上取整’模式,然后计算2.0 + 3.0,然后再次改变取整模式——或者不改变”)。一些基本的浮点函数(例如sinpow)是用微妙的浮点算法实现的,如果舍入模式可能会完全出错……
  • MSDN 声明对于x87SSE 控制字,“修改 FPCSR 中任何字段的被调用者必须在返回给调用者之前恢复它们。此外,已修改任何这些字段的调用者必须在调用被调用者之前将它们恢复为其标准值,除非根据协议,被调用者期望修改后的值。"。进入/退出任何函数的标准精度和舍入模式是 Double 和 Round-to-Nearest。你能证实是这样吗?

标签: c++ qt webkit pow x87


【解决方案1】:

pow 结果不正确的原因是 x87 寄存器 ST0-ST3 变成了1#SNAN,因此 TAGS = 0xFF。它发生在销毁包含视频 Flash 对象的 QWebView 期间。 x87 和 SSE 的控制字包含正确的值。 经过测试的 Adove Flash 库:NPSWF64_14_0_0_125.dll

WebCore::PluginView::stop 方法调用 Adob​​e Flash 插件对象的析构函数时会发生这种情况。

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 库中的方法将新值写入寄存器。

【讨论】:

  • @pascal-cuoq 非常感谢您的 cmets 调查结果。
【解决方案2】:

稍微补充一点,我想我遇到了同样的问题(在调用 Math.Cos 并且恰好在 .NET Windows Forms WebBrowser 控件中显示 Flash 视频的 64 位 C# 应用程序中)。

同样,在我的情况下,64 位 Flash OCX (16.0.0.305) 中的以下代码似乎在 MM0、MM1、MM2、MM3 寄存器中留下了无效值(此代码似乎有点快内存拷贝):

C:\Windows\System32\Macromed\Flash\Flash64_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.Cos 时,fld 操作码失败,因为它试图将值 2.0 推入浮点堆栈(即,当它试图准备计算 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

在执行 fld 操作码之前,浮点寄存器与 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

在执行 fld 操作码后,浮点寄存器如下(注意 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 的余弦。因此,C# 中 Math.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 控件,也会出现此问题。

所以一些潜在的解决方法是:

  1. 执行一次虚拟数学计算并忽略结果。

  2. 编写一个 64 位 DLL,导出一个执行 finit 或 emms 操作码的函数(以重置浮点寄存器的状态)。从 C# 调用该函数。

  3. 作为 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;
    }
}

【讨论】:

    【解决方案3】:

    显然,您在 Qt 的 webkit 版本中遇到了一个错误。我无法在 QT 5.3 中使用 MSVC 13 x64 的 QtCreator 构建在发布和调试中重现。

    QtWebKit 已经报告了一些带有浮点的错误:

    【讨论】:

    • * 此错误与问题无关。 * 我在 Qt 4.7 和 4.8 中测试过。
    • 我知道,我只想说您不是第一个使用 webkit 遇到这种 种类 错误的人。 :)
    • 其实是Adobe Flash Player插件的bug。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多