[Assembly] Ist das "sollte nicht passieren" einen AMD Fusion CPU Bug zum Absturz bringen?



Answers

Ich bin ein wenig besorgt, dass der für if (wsdHooks.xBenignBegin) generierte Code nicht sehr allgemein ist. Es nimmt an, dass der einzige wahre Wert 1 während es wirklich für irgendeinen Nicht-Null-Wert testen sollte. Dennoch ist MSVC manchmal auf diese Weise verblüffend. Es ist wahrscheinlich nichts. Egal: Diese Anweisungen sind für C Code nicht dargestellt.

Wenn das Eflag- Z Bit gelöscht ist und EAX Null ist, ist der Code durch Ausführen des Befehls nicht angekommen

719f9fa7    test    eax,eax

Es muss ein Sprung von einer anderen Stelle zu der folgenden Anweisung ( 719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d ) oder sogar die 719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d selbst sein.

Eine weitere Komplikation ist, dass es bei der x86-Familie üblich ist, dass ein ungültiges Sprungziel (wie das zweite Byte des JE Befehls) für einige Befehle unbeirrt (keine Fehler) ausgeführt wird und oft schließlich die richtige Befehlsausrichtung erreicht. Anders gesagt, Sie suchen vielleicht nicht nach einem Sprung zum Anfang irgendeiner dieser Anweisungen: Ein Sprung könnte mitten in ihren Bytes sein, was zur Ausführung von unauffälligen Operationen wie add [al+ebp],al das nicht add [al+ebp],al auffallen.

Ich sage voraus, dass ein Haltepunkt bei der Testanweisung für die Ausnahme nicht getroffen wird. Der einzige Weg, um solche Ursachen zu finden, ist, entweder sehr glücklich zu sein, oder alles zu verdächtigen und sie unschuldig eins nach dem anderen zu beweisen.

Question

Meine Firma hat begonnen, eine Reihe von Kunden anzurufen, weil unser Programm mit einer Zugriffsverletzung auf ihren Systemen abstürzt.

Der Absturz geschieht in SQLite 3.6.23.1, das wir als Teil unserer Anwendung liefern. (Wir liefern einen benutzerdefinierten Build, um die gleichen VC ++ - Bibliotheken zu verwenden wie der Rest der App, aber der SQLite-Standardcode.)

Der Absturz geschieht, wenn pcache1Fetch call 00000000 ausführt, wie der WinDbg-Aufrufstack zeigt:

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 ist:

if( createFlag==1 ) sqlite3BeginBenignMalloc();

Der Compiler 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 durch test eax, eax , aber es ist nicht Null. Da das Null-Flag nicht gesetzt ist, wird nicht gesprungen, und dann stürzt die App ab, indem sie versucht, den call eax (00000000) auszuführen.

Update : eax sollte hier immer 0 sein, da sqlite3Hooks.xBenignBegin nicht in unserem Build des Codes gesetzt ist. Ich konnte SQLite mit definiertem SQLITE_OMIT_BUILTIN_TEST neu SQLITE_OMIT_BUILTIN_TEST , was #define sqlite3BeginBenignMalloc() im Code #define sqlite3BeginBenignMalloc() und diesen #define sqlite3BeginBenignMalloc() vollständig 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 Code-Pfad passieren?

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

  • 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 handelt es sich hierbei um "Llano" -Modell-AMD-Fusion-Chips, die auf dem K10-Kern basieren und im Juni 2011 veröffentlicht wurden, als wir erstmals mit dem Erstellen von Berichten begannen.

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

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

Ehrlich gesagt scheint es nicht möglich zu sein, dass es sich wirklich um einen CPU- oder Betriebssystemfehler handelt, weil die Kunden in anderen Anwendungen keine Bluescreens oder Abstürze bekommen. 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 auf derselben Anweisung; .time meldet irgendwo zwischen 1m30s und 7m 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 Multithread ist und sowohl eine hohe CPU- als auch I / O-Nutzung aufweist. Die Anwendung erzeugt standardmäßig vier Worker-Threads und postet 80 +% CPU-Auslastung (es gibt eine Blockierung für I / O sowie für Mutexe im SQLite-Code), bis sie abstürzt. Ich änderte die Anwendung, um nur zwei Threads zu verwenden, und es stürzte immer noch ab (obwohl es länger dauerte). Ich mache jetzt einen Test mit nur einem Thread, und es ist noch nicht abgestürzt.

Beachten Sie auch, dass es sich nicht um ein reines CPU-Auslastungsproblem handelt. Ich kann Prime95 ohne Fehler auf dem System laufen lassen und es wird die CPU-Temperatur auf> 70 ° C erhöhen, während meine Anwendung kaum die Temperatur über 50 ° C erreicht, während sie läuft.

Update 16. August: Wenn Sie die Anweisungen leicht stören, wird das Problem "weggehen". Für eaxmple, die Speicherlast ( mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)] ) durch xor eax, eax mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)] xor eax, eax verhindert xor eax, eax den Absturz. Das Ändern des ursprünglichen C-Codes zum Hinzufügen einer zusätzlichen Überprüfung zur if( createFlag==1 ) ändert die relativen Offsets verschiedener Sprünge im kompilierten Code (sowie die Position der Anweisungen test eax, eax und call eax ) und auch scheint das Problem zu verhindern.

Das seltsamste Ergebnis, das ich bis jetzt gefunden habe, ist, dass das Ändern der jne bei 719f9fa0 zu zwei nop Anweisungen (so dass die Kontrolle immer auf den test eax, eax fällt test eax, eax Anweisung, egal was der Wert von createFlag / esi ist) das Programm zulässt Lauf ohne zu stürzen.