2014-07-17 8 views
11

Ich habe eine Qt C++ - Anwendung, wo es einen GUI-Thread gibt, in dem einige Gleitkommaberechnungen passieren. Es öffnet sich auch QWebView, wo es einen Flash-Player mit etwas Video gibt.Warum berechnet pow() falsch, wenn Webkit läuft?

Es ist offensichtlich, dass das Schließen von QWebView die nächste Gleitkommaoperation beeinträchtigt. So gibt pow(double, double) bestimmte, aber falsche Werte zurück.

In einem Fall gab es Werte 1000 mal mehr als die richtige zurück. Ein anderes Mal gab es 1 zurück. #inf wenn mit Argumenten verwendet pow(10.0, 2.0).

Ich muss erwähnen, dass es auf verschiedenen Computern getestet wird und nicht für eine bestimmte CPU spezifisch ist.

Haben Sie Vorschläge, wie Sie den Platz in Webkit finden, der mit Co-Prozessor etwas falsch macht und wie man es verhindert?

Probe (nur x64)

Umwelt: Qt 4.7.4, C++, HTML und Animationen

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> 

ist hier ein voll funktionsfähiges Programm mit Quellen: Download 15MB

+0

Bitte fügen Sie Code für das Problem zu reproduzieren. Wie denkst du, interferiert webkit mit der pow() - Funktion? –

+0

@ sebastian-lang Es stört irgendwie. Schau mal hier: [Screenshot] (http://i.imgur.com/FuquLxN.png) Ich fand heraus, dass nur der erste Aufruf von pow falsches Ergebnis zurückgibt. Auch ich werde mein Bestes tun, um ein funktionierendes Beispiel zu machen, weil es Tausende von Codezeilen in der realen Anwendung gibt. – Ezee

+3

@Ezee Ihre Schritte zum Reproduzieren sind ein Screenshot von zwei Codezeilen in XCode? Sie verstehen völlig falsch, wofür diese sind. Wir glauben Dir. Wir wollen nicht beweisen, dass es passiert. Wir müssen das Problem wiederholen, um Ihnen die richtige Erklärung zu geben. Wie sollen wir von einem Screenshot mit zwei Zeilen Code dorthin gehen? (Beachten Sie, dass "wir" StackOverflow ist, nicht ich insbesondere.) –

Antwort

6

Grund des falschen Ergebnis von pow ist, dass x87-Register ST0-ST3 wurde 1#SNAN und so TAGS = 0xFF . Dies geschieht während der Zerstörung von QWebView mit einem Video Flash-Objekt. Die Kontrollwörter von x87 und SSE enthalten korrekte Werte. Getestet Adove Flash-Bibliothek: NPSWF64_14_0_0_125.dll

Es passiert, wenn WebCore::PluginView::stop Methode ruft einen Destruktor des Adobe Flash-Plugin-Objekt.

NPError npErr = m_plugin->pluginFuncs()->destroy(m_instance, &savedData); 

Hier ist das Verfahren (NPSWF64.dll), die die Register verderben (es tatsächlich nutzt Register MMX mit x87 Registern zugeordnet ist):

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   

falsche Berechnung von pow von diesem Fehler zu verhindern, es ist benötigt, um die Registerwerte wiederherzustellen. Ich benutze den einfachsten Weg, dies zu tun. Wenn das Plugin zerstört wird, rufe ich pow mit einigen Argumenten an und es stellt die Register wieder her. Der nächste Anruf wird korrekt sein.
Es gibt eine kompliziertere (aber wahrscheinlich korrekte) Möglichkeit, dasselbe zu tun, indem neue Werte in die Register geschrieben werden, indem Methoden aus der Bibliothek float.h verwendet werden.

+0

@ pascal-cuoq Ich würde mich über Ihre Kommentare zu den Ergebnissen der Untersuchung freuen. – Ezee

1

Offenbar haben Sie einen Fehler in der Webkit-Version Ihres Qt gefunden. Ich kann in QT 5.3 mit QtCreator Builds nicht reproduzieren, wenn MSVC 13 x64 in Release und Debug verwendet wird.

Es gibt bereits einige Fehler für QtWebKit mit Gleitpunkte berichtet:

+0

* Dieser Fehler ist nicht im Zusammenhang mit dem Problem. * Ich habe es in Qt 4.7 und 4.8 getestet. – Ezee

+2

Ich weiß, ich möchte nur sagen, dass Sie nicht der Erste sind, der diesen * Art * Bug mit Webkit erreicht. :) – fjardon

+1

Eigentlich wurde es ein Fehler im Adobe Flash Player Plugin. – Ezee

2

Nur um hinzuzufügen, ich denke, ich habe das gleiche Problem (in einer 64-Bit-C# -Anwendung, die Math.Cos aufruft und zeigt auch ein Flash-Video im .NET Windows Forms WebBrowser-Steuerelement) .

In meinem Fall scheint es, dass der folgende Code in der 64-Bit-Flash-OCX (16.0.0.305) ungültige Werte in den Registern MM0, MM1, MM2, MM3 zurücklässt (dieser Code scheint eine Art von schneller Speicher kopieren):

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 

der obige Code in dem Flash-OCX ausgeführt wird, wie der Web-Browser von der Seite zeigt das Flash-Video navigiert.

Bevor dieser Code die Gleitkomma ausführt wie folgt waren Registern (untersucht unter Verwendung von 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 

Nachdem der obige Code die Gleitkommaregister waren wie folgt ausgeführt wurde:

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 

An diesem Punkt scheinen die Fließkomma-Register in einem "korrupten" Zustand zu sein.

So später wieder in dem C# Programm, wenn die Math.cos FLD opcode Ausführen fehlschlägt, wie es versucht, den Wert 2,0 auf den Gleitkomma-Stapel zu drücken (dh., Wie es für die Berechnung des Kosinus von 2,0 herzustellen versucht)

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 

Unmittelbar vor der FLD Opcode die Gleitkommaregister waren genau ausgeführt wird, wie sie durch den Flash-OCX gelassen wurden: (dh wie oben.):

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 

Unmittelbar nach dem FLD Opcode ausgeführt wurde Fließkommaregister waren wie folgt (notic e dass ST0 ist #IND eher als der erwartete Wert von 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 

Das bedeutet dann, dass der EKAS Opcode den Kosinus #IND zu berechnen versucht. Und so endet es, dass der Rückgabewert von Math.Cos in C# Double.NaN statt der erwarteten -0.416146836547142 ist.

Zergliedern des Gleitkomma-Statuswortes (FPSW) -Wert von 0x3a61 zeigt oben, dass das Problem „ein Versuch, einen Wert in ein Register zu laden, die nicht frei ist“:

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 

zu beachten, dass das Problem nur passiert das erste Mal, dass Math.Cos (2) versucht wird (dh kurz nach der Navigation weg von der Flash-Video-Seite). Der zweite und nachfolgende Versuche, Math.Cos (2) zu berechnen, sind erfolgreich.

Das Problem tritt auch auf, wenn das WebBrowser-Steuerelement verworfen wird, während ein Flash-Video abgespielt wird (oder pausiert).

So einige mögliche Abhilfen sind:

  1. Führen Sie eine Dummy-mathematische Berechnung und das Ergebnis ignorieren.

  2. Schreiben Sie eine 64-Bit-DLL, die eine Funktion exportiert, die die finit- oder emms-Opcodes ausführt (um den Zustand der Gleitkommaregister zurückzusetzen). Nennen Sie diese Funktion von C#.

  3. Ausführen als ein 32-Bit-Prozess.

Hinweis: eine Dummy-Ausnahme in C# zu werfen und dann diese Ausnahme abfangen helfen nicht (dies für andere Gleitkomma Korruption Probleme vorgeschlagen wird).

Hier ist ein C# -Code, der das Problem reproduziert (stellen Sie sicher, dass es kompiliert und als 64-Bit ausgeführt wird; klicken Sie auf die Schaltfläche Berechnen, nachdem das Video abgespielt wurde).

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; 
    } 
}