Ma société a commencé à recevoir un certain nombre d'appels de clients parce que notre programme se bloque avec une violation d'accès sur leurs systèmes.
Le crash se produit dans SQLite 3.6.23.1, que nous fournissons dans le cadre de notre application. (Nous fournissons une version personnalisée, afin d'utiliser les mêmes bibliothèques VC++ que le reste de l'application, mais il s'agit du code SQLite standard).
Le crash se produit lorsque pcache1Fetch
exécute call 00000000
comme le montre le callstack de 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 ligne de code C correspondante est la suivante
if( createFlag==1 ) sqlite3BeginBenignMalloc();
Le compilateur met en ligne sqlite3BeginBenignMalloc
qui est défini comme suit :
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();
}
}
Et l'assemblage pour cela est :
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]
Les registres sont :
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
est 0 (ce qui est le cas), l'indicateur zéro doit être activé par test eax, eax
mais il n'est pas nul. Parce que l'indicateur zéro n'est pas activé, je
ne saute pas, et ensuite l'application se plante en essayant d'exécuter call eax (00000000)
.
Mise à jour : eax
devrait toujours être 0 ici car sqlite3Hooks.xBenignBegin
n'est pas défini dans notre version du code. Je pourrais reconstruire SQLite avec SQLITE_OMIT_BUILTIN_TEST
défini, ce qui permettrait d'activer #define sqlite3BeginBenignMalloc()
dans le code et omettre entièrement ce chemin de code. Cela pourrait résoudre le problème, mais cela ne semble pas être une "vraie" solution ; qu'est-ce qui empêcherait le problème de se produire dans un autre chemin de code ?
Jusqu'à présent, le facteur commun est que tous les clients utilisent "Windows 7 Home Premium 64-bit (6.1, Build 7601) Service Pack 1" et ont l'un des processeurs suivants (selon DxDiag) :
- AMD A6-3400M APU avec Radeon(tm) HD Graphics (4 CPUs), ~1.4GHz
- APU AMD A8-3500M avec Radeon(tm) HD Graphics (4 CPUs), ~1.5GHz
- AMD A8-3850 APU avec Radeon(tm) HD Graphics (4 CPUs), ~2.9GHz
Selon le site Wikipedia Article sur l'AMD Fusion Il s'agit de puces AMD Fusion de modèle "Llano", basées sur le noyau K10, qui ont été commercialisées en juin 2011, date à laquelle nous avons commencé à recevoir des rapports.
Le système du client le plus courant est le Toshiba Satellite L775D, mais nous avons également des rapports de crash provenant de systèmes HP Pavilion dv6 & dv7 et Gateway.
Ce crash pourrait-il être causé par une erreur du CPU (voir Errata pour les processeurs AMD Family 12h ), ou y a-t-il une autre explication possible que je ne vois pas ? pourrait être l'overclocking mais il est étrange que seul ce modèle spécifique de CPU soit affecté, si c'est le cas).
Honnêtement, il ne semble pas possible qu'il s'agisse vraiment d'une erreur du processeur ou du système d'exploitation, car les clients n'obtiennent pas d'écrans bleus ou de plantages dans d'autres applications. Il doit y avoir une autre explication, plus probable, mais laquelle ?
Mise à jour du 15 août : J'ai acquis un ordinateur portable Toshiba L745D avec un processeur AMD A6-3400M et je peux reproduire le crash de façon constante lors de l'exécution du programme. Le crash se produit toujours sur la même instruction ; .time
rapporte entre 1m30s et 7m de temps d'utilisation avant le crash. Un fait (qui peut être pertinent pour le problème) que j'ai négligé de mentionner dans le message d'origine est que l'application est multithread et a une utilisation élevée du CPU et des E/S. L'application génère quatre threads par défaut et affiche une utilisation du CPU de 80+% (il y a un certain blocage pour les E/S ainsi que pour les mutex dans le code SQLite) jusqu'à ce qu'elle se plante. J'ai modifié l'application pour n'utiliser que deux threads, et elle s'est quand même plantée (bien que cela ait pris plus de temps). Je fais maintenant un test avec un seul thread, et il n'a pas encore planté.
Notez également qu'il ne semble pas s'agir uniquement d'un problème de charge CPU ; je peux exécuter Prime95 sans erreur sur le système et il fera monter la température du CPU à >70°C, alors que mon application fait à peine dépasser la température de 50°C pendant qu'elle fonctionne.
Mise à jour du 16 août : En perturbant légèrement les instructions, le problème "disparaît". Par exemple, en remplaçant la charge mémoire ( mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]
) avec xor eax, eax
empêche le crash. En modifiant le code C original pour ajouter une vérification supplémentaire à la fonction if( createFlag==1 )
modifie les décalages relatifs de divers sauts dans le code compilé (ainsi que l'emplacement de la balise test eax, eax
y call eax
) et semble également prévenir le problème.
Le résultat le plus étrange que j'ai trouvé jusqu'à présent est que le fait de changer le jne
à l'adresse 719f9fa0
à deux nop
(afin que le contrôle toujours tombe sur le test eax, eax
quelle que soit la valeur de l'instruction createFlag
/ esi
est) permet au programme de fonctionner sans planter.