68 votes

Est-ce que ce crash "qui ne devrait pas se produire" est un bug du CPU AMD Fusion ?

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.

27voto

Bradley Grainger Points 12126

J'ai parlé de cette erreur à un ingénieur d'AMD lors de la conférence Microsoft Build, et je lui ai montré ma reproduction. Il m'a envoyé un courriel ce matin :

Nous avons enquêté et trouvé que cela est dû à un errata connu dans la famille de la famille d'APU Llano. Il peut être corrigé par une mise à jour du BIOS en fonction de l'OEM. BIOS - si possible, recommandez-la à vos clients (même si vous avez clients (même si vous avez une solution de contournement).

Au cas où cela vous intéresserait, l'errata est 665 dans la Famille 12h. Revision Guide (voir page 45) : http://support.amd.com/TechDocs/44739_12h_Rev_Gd.pdf#page=45

Voici la description de cet erratum :

665 L'instruction Integer Divide peut provoquer un comportement imprévisible

Description

Dans le cadre d'un ensemble très spécifique et détaillé de conditions de synchronisation internes, le cœur du processeur peut interrompre une instruction spéculative de division d'entier DIV ou IDIV (en raison de la réorientation de l'exécution spéculative, par exemple à cause d'un branchement mal prédit) mais peut suspendre ou achever prématurément la première instruction du chemin non spéculatif.

Effet potentiel sur le système

Comportement imprévisible du système, se traduisant généralement par un blocage du système.

Solution suggérée

Le BIOS doit définir MSRC001_1029[31].

Cette solution de contournement modifie la latence de l'instruction DIV/IDIV spécifiée dans l'instruction Guide d'optimisation logicielle pour les processeurs de la famille AMD 10h et 12h , commande n° 40546. Avec cette solution de contournement appliquée, la latence DIV/IDIV pour les processeurs AMD de la famille 12h est similaire à la latence DIV/IDIV pour les processeurs AMD de la famille 10h.

Correction prévue

Non

1voto

wallyk Points 33150

Je suis un peu inquiet que le code généré pour if (wsdHooks.xBenignBegin) n'est pas très général. Il suppose que la seule vraie valeur est 1 alors qu'il faudrait plutôt tester tout valeur non nulle. Pourtant, MSVC est parfois déconcertant de cette façon. Ce n'est probablement rien. Peu importe : ces instructions sont pour C code non présenté.

Étant donné que le eflag Z est clair et EAX est zéro, le code n'est pas arrivé ici en exécutant l'instruction

719f9fa7    test    eax,eax

Il doit y avoir un saut d'un autre endroit à l'instruction qui suit ( 719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d ) ou même le call l'instruction elle-même.

Une autre complication est qu'avec la famille x86, il est courant qu'une cible de saut non valide (comme le deuxième octet de la ligne JE ) s'exécute sans problème (pas d'erreur) pendant un certain nombre d'instructions, et finit souvent par retrouver l'alignement correct des instructions. En d'autres termes, il se peut que vous ne cherchiez pas un saut au début de l'une de ces instructions : un saut pourrait se trouver au milieu de leurs octets, résultant en l'exécution d'opérations banales telles que add [al+ebp],al qui ont tendance à ne pas être remarquées.

Je prédis qu'un point d'arrêt à la test ne sera pas touchée par l'exception. Les seules façons de trouver de telles causes sont soit d'être très chanceux, soit de tout suspecter et de prouver leur innocence une à une.

-1voto

John Points 3990

Avant d'envisager la possibilité d'un bug du CPU, essayez d'écarter les causes les plus probables

  1. Un chemin de code différent pour l'instruction d'appel. Utilisez l'instruction uf pour désassembler la fonction et rechercher d'autres sauts/branchements vers l'instruction d'appel.

  2. Saut / appel à 0 à partir de la fonction de crochet. dps SQLite_Interop!sqlite3Hooks l 2 et vérifiez qu'il affiche des nuls.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X