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 pcache1Fetch
ausfü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 eax
0 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, je
nicht springt und die App beim Ausführen abstürzt call eax (00000000)
.
Update : eax
sollte hier immer 0 sein, da sqlite3Hooks.xBenignBegin
dies in unserem Build des Codes nicht festgelegt ist. Ich könnte SQLite mit SQLITE_OMIT_BUILTIN_TEST
defined 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. .time
meldet 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, eax
den 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, eax
und call eax
Aussage) und scheint auch das Problem zu vermeiden.
Das seltsamste Ergebnis, das ich bisher gefunden habe, ist, dass das Programm durch Ändern der jne
at 719f9fa0
auf zwei nop
Anweisungen (so dass die Steuerung immer auf die test eax, eax
Anweisung fällt , unabhängig vom Wert von createFlag
/ esi
) ohne Absturz ausgeführt werden kann.
test
Setzen falscher Flags bei internen QS-Tests nicht erfasst wird. Zumal diesetest then jump
Operation eine sehr häufige Compileroptimierung zu sein scheint, die in einer Vielzahl von Programmen verwendet wird.Antworten:
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:
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
quelle
Ich bin etwas besorgt, dass der generierte CodeEgal: Diese Anweisungen gelten fürif (wsdHooks.xBenignBegin)
nicht sehr allgemein ist. Er geht davon aus dem einzig wahren Wert ist ,1
während es wirklich testen soll jeden Wert ungleich Null. Trotzdem ist MSVC manchmal so verwirrend. Es ist wahrscheinlich nichts.C
Code, der nicht angezeigt wird.Da das eflag-
Z
Bit klar undEAX
Null ist, ist der Code durch Ausführen des Befehls nicht hierher gekommenEs muss ein Sprung von irgendwo anders zur folgenden Anweisung (
719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d
) oder sogar zurcall
Anweisung 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
JE
Befehls) 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],al
die normalerweise nicht bemerkt werden.Ich gehe davon aus, dass ein Haltepunkt in der
test
Anweisung 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.quelle
test
Setzt ZF nur, wenn eseax & eax
gleich Null ist, so dass es mit dem folgenden ziemlich sicher istje
.if( createFlag==1 ) sqlite3BeginBenignMalloc();
nicht der Fall istif (wsdHooks.xBenignBegin)
(siehe den Kommentar des OP zursqlite3BeginBenignMalloc
Inline)jne
vor demtest
durchnop
Anweisungen den Absturz scheinbar verhindert. (100% reproduzierbar ohne diese Änderung, 0% reproduzierbar an einem Testtag damit.) Wenn eine andere Anweisung in die Mitteje
oder direkt in die springtcall
, ist sie von dieser Änderung nicht betroffen. Wie erklärt die Theorie, dass ein anderer Code zu dem springt,je
oder diecall
, dass dies nur auf Llano-APUs geschieht?Versuchen Sie, die wahrscheinlicheren Ursachen auszuschließen, bevor Sie die Möglichkeit eines CPU-Fehlers in Betracht ziehen
Ein anderer Codepfad zur Aufrufanweisung. Verwenden Sie den
uf
Befehl, um die Funktion zu zerlegen und nach anderen Sprüngen / Verzweigungen zur Aufrufanweisung zu suchenVon der Hook-Funktion auf 0 springen / aufrufen.
dps SQLite_Interop!sqlite3Hooks l 2
und überprüfen Sie, ob Nullen angezeigt werden.quelle
dps SQLite_Interop!sqlite3Hooks l 2
zeigt00000000 00000000
.ba w 4 SQLite_Interop!sqlite3Hooks
) festgelegt und vor dem Absturz wurden keine Schreibvorgänge an diese Adresse ausgeführt.