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.