Ist dies "sollte nicht passieren" Absturz ein AMD Fusion CPU-Fehler?

68

In meinem Unternehmen haben einige Kunden angerufen, weil unser Programm mit einer Zugriffsverletzung auf ihren Systemen abstürzt.

Der Absturz tritt in SQLite 3.6.23.1 auf, das wir als Teil unserer Anwendung ausliefern. (Wir liefern einen benutzerdefinierten Build aus, um dieselben VC ++ - Bibliotheken wie der Rest der App zu verwenden, aber es handelt sich um den Standard-SQLite-Code.)

Der Absturz passiert , wenn pcache1Fetchausführt call 00000000, wie sie in der WinDbg Aufrufliste angezeigt:

0b50e5c4 719f9fad 06fe35f0 00000000 000079ad 0x0
0b50e5d8 719f9216 058d1628 000079ad 00000001 SQLite_Interop!pcache1Fetch+0x2d [sqlite3.c @ 31530]
0b50e5f4 719fd581 000079ad 00000001 0b50e63c SQLite_Interop!sqlite3PcacheFetch+0x76 [sqlite3.c @ 30651]
0b50e61c 719fff0c 000079ad 0b50e63c 00000000 SQLite_Interop!sqlite3PagerAcquire+0x51 [sqlite3.c @ 36026]
0b50e644 71a029ba 0b50e65c 00000001 00000e00 SQLite_Interop!getAndInitPage+0x1c [sqlite3.c @ 40158]
0b50e65c 71a030f8 000079ad 0aecd680 071ce030 SQLite_Interop!moveToChild+0x2a [sqlite3.c @ 42555]
0b50e690 71a0c637 0aecd6f0 00000000 0001edbe SQLite_Interop!sqlite3BtreeMovetoUnpacked+0x378 [sqlite3.c @ 43016]
0b50e6b8 71a109ed 06fd53e0 00000000 071ce030 SQLite_Interop!sqlite3VdbeCursorMoveto+0x27 [sqlite3.c @ 50624]
0b50e824 71a0db76 071ce030 0b50e880 071ce030 SQLite_Interop!sqlite3VdbeExec+0x14fd [sqlite3.c @ 55409]
0b50e850 71a0dcb5 0b50e880 21f9b4c0 00402540 SQLite_Interop!sqlite3Step+0x116 [sqlite3.c @ 51744]
0b50e870 00629a30 071ce030 76897ff4 70f24970 SQLite_Interop!sqlite3_step+0x75 [sqlite3.c @ 51806]

Die relevante Zeile des C-Codes lautet:

if( createFlag==1 ) sqlite3BeginBenignMalloc();

Der Compiler inlines sqlite3BeginBenignMalloc, definiert als:

typedef struct BenignMallocHooks BenignMallocHooks;
static SQLITE_WSD struct BenignMallocHooks {
  void (*xBenignBegin)(void);
  void (*xBenignEnd)(void);
} sqlite3Hooks = { 0, 0 };

# define wsdHooksInit
# define wsdHooks sqlite3Hooks

SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void){
  wsdHooksInit;
  if( wsdHooks.xBenignBegin ){
    wsdHooks.xBenignBegin();
  }
}

Und die Versammlung dafür ist:

719f9f99    mov     esi,dword ptr [esp+1Ch]
719f9f9d    cmp     esi,1
719f9fa0    jne     SQLite_Interop!pcache1Fetch+0x2d (719f9fad)
719f9fa2    mov     eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]
719f9fa7    test    eax,eax
719f9fa9    je      SQLite_Interop!pcache1Fetch+0x2d (719f9fad)
719f9fab    call    eax ; *** CRASH HERE ***
719f9fad    mov     ebx,dword ptr [esp+14h]

Die Register sind:

eax=00000000 ebx=00000001 ecx=000013f0 edx=fffffffe esi=00000001 edi=00000000
eip=00000000 esp=0b50e5c8 ebp=000079ad iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202

Wenn eax0 ist (was es ist), sollte das Null-Flag von gesetzt werden test eax, eax, aber es ist nicht Null. Da das Null-Flag nicht gesetzt ist, jenicht springt und die App beim Ausführen abstürzt call eax (00000000).

Update : eaxsollte hier immer 0 sein, da sqlite3Hooks.xBenignBegindies in unserem Build des Codes nicht festgelegt ist. Ich könnte SQLite mit SQLITE_OMIT_BUILTIN_TESTdefined neu #define sqlite3BeginBenignMalloc()erstellen , was sich im Code einschalten und diesen Codepfad komplett weglassen würde . Das mag das Problem lösen, aber es fühlt sich nicht wie eine "echte" Lösung an. Was würde es in einem anderen Codepfad verhindern?

Bisher ist der gemeinsame Faktor, dass alle Kunden "Windows 7 Home Premium 64-Bit (6.1, Build 7601) Service Pack 1" ausführen und über eine der folgenden CPUs verfügen (laut DxDiag):

  • AMD A6-3400M APU mit Radeon (tm) HD-Grafik (4 CPUs), ~ 1,4 GHz
  • AMD A8-3500M APU mit Radeon (tm) HD-Grafik (4 CPUs), ~ 1,5 GHz
  • AMD A8-3850 APU mit Radeon (tm) HD-Grafik (4 CPUs), ~ 2,9 GHz

Laut dem AMD Fusion-Artikel von Wikipedia sind dies alles AMD Fusion-Chips des "Llano" -Modells, die auf dem K10-Kern basieren und im Juni 2011 veröffentlicht wurden, als wir zum ersten Mal Berichte erhielten.

Das am häufigsten verwendete Kundensystem ist das Toshiba Satellite L775D. Wir haben jedoch auch Absturzberichte von HP Pavilion dv6 & dv7- und Gateway-Systemen.

Könnte dieser Absturz durch einen CPU-Fehler verursacht werden (siehe Errata für 12-Stunden-Prozessoren der AMD-Familie ), oder gibt es eine andere mögliche Erklärung, die ich übersehen habe? (Laut Raymond könnte es Übertakten sein , aber es ist seltsam, dass nur dieses spezielle CPU-Modell betroffen ist, wenn ja.)

Ehrlich gesagt scheint es nicht möglich zu sein, dass es sich wirklich um einen CPU- oder Betriebssystemfehler handelt, da die Kunden in anderen Anwendungen keine Bluescreens oder Abstürze erhalten. Es muss eine andere, wahrscheinlichere Erklärung geben - aber was?

Update 15. August: Ich habe ein Toshiba L745D-Notebook mit einem AMD A6-3400M-Prozessor erworben und kann den Absturz beim Ausführen des Programms konsistent reproduzieren. Der Absturz erfolgt immer nach der gleichen Anweisung. .timemeldet zwischen 1 und 30 Minuten Benutzerzeit vor dem Absturz. Eine Tatsache (die für das Problem relevant sein kann), die ich im ursprünglichen Beitrag nicht erwähnt habe, ist, dass die Anwendung über mehrere Threads verfügt und sowohl eine hohe CPU- als auch eine E / A-Auslastung aufweist. Die Anwendung erzeugt standardmäßig vier Worker-Threads und veröffentlicht eine CPU-Auslastung von 80 +% (es gibt einige Blockierungen für E / A sowie für Mutexe im SQLite-Code), bis sie abstürzt. Ich habe die Anwendung so geändert, dass nur zwei Threads verwendet werden, und sie ist immer noch abgestürzt (obwohl es länger gedauert hat). Ich führe jetzt einen Test mit nur einem Thread durch und er ist noch nicht abgestürzt.

Beachten Sie auch, dass es sich anscheinend nicht nur um ein CPU-Lastproblem handelt. Ich kann Prime95 ohne Fehler auf dem System ausführen und es erhöht die CPU-Temperatur auf> 70 ° C, während meine Anwendung während des Betriebs kaum eine Temperatur über 50 ° C erreicht.

Update 16. August: Wenn Sie die Anweisungen leicht stören, wird das Problem "verschwinden". Zum Beispiel verhindert das Ersetzen der Speicherlast ( mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]) durch xor eax, eaxden Absturz. Den Original - C - Code zu modifizieren einen zusätzlichen Scheck an die hinzuzufügen if( createFlag==1 )Anweisung ändert die relativen Verschiebungen von verschiedenen Sprüngen im kompilierten Code (sowie die Position der test eax, eaxund call eaxAussage) und scheint auch das Problem zu vermeiden.

Das seltsamste Ergebnis, das ich bisher gefunden habe, ist, dass das Programm durch Ändern der jneat 719f9fa0auf zwei nopAnweisungen (so dass die Steuerung immer auf die test eax, eaxAnweisung fällt , unabhängig vom Wert von createFlag/ esi) ohne Absturz ausgeführt werden kann.

Bradley Grainger
quelle
2
Es ist mit ziemlicher Sicherheit kein CPU-Fehler. Haben Sie darüber nachgedacht, einen einfacheren Testfall zu erstellen?
Oliver Charlesworth
2
@Mehrdad: Ja, ein Code könnte einfach dorthin springen, aber es hat wirklich gute Arbeit geleistet, den Callstack vorzutäuschen, wenn ja.
Bradley Grainger
1
Ich muss Oli zustimmen. Es ist äußerst unwahrscheinlich, dass ein so grundlegendes Problem wie das testSetzen falscher Flags bei internen QS-Tests nicht erfasst wird. Zumal diese test then jumpOperation eine sehr häufige Compileroptimierung zu sein scheint, die in einer Vielzahl von Programmen verwendet wird.
aroth
3
Ich wollte mich nur einschalten und sagen, dass das eine sehr gut geschriebene Frage war. +1
Gahooa
1
@flolo: Dies ist ein 32-Bit-Prozess, der unter 64-Bit-Windows (WOW64) ausgeführt wird. Diese Ausgabe ist normal.
Bradley Grainger

Antworten:

27

Ich habe auf der Microsoft Build-Konferenz mit einem AMD-Ingenieur über diesen Fehler gesprochen und ihm meinen Repro gezeigt. Er hat mir heute Morgen eine E-Mail geschickt:

Wir haben untersucht und festgestellt, dass dies auf bekannte Errata in der Llano APU-Familie zurückzuführen ist. Es kann je nach OEM über ein BIOS-Update behoben werden. Wenn möglich, empfehlen Sie es Ihren Kunden (auch wenn Sie eine Problemumgehung haben).

Falls Sie interessiert sind, lautet die Errata 665 im Family 12h Revision Guide (siehe Seite 45): http://support.amd.com/TechDocs/44739_12h_Rev_Gd.pdf#page=45

Hier ist die Beschreibung dieses Erratums:

665 Integer Divide-Anweisung kann zu unvorhersehbarem Verhalten führen

Beschreibung

Unter einem hochspezifischen und detaillierten Satz interner Zeitsteuerungsbedingungen kann der Prozessorkern einen spekulativen DIV- oder IDIV-Integer-Divide-Befehl abbrechen (aufgrund der Umleitung der spekulativen Ausführung, beispielsweise aufgrund einer falsch vorhergesagten Verzweigung), aber den ersten hängen lassen oder vorzeitig abschließen Anweisung des nicht spekulativen Pfades.

Mögliche Auswirkungen auf das System

Unvorhersehbares Systemverhalten, das normalerweise zu einem Systemstillstand führt.

Vorgeschlagene Problemumgehung

Das BIOS sollte MSRC001_1029 [31] einstellen.

Diese Problemumgehung ändert die DIV / IDIV-Befehlslatenz, die im Softwareoptimierungshandbuch für 10- und 12- Stunden- Prozessoren der AMD-Familie , Bestellnummer 40546, angegeben ist. Mit dieser Problemumgehung ähnelt die DIV / IDIV-Latenz für 12-Stunden-Prozessoren der AMD-Familie der DIV / IDIV-Latenz für 10-Stunden-Prozessoren der AMD-Familie.

Fix geplant

Nein

Bradley Grainger
quelle
1
Das Problem "665 Integer Divide" wird in den Passmark-Foren behandelt: passmark.com/forum/… Ein Kommentar besagt, dass das Problem nur beim Zweikanal-RAM auftritt . Ein Computer mit einem 4 GB RAM-Stick und einer Llano-CPU ist also wahrscheinlich ohne das BIOS-Update in Ordnung. Wenn Sie jedoch 20 US-Dollar für ein Upgrade auf 8 GB ausgeben, treten Probleme auf, die Sie wahrscheinlich (zu Unrecht!) Dem RAM zuschreiben. Leider führt die "Korrektur" des BIOS zu einer Verlangsamung des ganzzahligen mathematischen Benchmarks von Passmark um> 80% und einer Verringerung des Passmark-Scores um> 30%.
Dave Burton
1

Ich bin etwas besorgt, dass der generierte Code if (wsdHooks.xBenignBegin)nicht sehr allgemein ist. Er geht davon aus dem einzig wahren Wert ist , 1während es wirklich testen soll jeden Wert ungleich Null. Trotzdem ist MSVC manchmal so verwirrend. Es ist wahrscheinlich nichts. Egal: Diese Anweisungen gelten für CCode, der nicht angezeigt wird.

Da das eflag- ZBit klar und EAXNull ist, ist der Code durch Ausführen des Befehls nicht hierher gekommen

719f9fa7    test    eax,eax

Es muss ein Sprung von irgendwo anders zur folgenden Anweisung ( 719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d) oder sogar zur callAnweisung selbst erfolgen.

Eine weitere Komplikation besteht darin, dass es bei der x86-Familie häufig vorkommt, dass ein ungültiges Sprungziel (wie das zweite Byte des JEBefehls) für einige Befehle ungestört (keine Fehler) ausgeführt wird und häufig schließlich wieder die richtige Befehlsausrichtung erreicht. Anders gesagt, Sie suchen möglicherweise nicht nach einem Sprung zum Anfang einer dieser Anweisungen: Ein Sprung befindet sich möglicherweise in der Mitte ihrer Bytes, was dazu führt, dass unauffällige Operationen ausgeführt werden, add [al+ebp],aldie normalerweise nicht bemerkt werden.

Ich gehe davon aus, dass ein Haltepunkt in der testAnweisung für die Ausnahme nicht erreicht wird. Die einzige Möglichkeit, solche Ursachen zu finden, besteht darin, entweder sehr viel Glück zu haben oder alles zu vermuten und sie einzeln als unschuldig zu beweisen.

Wallyk
quelle
In Bezug auf Ihren ersten Absatz: testSetzt ZF nur, wenn es eax & eaxgleich Null ist, so dass es mit dem folgenden ziemlich sicher ist je.
Michael Foukarakis
Wenn die Analyse deaktiviert ist, ist die Prüfung gegen 1 darauf zurückzuführen, dass der C-Code gegen 1 prüft, da dies für die Zeile if( createFlag==1 ) sqlite3BeginBenignMalloc();nicht der Fall ist if (wsdHooks.xBenignBegin)(siehe den Kommentar des OP zur sqlite3BeginBenignMallocInline)
Necrolis,
@ Michael Foukarakis: Ein fairer Punkt, also habe ich meinen Kommentar redigiert.
Wallyk
1
Ich versuche nicht, argumentativ zu sein, aber ich denke, Ihre Theorie widerspricht meiner Feststellung, dass das Ersetzen des jnevor dem testdurch nopAnweisungen den Absturz scheinbar verhindert. (100% reproduzierbar ohne diese Änderung, 0% reproduzierbar an einem Testtag damit.) Wenn eine andere Anweisung in die Mitte jeoder direkt in die springt call, ist sie von dieser Änderung nicht betroffen. Wie erklärt die Theorie, dass ein anderer Code zu dem springt, jeoder die call, dass dies nur auf Llano-APUs geschieht?
Bradley Grainger
-1

Versuchen Sie, die wahrscheinlicheren Ursachen auszuschließen, bevor Sie die Möglichkeit eines CPU-Fehlers in Betracht ziehen

  1. Ein anderer Codepfad zur Aufrufanweisung. Verwenden Sie den ufBefehl, um die Funktion zu zerlegen und nach anderen Sprüngen / Verzweigungen zur Aufrufanweisung zu suchen

  2. Von der Hook-Funktion auf 0 springen / aufrufen. dps SQLite_Interop!sqlite3Hooks l 2und überprüfen Sie, ob Nullen angezeigt werden.

John
quelle
1. (Ich habe bereits in einem Kommentar darauf geantwortet, aber die ursprüngliche Frage nicht aktualisiert, daher war dies nicht offensichtlich. Ich entschuldige mich.) Der Absturzcode enthält 0x2B Byte in der Funktion (fast direkt nach dem Prolog). Ich habe die gesamte Funktion demontiert und es gibt keine Sprünge so früh zurück; Diese Anweisung tritt vor den Schleifen im Funktionskörper auf. Ein Sprung von außerhalb der Funktion ist natürlich möglich, aber schwer mit dem Stapel zu vereinbaren. 2. dps SQLite_Interop!sqlite3Hooks l 2zeigt 00000000 00000000.
Bradley Grainger
Ich habe auch einen Daten-Haltepunkt ( ba w 4 SQLite_Interop!sqlite3Hooks) festgelegt und vor dem Absturz wurden keine Schreibvorgänge an diese Adresse ausgeführt.
Bradley Grainger