Le test savait faire planter Word 97. La panne restait sporadique, mais le script la ramenait assez souvent pour qu’une équipe en fin de release n’ait aucune envie de jouer à la roulette avec ses utilisateurs.

Puis quelqu’un lançait le débogueur.

Plus de crash.

Raymond Chen raconte cette histoire près de trente ans plus tard à partir du souvenir d’un collègue. Le bug touchait un chemin de code assez courant et avait été classé ship-stopping : suffisamment grave pour empêcher la sortie si l’équipe ne comprenait pas ce qui se passait. Le laboratoire avait même un script capable de provoquer le plantage avec une assez bonne fiabilité.1

Le problème était donc reproductible jusqu’au moment précis où l’on ajoutait l’outil censé montrer ce qui cassait.

Chen ne donne pas la raison exacte de cette disparition. C’est important de le garder ainsi. Un débogueur peut modifier beaucoup de choses, depuis l’implantation de breakpoints jusqu’au timing ou à l’état du processus, mais le billet ne dit pas laquelle jouait ici. L’histoire devient plus intéressante si on ne remplit pas le trou à sa place.

Un autre observateur

Une idée envisagée paraît aujourd’hui presque excessive : sortir un ICE, un in-circuit emulator.1

Dans le récit de Chen, ce matériel remplace physiquement le CPU de la machine cible. Un câble rejoint le socket du processeur et l’émulateur reproduit les signaux électriques qu’aurait générés le vrai composant. L’équipe peut alors inspecter mémoire et événements depuis l’extérieur du système qu’elle essaie de comprendre.1

L’idée avait une logique presque physique : puisque l’observation logicielle changeait le résultat, il fallait tenter d’observer la machine sans lui injecter le même environnement de debug.

L’ICE ne sera finalement pas le héros de l’histoire.

Les développeurs remarquent quelque chose de beaucoup plus banal dans le parc de machines qui plantent : la plupart viennent du même fabricant, et elles ont été construites avant une certaine date. Une fois ce regroupement visible, ils trouvent un erratum CPU compatible avec la période de fabrication des systèmes touchés.1

Chen ne nomme ni le fabricant ni le modèle de processeur. Il ne donne pas non plus le motif exact d’instructions concerné. Le bon niveau de détail s’arrête donc ici : un erratum matériel connu pouvait être déclenché par une séquence de code particulière, sous des conditions précises.

Cela change complètement la nature du bug. Le code de Word pouvait être valide du point de vue du jeu d’instructions et tout de même provoquer un résultat incorrect sur certaines révisions de processeur.

Cent cinquante motifs

Le compilateur utilisé par l’équipe avait déjà reçu une mise à jour capable d’éviter la séquence dangereuse.1 La correction apparemment propre était donc disponible : reconstruire Word avec ce compilateur et laisser son générateur de code faire le travail.

L’équipe choisit de ne pas le faire.

À la place, elle écrit un outil qui parcourt les binaires existants et cherche la séquence susceptible de déclencher l’erratum. Il en trouve environ 150 occurrences.1

Ce nombre est intéressant parce qu’une reprise secondaire de l’histoire peut facilement le faire disparaître. The Register résume le passage en disant, en substance, qu’une occurrence fut trouvée.5 Le billet primaire est plus précis : il y avait environ 150 motifs candidats.

Le processeur demandait cependant une condition supplémentaire. La séquence devait traverser une frontière de page mémoire. Parmi les quelque 150 occurrences, une seule satisfaisait cette condition.1

Et c’est celle que le script de test avait réussi à trouver dans le monde réel.

Une frontière de page est exactement le genre de détail qui transforme un bug de laboratoire en fantôme. Deux copies du même motif machine peuvent être inoffensives ou problématiques selon leur adresse. Le code ne porte donc pas, à lui seul, toute la condition du crash : son placement en mémoire compte aussi.

La rareté venait donc moins du motif lui-même que de son adresse. Cent cinquante candidats dans le binaire, un seul placé de façon à réunir toutes les conditions.

Ne pas recompiler

Pourquoi refuser le nouveau compilateur si celui-ci savait déjà éviter l’erratum ? Parce que Word 97 était arrivé dans la partie du cycle où un changement propre peut être plus dangereux qu’un patch laid.

Chen utilise le terme Microsoft escrow. Dans un autre billet de 2026, il l’explique comme un build considéré comme celui qui sera livré s’il atteint les objectifs de qualité et de fiabilité. Sa traduction pratique est presque administrative : c’est fini, on ne touche plus à rien sauf vraie urgence.2

Changer de compilateur à ce moment-là ne modifie pas seulement la séquence fautive. Il peut régénérer une grande partie du code machine.

Le risque n’est même pas nécessairement que le nouveau compilateur contienne un bug. Chen donne un exemple plus désagréable : un bug préexistant dans le code source peut rester masqué par l’ancienne disposition des variables locales. Un autre compilateur change cette disposition, et une variable non initialisée qui semblait toujours contenir une valeur acceptable reçoit soudain zéro.1

Le nouveau compilateur pourrait alors être parfaitement correct et tout de même faire apparaître un vieux défaut. Il suffit qu’il change l’endroit où les données atterrissent en mémoire.

Cette méfiance n’est pas une coquetterie historique. Chen raconte ailleurs un bug Windows 3.1 où une correction minuscule ajoute une variable globale. Ce simple ajout déplace les autres variables en mémoire et révèle un vieux bug de corruption qui écrivait jusque-là, par chance, sur une valeur déjà nulle.4

À la fin d’un produit, la taille du diff source mesure mal la taille réelle du changement.

Un octet inutile

Le choix final tient dans une instruction : nop.

NOP signifie no operation. Le processeur avance, mais l’instruction n’est censée produire aucun effet fonctionnel utile. Ici, l’équipe l’insère dans le motif fautif afin que la séquence qui déclenche l’erratum ne se présente plus telle quelle au CPU.1

Ce n’est pas un correctif du processeur. Ce n’est pas davantage une réparation conceptuelle du code source de Word. C’est un contournement ciblé d’une condition matérielle connue.

Chen a raconté en 2011 une autre histoire où ce genre de chirurgie était courant, cette fois à propos d’anciennes révisions B1 du 80386 et de Windows 95.3 Plusieurs errata de ces puces dépendaient de l’enchaînement exact de deux types d’instructions. Insérer un NOP entre elles suffisait parfois à « casser » la combinaison dangereuse. Dans certains cas, même le NOP ordinaire appartenait à une catégorie gênante et il fallait utiliser une variante préfixée.3

Ce billet de 2011 n’identifie pas le processeur de l’incident Word 97. Ce sont deux histoires différentes. Il fournit simplement un précédent concret : sur x86, un NOP peut servir de cale mécanique dans le flux d’instructions, non parce qu’il accomplit quelque chose mais parce qu’il empêche deux choses de se retrouver dans la mauvaise configuration.

Le patch Word 97 reprend exactement cette philosophie de mitigation minimale.

Le bug connu

Il y a quelque chose de contre-intuitif dans cette décision. L’équipe connaît désormais un nouveau compilateur qui sait produire du code plus sûr face à cet erratum. Elle choisit tout de même de garder l’ancien outil et de modifier directement le binaire.

Au début d’un projet, patcher directement un binaire pour éviter un bug CPU mérite au minimum quelques sourcils levés. À quelques mètres de la ligne d’arrivée, le calcul est différent. Le produit entier a déjà accumulé des semaines ou des mois de tests sur une combinaison précise de sources, compilateur, options et binaires. Changer cette combinaison jette une partie de cette connaissance empirique.

Le NOP, lui, introduit un risque très localisé. L’équipe peut vérifier que l’unique occurrence à cheval sur la page ne déclenche plus le bug et que le scénario de test cesse de planter, sans demander à tout Word 97 de redevenir un produit neuf.

C’est la leçon que Chen répète dans ses histoires de fin de cycle : plus la sortie approche, plus la vitesse normale du développement doit ralentir.4

On continue à corriger les bugs graves. Mais chaque correction doit payer pour la quantité de monde qu’elle déplace autour d’elle.

Le fantôme reste

Il reste un morceau non résolu, et c’est très bien ainsi : pourquoi le débogueur faisait-il disparaître ce crash précis ?

Le récit publié ne le dit pas.1 Peut-être que l’équipe de l’époque le savait, peut-être que ce détail s’est perdu, peut-être qu’il n’était simplement pas nécessaire une fois l’erratum identifié. Nous n’avons pas à choisir à sa place.

Ce que l’histoire établit suffit déjà.

Un crash logiciel menait vers un défaut matériel. Le motif dangereux existait environ 150 fois mais une seule copie se trouvait au mauvais endroit. Un compilateur plus récent savait éviter le problème, mais le changer aurait élargi le risque à tout le produit. Une instruction qui ne fait rien a donc constitué le correctif le plus prudent.

Le correctif tient donc dans une instruction conçue pour ne rien faire. Pour une fois, l’absence d’effet était exactement la fonctionnalité recherchée.