Commençons par raconter un peu ma vie. Vendredi j'ai enfin, et avec immense soulagement, soumis pour publication un article sur lequel mon co-auteur et moi travaillions depuis maintenant environ un an et demi et qui fait finalement 80 pages (si j'ai le courage, j'essaierai de vulgariser un peu le contenu dans ce blog, mais en fait je crois que j'ai surtout envie de penser à autre chose jusqu'à ce que le relecteur nous envoie des zillions de corrections). Ce week-end je suis allé chez mes parents pour me détendre un peu avant d'attaquer la montagne de choses que j'ai mises de côté entre autres pour avoir le temps de finir cet article. Bref.
Les choses ne se sont pas tout à fait passées de façon aussi détendante(?) que je l'avais espéré. D'abord il a plu toute la journée comme les larmes d'une veuve, ce qui n'aide jamais. Ensuite, il y a eu l'affaire mystérieuse d'un dongle wifi qui a disparu corps et âme et au sujet duquel Hercule Poirot enquête sans doute encore (j'avoue que j'avais déjà eu plein de problèmes avec le wifi, mais le coup de l'antenne qui disparaît physiquement, je n'avais pas encore vu). Mais surtout, il y a eu un bug de Firefox, qui semblait déjà étrange au début et qui est finalement devenu le bug le plus bizarre que j'aie jamais rencontré.
Il faut savoir que j'ai un PC chez mes parents (que
j'utilise principalement pour faire des backups) dont la config est
presque exactement identique à celle de mon PC chez moi —
justement pour tenter d'éviter les surprises de choses qui
fonctionneraient différemment d'un côté et de l'autre. La principale
différence est que celui chez mes parents a un système (GNU/Linux)
32-bits alors que celui chez moi est 64-bits (et encore le noyau
est-il 64-bits des deux côtés). Mais à part ça, j'essaie d'installer
presque toujours les mêmes packages, de faire les mises à jour en même
temps, etc., et mon $HOME
est synchronisé entre les deux
machines (justement pour servir de sauvegarde). Je compile aussi des
Firefox de la même manière sur les deux machines (juste l'un en
32-bits et l'autre en 64-bits), à partir des mêmes sources, et je
m'attends donc à ce qu'ils fonctionnent de la même manière.
Et là voilà, j'arrive chez mes parents, je lance mon Firefox, je veux faire une recherche Google (à cause d'un petit problème sur mon mobile, une merdouille qui ne vaut même pas la peine d'être racontée), et je tombe sur un bug bizarre : la barre d'adresse ne marche pas. Plus exactement, je peux taper des choses dedans, il propose des complétions, mais la touche entrée ne fait rien, pas plus que la petite flèche censée aller à l'adresse indiquée, ou même le fait de cliquer sur une complétion — bref, la barre est essentiellement inactive. À part ça, tout semble marcher normalement : la touche entrée marche parfaitement ailleurs dans le navigateur, je peux cliquer sur des liens dans des pages Web, bref, il n'y a en gros que la barre d'adresse qui dysfonctionne.
Évidemment, c'est le genre de symptôme qui peut avoir un million de causes, rien de ce que je peux trouver en ligne n'est susceptible de me concerner. Autant dire que le docteur House voit son patient commencer à faire de la fièvre, il est loin de se douter jusqu'où le lapin blanc va le mener.
Commence donc mon enquête pour essayer de comprendre ce qui se
passe. Est-ce peut-être mon profil Firefox qui est corrompu ou un
problème avec une extension ou un plugin que j'aurais ? Non, le bug
se reproduit encore avec un profil vierge. Mais de façon non
complètement reproductible : parfois il cesse d'exister sur un profil
donné, et il ne réapparaît alors pas, alors que sur mon profil normal,
il persiste. J'explore une forêt de fausses pistes. Je vérifie que
le problème se produit encore en lançant Firefox sur un affichage
distant (en export DISPLAY
, comme on dit en jargon Unix).
Je constate un message d'erreur suspect dans
la console
du navigateur ayant à voir avec l'initialisation des moteurs de
recherche (NS_ERROR_FAILURE: Failure'Failure' when calling
method: [nsIBrowserSearchService::currentEngine]
browser.js:11478
) — mais pourquoi celle-ci
échouerait-elle ?
Ma première hypothèse porte sur la compilation : on parle d'un
Firefox que j'ai compilé moi-même, ce que je fais régulièrement, mais
lorsque j'ai compilé celui-là, il y a eu une coupure de courant qui a
éteint la machine ; j'ai bien sûr vérifié les systèmes de fichiers,
effacé le sous-arbre de compilation et redémarré la compilation à
zéro, mais il est possible que le plantage ait eu lieu au moment où la
compilation générait un script Python qui sert par la suite, il est
possible que celui-ci ait été corrompu par le plantage (typiquement,
remplacé par des zéros, il semble que ça arrive sous le système de
fichiers xfs
), qu'il soit situé dans l'arbre de
compilation « permanent » (avec les sources, plutôt que dans l'arbre
des résultats de compilation que j'ai effacé pour recommencer à zéro :
Firefox fait ça quand il s'agit de scripts qui servent à la
compilation elle-même), qu'il soit utilisé pour générer un fichier
décrivant les moteurs de recherche et qu'à cause de ça ceux-ci ne
s'initialisent pas bien. L'hypothèse était raisonnable (je la
mentionne pour montrer le genre de fausses pistes que j'ai pu
explorer), mais j'ai vite vérifié qu'elle était fausse : un Firefox
officiel compilé par Mozilla présente les mêmes symptômes (pour toute
version entre la 30 « stable » et la 33 « nightly »).
Je me convaincs que le symptôme initial (la barre d'adresse qui ne marche pas) est bien lié à l'erreur signalée sur l'échec d'initialisation des moteurs de recherche, mais ça ne m'apprend toujours pas sa cause. Et surtout, pourquoi suis-je le seul à rencontrer ce problème ? (on se doute bien que, même en se limitant aux utilisateurs GNU/Linux avec un système 32-bits, l'ensemble des versions de Firefox entre 30 et 33 est pas mal testé : s'il y avait un bug qui le rendait quasi inutilisable, ça se saurait assez vite).
Dimanche soir, j'ai soumis un bug-report à Bugzilla
(#1034984,
mais n'allez pas voir tout de suite si vous ne voulez pas vous faire
spoiler la fin de l'histoire !). Heureusement, ils sont assez
réactifs et m'ont appris l'existence d'une
préférence browser.search.log
qui permet d'afficher des
informations de debug sur ce que fait le mécanisme de recherche. Et
là, je découvre qu'il essaye d'ouvrir un
fichier /usr/local/opt/firefox-30.0.MOZ/browser/searchplugins/ing.xml
qui n'existe pas : ce qui existe, c'est un
fichier /usr/local/opt/firefox-30.0.MOZ/browser/searchplugins/bing.xml
(décrivant à Firefox le fonctionnement du moteur de
recherche Bing). Voici qui fait
remonter la cause un peu plus loin dans le trou du lapin blanc, mais
le mystère n'en est que plus épais : pourquoi diable mon Firefox
aurait-il l'idée de chercher à ouvrir un fichier ing.xml
quand il devrait aller prendre bing.xml
, et toujours,
pourquoi serais-je le seul à rencontrer ce problème ?
En allant fouiller un peu
dans le
code source du mécanisme de gestion des moteurs de recherche, je
comprends un petit peu mieux pourquoi le bug n'était pas déterministe
(il y a deux mécanismes d'initialisation, un « synchrone » et un
« asynchrone », il est probable que l'utilisation de l'un ou de
l'autre ne soit pas déterministe, et c'est l'« asynchrone » qui
échoue). La cause est un peu mieux cernée : c'est
l'objet OS.File.DirectoryIterator
qui renvoie des noms
tronqués d'un caractère (du coup, ça donne ce ing.xml
,
qui n'existe pas et que Firefox n'arrive pas à ouvrir). Je constate
que c'est systématique — quel que soit le répertoire que je lui fais
lister, il manque toujours le premier caractère des noms de fichier.
Mais la raison profonde n'est pas plus claire pour un sou, ni pourquoi
je suis le seul concerné.
J'ai passé un temps fou à lancer Firefox dans un nombre incroyable
de combinaisons ordinateur×configuration, dans des machines virtuelles
ou sur des vrais ordinateurs, et à m'arracher les cheveux : j'ai beau
tenter d'utiliser une config absolument identique dans mes machines
virtuelles (jusqu'au noyau 64-bits pour un userland 32-bits ; et en
essayant de reprendre exactement les versions de toutes les
bibliothèques listées dans le /proc/$PID/maps
du Firefox
en cours d'exécution), impossible de reproduire le bug. Il n'y a
apparemment que sur mon PC chez mes parents qu'il se
produit !
Une nouvelle option de debug (toolkit.osfile.log
) me
montre que le bug vient de bien profondément dans Firefox : les
threads de travail voient bien passer des noms tronqués d'un
caractère. J'en ai appris beaucoup sur la façon de débugguer du
JavaScript (et comment débugguer le JavaScript de Firefox lui-même… ce
qui n'est malheureusement pas possible dans les threads de travail),
et sur la façon dont s'organise le source de ce machin (à ce stade-là,
les bouts incriminés étaient
principalement ici
et là),
mais toujours sans comprendre quelle pouvait être la cause profonde du
problème, ou de sa rareté.
Et puis, en regardant un commentaire dans le source,
// Structure |dirent| // Building this type is rather complicated, as its layout varies between // variants of Unix. For this reason, we rely on a number of constants // (computed in C from the C data structures) that give us the layout. // The structure we compute looks like // { int8_t[...] before_d_type; // ignored content // int8_t d_type ; // int8_t[...] before_d_name; // ignored content // char[...] d_name; // };
— j'ai eu une illumination (si le struct dirent
n'est
pas bien interprété, c'est qu'il y a une différence d'ABI
quelque part, or quelle pourrait bien être la raison de cette
différence d'ABI ?), et surtout, je me suis rendu compte
que j'aurais dû bondir plus tôt en consultant la liste des
bibliothèques chargées en mémoire par Firefox
(/proc/$PID/maps
) et en
voyant /lib/libc.so.5.4.46
y apparaître.
Le problème, c'est la libc5. Ceux qui connaissent bien Linux auront compris de quoi je veux parler, mais pour les autres, une explication s'impose : la libc, ou bibliothèque C du système, c'est la bibliothèque qui contient les fonctions standard du langage C, et qui sert, de fait, sur un système Unix, d'interface entre le noyau et les programmes utilisateurs (même s'ils ne sont pas écrits en C, dans essentiellement tous les cas, ils s'appuient sur la bibliothèque C). La bibliothèque C de GNU/Linux a une histoire un peu compliquée, mais disons qu'entre environ 1995 et 1999, la version standard de la bibliothèque C de GNU/Linux était appelée libc5, et qu'entre 1997 et 2000 (ça a pris du temps !) s'est effectué une migration vers une nouvelle version, profondément incompatible, appelée libc6 — c'est celle qui est encore utilisée maintenant (il y a eu beaucoup d'évolutions depuis, mais aucune ne marquant un changement incompatible). La libc5 a continué à être un peu maintenue jusque vers 2003, et depuis 2003 on peut la considérer comme complètement obsolète (et elle n'existe carrément pas sur les systèmes 64-bits : il n'y a que sur les 32-bits qu'il y a même la possibilité d'avoir une libc5). Comme je suis du genre conservateur (pour ne pas dire dinosaure), j'ai encore une libc5 fonctionnelle sur mes systèmes GNU/Linux 32-bits, ce qui n'est évidemment pas le cas de tous les gens qui installent des Ubuntu ou autres distributions récentes (et a fortiori tous les systèmes 64-bits).
Et le bug, c'est que l'interface JavaScript de Firefox servant à
charger directement des fonctions de la libc (qui n'est elle-même pas
énormément utilisée, en fait : il y a toutes sortes d'autres
mécanismes d'appel possibles) va chercher assez stupidement n'importe
quelle bibliothèque qui s'appelle libc
qu'elle trouve, et
dans mon cas elle trouve et charge la libc5. Assez inexplicablement,
ça ne plante pas tout le navigateur de se retrouver avec deux versions
incompatibles de la libc (c'est bien sûr la version moderne, la libc6,
qui sert pour presque tout, et avec laquelle Firefox a été compilé),
mais dans l'appel à readdir
exécuté depuis ce code
JavaScript-là, c'est la version de la libc5 qui est utilisée. Or
l'interface d'appel diffère de celle de la libc6 (prévue à la
compilation) en ce que, dans la structure qui sert à retourner le
résultat, l'emplacement du nom de fichier a été décalé d'un octet
(j'imagine pour des raisons d'alignement) : du coup, tout était lu de
travers par juste un octet.
Et vérification faite, si je retire le
fichier /lib/libc.so.5.4.46
de mon système, mon Firefox
se remet à marcher normalement.
Tout de
même, la chaîne de causalité est assez impressionnante : la présence
de la libc5 et Firefox décidant stupidement de l'ouvrir fait qu'une
certaine interface JavaScript vers la libc ne fonctionne pas tout à
fait correctement, ce qui a comme conséquence que les noms de fichier
sont tronqués d'un caractère, et le seul effet détectable est que le
système de moteurs de recherche essaie d'ouvrir ing.xml
au lieu du bing.xml
qui se trouve dans le répertoire,
échoue, et du coup la barre d'adresse ne fonctionne plus ! Et seul
quelqu'un ayant encore une libc5 installée sur un système 32-bits
pouvait détecter le problème (et encore, il y a peut-être des
conditions supplémentaires pour que Firefox décide de la lire, et
encore d'autres pour que l'initialisation des moteurs de recherche
passe par le chemin asynchrone). Voilà le genre de bugs complètement
bizarres et obscurs qui aurait pu rester indétecté pendant
longtemps !
Il me semble que j'ai mérité une médaille (en chocolat).