assembly - ¿Esto "no debería suceder" bloquea un error de AMD Fusion CPU?
crash x86 (3)
Mi compañía ha comenzado a llamar a varios clientes porque nuestro programa se bloquea con una infracción de acceso en sus sistemas.
El bloqueo ocurre en SQLite 3.6.23.1, que enviamos como parte de nuestra aplicación. (Enviamos una compilación personalizada, para utilizar las mismas bibliotecas de VC ++ que el resto de la aplicación, pero es el código SQLite estándar).
El bloqueo ocurre cuando pcache1Fetch
ejecuta la call 00000000
, como se muestra en la pila de llamadas WinDbg:
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]
La línea relevante del código C es:
if( createFlag==1 ) sqlite3BeginBenignMalloc();
El compilador enlista sqlite3BeginBenignMalloc
, que se define como:
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();
}
}
Y la asamblea para esto es:
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]
Los registros son:
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
Si eax
es 0 (lo cual es), el indicador de cero debe establecerse mediante test eax, eax
, pero no es cero. Como el indicador de cero no está establecido, je
no salta y la aplicación se bloquea al intentar ejecutar la call eax (00000000)
.
Actualización : eax
siempre debe ser 0 aquí porque sqlite3Hooks.xBenignBegin
no está establecido en nuestra compilación del código. Pude reconstruir SQLite con SQLITE_OMIT_BUILTIN_TEST
definido, que #define sqlite3BeginBenignMalloc()
en el código y omitiría esta ruta de código por completo. Eso puede resolver el problema, pero no se siente como una solución "real"; ¿Qué podría evitar que suceda en alguna otra ruta de código?
Hasta ahora, el factor común es que todos los clientes ejecutan "Windows 7 Home Premium 64-bit (6.1, Build 7601) Service Pack 1" y tienen una de las siguientes CPU (según DxDiag):
- APU AMD A6-3400M con gráficos Radeon (tm) HD (4 CPU), ~ 1.4GHz
- APU AMD A8-3500M con gráficos Radeon (tm) HD (4 CPU), ~ 1.5GHz
- APU AMD A8-3850 con gráficos Radeon (tm) HD (4 CPU), ~ 2.9GHz
Según el artículo AMD Fusion de Wikipedia , estos son todos los chips AMD Fusion modelo "Llano" basados en el núcleo K10 y fueron lanzados en junio de 2011, que es cuando comenzamos a recibir informes.
El sistema de cliente más común es el Toshiba Satellite L775D, pero también tenemos informes de fallas de los sistemas HP Pavilion dv6 y dv7 y Gateway.
¿Podría este bloqueo ser causado por un error de CPU (ver Errata para procesadores AMD Familia 12h ), o hay alguna otra explicación posible que estoy pasando por alto? (Según Raymond, podría ser un overclocking , pero es extraño que solo este modelo específico de CPU se vea afectado, si es así).
Honestamente, no parece posible que sea realmente un error de CPU o SO, porque los clientes no reciben bluescreens o fallas en otras aplicaciones. Debe haber alguna otra explicación más probable, ¿pero qué?
Actualización 15 de agosto: Adquirí un portátil Toshiba L745D con un procesador AMD A6-3400M y puedo reproducir el bloqueo constantemente cuando ejecuto el programa. El bloqueo siempre está en la misma instrucción; .time
informa entre 1m30s y 7m de tiempo del usuario antes del accidente. Un hecho (que puede ser pertinente para el problema) que no mencioné en la publicación original es que la aplicación tiene varios subprocesos y tiene un alto uso de CPU y E / S. La aplicación genera cuatro subprocesos de trabajo de forma predeterminada y publica 80 +% de uso de CPU (hay algo de bloqueo para E / S así como para mutexes en el código de SQLite) hasta que se bloquea. Modifiqué la aplicación para que solo usara dos hilos y aún se colgó (aunque tardó más en suceder). Ahora estoy ejecutando una prueba con solo un hilo, y aún no se ha bloqueado.
Tenga en cuenta también que no parece ser un problema de carga de la CPU; Puedo ejecutar Prime95 sin errores en el sistema y aumentará la temperatura de la CPU a> 70 ° C, mientras que mi aplicación apenas alcanza la temperatura por encima de los 50 ° C mientras está en funcionamiento.
Actualización 16 de agosto: Perturbar las instrucciones hace que el problema "desaparezca". Por ejemplo, reemplazando la carga de memoria ( mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]
) con xor eax, eax
evita el bloqueo. La modificación del código C original para agregar una verificación adicional a la if( createFlag==1 )
cambia las compensaciones relativas de varios saltos en el código compilado (así como también la ubicación de las declaraciones test eax, eax
y call eax
) y también parece prevenir el problema
El resultado más extraño que he encontrado hasta ahora es que cambiar la jne
en 719f9fa0
a dos instrucciones de nop
(para que el control siempre caiga en la test eax, eax
instruction, sin importar el valor de createFlag
/ esi
) permite que el programa corre sin chocar
Antes de considerar la posibilidad de una falla en la CPU, trate de descartar las causas más probables
Una ruta de código diferente a la instrucción de llamada. Utilice el comando
uf
para desmontar la función y buscar otros saltos / ramas a la instrucción de llamadaSalta / llama a 0 desde la función de gancho.
dps SQLite_Interop!sqlite3Hooks l 2
y verifica que muestra nulos.
Hablé con un ingeniero de AMD en la conferencia Microsoft Build sobre este error y le mostré mi repro. Él me envió un correo electrónico esta mañana:
Hemos investigado y encontrado que esto se debe a una errata conocida en la familia APU de Llano. Se puede solucionar mediante una actualización del BIOS en función del OEM: si es posible, recomiéndelo a sus clientes (aunque tenga una solución alternativa).
En caso de que le interese, la errata es 665 en la Guía de revisión de Family 12h (consulte la página 45): http://support.amd.com/TechDocs/44739_12h_Rev_Gd.pdf#page=45
Aquí está la descripción de esa errata:
665 Instrucción de división de enteros puede provocar un comportamiento impredecible
Descripción
Bajo un conjunto altamente específico y detallado de condiciones de temporización internas, el núcleo del procesador puede abortar una instrucción especulativa dividir enteros DIV o IDIV (debido a que la ejecución especulativa se redirige, por ejemplo debido a una rama mal predicha) pero puede bloquearse o completar prematuramente la primera instrucción del camino no especulativo.
Efecto potencial en el sistema
Comportamiento impredecible del sistema, que generalmente da como resultado un bloqueo del sistema.
Solución sugerida
El BIOS debe establecer MSRC001_1029 [31].
Esta solución alternativa altera la latencia de instrucciones DIV / IDIV especificada en la Guía de optimización de software para procesadores AMD Familia 10h y 12h , pedido n.º 40546. Con esta solución aplicada, la latencia DIV / IDIV para procesadores AMD Familia 12h es similar a la latencia DIV / IDIV para los procesadores AMD Family 10h.
Arreglar planeado
No
Estoy un poco preocupado de que el código generado para if (wsdHooks.xBenignBegin)
no sea muy general.Asume que el único valor verdadero es 1
mientras que realmente debería estar probando para cualquier valor distinto de cero.Aún así, MSVC a veces desconcierta de esa manera. Probablemente no sea nada. No importa: estas instrucciones son para el código C
no presentado.
Dado que el bit de eflag Z
es claro y EAX
es cero, el código no llegó aquí al ejecutar la instrucción
719f9fa7 test eax,eax
Debe haber un salto desde otro lugar a la siguiente instrucción ( 719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d
) o incluso a la instrucción de la call
sí.
Otra complicación es que con la familia x86, es común que un objetivo de salto no válido (como el segundo byte de la instrucción JE
) ejecute sin perturbaciones (sin fallas) para bastantes instrucciones, a menudo volviendo a la alineación de instrucción adecuada. Dicho de otra manera, puede que no esté buscando un salto al comienzo de cualquiera de estas instrucciones: un salto podría estar en medio de sus bytes, lo que da como resultado la ejecución de operaciones poco destacables como add [al+ebp],al
que no tienden a notificado.
Predigo que un punto de interrupción en la instrucción de test
no se verá afectado por la excepción. La única forma de encontrar tales causas es tener mucha suerte o sospechar todo y demostrar que son inocentes uno a uno.