« It works on my satellite » ou l'histoire d'un bug dans l'espace

Posté par  (Mastodon) . Édité par L'intendant zonard, Florent Zara, cli345, Benoît Sibaud, Jona et dcp. Modéré par Benoît Sibaud. Licence CC By‑SA.
Étiquettes :
63
10
jan.
2026
Technologie

Cette dépêche raconte un vieux bug que j’ai eu sur un satellite. L’identification, la reproduction, la correction. C’est le bug qui m’a le plus intéressé/marqué dans ma carrière (jusqu’ici), C’est pourquoi cela pourrait aussi vous intéresser.

L’appel

Il y a bien longtemps, dans une galaxie lointaine. Ah non, pardon. Un long weekend de 14 juillet, sur une plage, je reçois un coup de fil : « Un des satellites a rebooté, à cause d’une erreur logicielle, est-ce que tu es disponible pour venir comprendre ce qu’il s’est passé ? A priori, il fonctionne toujours, mais il est passé tout seul sur le calculateur redondant. »

Quelques mois avant, on avait lancé une première grappe de six satellites ; d’autres lancements sont prévus pour compléter une constellation dans les mois/années à venir. Comme tout marche bien depuis des mois, personne de l’équipe logiciel de bord n’est d’astreinte. Sur ces satellites, j’étais surtout sur la partie validation. En gros, ce jour-là pour moi, ce n’était pas possible, mais j’y suis allé le lendemain, un samedi ou dimanche.

Sommaire

L’objectif et les moyens de débug

Si nos managers nous ont appelé, c’est parce quand un satellite bugue en prod (on va dire en vol, plutôt), c’est comme pour n’importe quel autre logiciel, des gens veulent des réponses à des questions comme :

  • pourquoi ?
  • est-ce que c’est grave ?
  • est-ce que ça va se reproduire ?
  • comment on corrige ?

Par contre, les moyens sont potentiellement différents de ce que vous avez dans d’autres environnements (ou pas, j’imagine que ça dépend des gens) Ce qu’on a :

  • le code
  • la doc
  • des bancs de tests (avec le même matériel pour le calculateur)
  • des gens
  • un tout petit peu de contexte logiciel sauvegardé au moment de l’erreur (j’y reviens)
  • la télémétrie avant l’anomalie (tout allait bien)
  • la télémétrie après l’anomalie (tout va bien, mais on est passé du mode matériel 2 au mode 3. En gros c’est le même, sauf qu’on utilise certains équipements “redondants” au lieu du “nominal”, dont le calculateur)

Premier élément, qui a mené au fait que c’est nous (du logiciel) qui avons été appelés, c’est que le matériel qui gère le mode (2 -> 3) peut changer de mode pour plusieurs raisons, mais il sait pourquoi il le fait. Et la raison c’est « le logiciel m’a dit de le faire ». Donc ça vient de nous.

L’analyse

Comme tout va bien, on va regarder le contexte sauvegardé. Ce n’est pas un core dump qu’on peut passer à gdb, mais ça contient quelques infos :

  • le code de l’erreur ILLEGAL CPU INSTRUCTION
  • le Program Counter %pc qui nous donne l’adresse de l’instruction exécutée au moment de l’erreur
  • l’adresse de la prochaine instruction à exécuter %npc (ici c’est l’adresse juste après %pc, rien de surprenant)
  • une copie des registres (bon, on ne va pas en avoir besoin, donc je ne vous fais pas un cours sur SPARC et ses registres tournant, de toute façon j’ai oublié. On pourrait probablement les utiliser pour récupérer partiellement la pile d’appel, on l’a surement fait)
  • la date et l’heure (super info utile. Enfin, ça correspond à notre anomalie, j’imagine que c’est pour ça qu’on l’avait)
  • surement d’autres choses, mais pas utiles pour la suite.

Problème résolu donc ? on est à l’adresse %pc, on l’exécute et le CPU nous dit que l’instruction n’est pas légale. Qu’est-ce qu’il y a ici ? Une instruction légale, quelle que soit la valeur des registres. Pareil pour un peu plus haut et un peu plus bas, rien qui provoque cette erreur. Que s’est-il passé ?

On est dans l’espace, donc l’explication facile (dès qu’on n’explique pas un truc) : l’instruction a dû avoir un Single Event Upset (SEU), un bit flip. Ça a transformé une instruction légale en instruction illégale. C’est simple ? Sauf que non, on est dans l’espace, en conséquence, on a tout un mécanisme de protection contre les SEU. C’est pas infaillible (par exemple si on a deux bits inversés, on ne peut pas corriger) mais ce n’est pas la bonne signature. Si c’était ça, ça dirait DOUBLE EDAC ERROR, pas ILLEGAL CPU INSTRUCTION.

Donc la cause de l’anomalie n’est pas un SEU.

EDAC / Protection contre les SEU

Je suis sûr que vous êtes intéressé, donc je vais vous décrire la protection contre les bit flips. C’est un mix de matériel/logiciel (en plus d’avoir une boite autour qui diminue la probabilité). En mémoire (RAM, ROM) pour 4 octets de données “utiles”, on consomme 5 octets. Le 5ᵉ octet contient un code de contrôle calculé à partir des 4 autres (EDAC). Si un bit change (sur les 5 × 8 = 40 bits), on peut non seulement le détecter mais aussi reconstruire la valeur correcte. Si deux bits changent (ou plus, mais il y a une limite), on peut détecter l’erreur mais pas la corriger (cf: le DOUBLE EDAC ERROR mentionné plus haut)

C’est complètement transparent vu du logiciel (code source, ou assembleur), tout ça est calculé par le matériel. Quand on écrit en mémoire 0x12345678 il calcule le code et écrit 0x12345678XY avec la bonne valeur de X et Y. Quand on lit, pareil, le matériel commence par lire 0x12345678XY, calcule la somme de contrôle sur les 4 octets, si c’est le bon, il nous donne 0x12345678.

Là où ça se complique, c’est quand il y a un changement. Disons qu’on a maintenant 0x02345678XY. (1 --> 0). Il se passe deux choses ici :

  1. le matériel dit au logiciel 0x12345678 (il corrige, mais uniquement la valeur envoyée au software. Pas la valeur enregistrée en mémoire)
  2. il émet un signal SINGLE EDAC ERROR.

C’est là que le logiciel intervient, dans le point 2. Ce signal est lié à une trap qui corrige la mémoire. Schématiquement c’est lié à une fonction qui ressemble à ceci (en assembleur SPARC en vrai, mais j’ai tout oublié)

; adresse vient du contexte, c’est l’adresse qui a été lue en dernier, qui a généré la trap
disable_edac_trap: ; Désactiver la trap. Sinon on déclencherait la trap depuis la trap
load [adresse], reg ; Lire 4 octets (lecture = correction auto)
enable_edac_trap: ;
store reg, [adresse] ; Réécrire la valeur corrigée

On lit la valeur, c’est corrigé vu du logiciel par le matériel, on réécrit la valeur, tout est corrigé.

Cette trappe peut être déclenchée par n’importe quelle instruction qui lit de la mémoire (ou par le fait de charger une instruction elle-même depuis la mémoire), et on a même une tâche de fond (plus basse priorité, qui tourne en permanence quand il reste du temps de calcul disponible) qui fait

// en gros. En vrai légèrement plus compliqué
void background_task(void) {
int address = MEMORY_START;
volatile int value;
while (1) {
value = *address; // s’il y a un bit flip en mémoire, ce sera corrigé par la trap
address += 4;
if (address >= MEMORY_END) {
address = MEMORY_START;
}
}
}

L’idée de cette fonction c’est de lire la mémoire régulièrement. Si on ne faisait pas ça, peut-être que certaines cases mémoires auraient deux bit flips, car pas corrigé après le premier si on ne lit pas la mémoire avant qu’un autre arrive. Ce n’est pas très fréquent d’avoir des bit flips, mais sur les 6 satellites, en cumulé, on en détecte quelques-uns par jour.

L’hypothèse

De retour à la case départ donc. On exécute apparemment l’instruction stockée dans %pc, valide. Et le CPU nous dit qu’elle est invalide, mais clairement, elle est valide. On tourne en rond, on est samedi ou dimanche, fin d’après midi, et le satellite, lui aussi il tourne en rond, sans problèmes. Tout à coup, quelqu’un a l’idée de dire « bon, on ne résoudra pas ça aujourd’hui. On se revoit lundi ? ». On rentre, je bois un verre avec mes colocs (enfin, je suppose. C’était une activité habituelle pour un weekend, ça, au moins)

Retour au bureau, et là (surement plus tard, pas lundi 9h) on a David (un collègue) qui propose : "Comme clairement %pc est valide, est qu’on exécute quelque chose d’invalide, est-ce qu’on est sûr qu’on a bien enregistré %pc?". On vérifie, le code qui fait ça a l’air correct. En plus le contexte général, ce qu’il y a dans les registres est correct. Toujours David "OK, le logiciel est correct, mais est-ce qu’on est sûr que %pc c’est bien toujours l’instruction qu’on exécute ?".

Donc, on vérifie, par acquit de conscience et on remarque que non, pas nécessairement. Si on est dans une trap, le %pc qu’on enregistre pointe vers l’instruction qui a provoqué la trap, pas l’instruction de la trap qu’on exécute. Bon, OK, ça ne nous avance pas nécessairement (mais si j’en parle…)

Nouvelle question donc : Si on est à %pc, quelles sont les traps qui peuvent s’exécuter ? Il y a plein de possibilités, la plupart viennent de causes extérieures (timer matériel, plein d’autres évènements extérieurs) et potentiellement aussi la trap de l’EDAC si on lit une valeur (et l’instruction à %pc lit une valeur).

Donc techniquement, on pourrait aussi être n’importe où dans le code (assembleur) de toutes les traps. Avant on cherchait pourquoi c’était illégal d’exécuter %pc, maintenant on cherche pourquoi ça serait illégal d’exécuter %pc ou n’importe quelle ligne d’une trap active/activable à ce moment-là.

Chez moi, ça marche

Sauf que le code des traps, c’est pas nous qui l’avons écrit. C’est bien du code qui vient de l’entreprise, mais il existe depuis plusieurs années, est utilisé sur le même processeur depuis plusieurs années, et il a plusieurs dizaines d’années de vol (cumulé, en additionnant les satellites) sans problème.

En suivant les principes bien connus du développement logiciel, si on utilise un logiciel sur étagère, pas besoin de le valider (surtout ça coute de l’argent. Cela dit même si on avait essayé, je ne pense pas qu’on aurait trouvé de problème), vu qu’il marche. Par acquit de conscience, on demande, et on nous répond "bah chez nous ça marche" (la légende veut qu’une histoire similaire soit à l’origine de Docker, je ne sais pas si c’est vrai, mais le fameux "it works on my desktop, ship my desktop"…)

Vous avez peut-être lu le titre de l’article, donc vous imaginez où je vais. On se demande « OK, pourquoi ça marche pour eux, et pas pour nous ? » Quelles sont les différences ?

  • on est sur le même CPU/MCU (donc non, c’est pas ça)
  • on a changé de compilateur pour une nouvelle version (mais 1. c’est un compilateur “certifié”, et 2. les traps sont en assembleur…)
  • on est en orbite plus basse, et on a plus de SEU (mais même, quand on regarde leur historique, ils en ont beaucoup aussi, et en cumulé, beaucoup plus. Après… peut-être n’a-t-on pas de chance ?)

L’erreur

Ok, on a changé de compilateur, les traps sont en assembleur, mais le reste du code est dans un langage bien plus courant (non, je rigole, en vrai c’est en Ada…), peut-être que l’interaction entre les traps et le reste du code a changé ?

Pourquoi est-ce qu’on a décidé de changer de compilateur ? Ah pour des histoires de taille mémoire (640 kB should be enough? On avait même plus, genre 2 Mo de ROM, 4 Mo de RAM, large… ou pas). D’ailleurs, au moment du changement, on en a profité pour faire quelques optimisations. Non pas des flags genre -O1 ou -O2. Plus des choses sur le layout mémoire, on a ajouté __attribute__((packed)) qui est supporté, on a un peu changé le linker script…

Par exemple, le packed, ça nous permet de gagner de la place, avant toutes les variables étaient alignées sur une adresse multiple de 4, que ça soit un nombre sur quatre octets, ou un char d’un octet, ils prenaient au moins quatre octets. Maintenant, on a mis les data types multiples de quatre au début de la structure, bien alignés, puis les types qui prenent deux octets, on en met deux dans quatre octets (au lieu d’un et de gacher deux octets pour rien), puis les types de un octect, on en met 4.

D’ailleurs, par exemple, l’instruction à %pc, elle charge une donnée d’un seul octet qui est dans une adresse du type XXX+3, où X est un multiple de 4. C’est pas illégal de faire ça (donc non, toujours pas d’instruction illégale ici)

Après quoi, c’est là où David revient (dans mon souvenir en tout cas, ça venait beaucoup de lui, mais on était beaucoup à échanger sur le sujet). "Ok, %pc lit une donnée non alignée, et il le fait correctement. Mais s’il y a un bit flip, il se passe quoi ?. Bah rien, EDAC détectée, trap, on exécute le code assembleur qui marche sur les autres satellites.

Ah oui, mais non. Si on lit un octet, on peut lire XXX+3, mais si on lit 4 octets, c’est interdit. Il faut lire une adresse multiple de 4. Et donc on a une EDAC, et quand on rentre dans la trap

; adresse == XXX+3
disable_edac_trap: ;
load [adresse], reg ; Lire 4 octets
enable_edac_trap: ;
store reg, [adresse] ;

Ah oui, mais non. load ça lit 4 octets, c’est illégal de lui passer une adresse non multiple de 4, c’est une illegal instruction. Donc ça pourrait être ça :

  1. bit flip sur les quatre octets situés à XXX (l’EDAC est toujours calculé sur 4 octets d’une adresse alignée, même si on lit décalé)
  2. on rentre dans la fonction qui contient %pc
  3. on lit un octet à XXX+3
  4. ça déclenche la trap
  5. la trap essaye de lire 4 octets à XXX+3
  6. ILLEGAL CPU INSTRUCTION, allez en prison sans passer par la case départ

La reproduction

Sur le papier, ça marche. On peut même faire un petit logiciel sur le banc, qui fait juste un load [XXX+3], reg et qui génère une ILLEGAL CPU INSTRUCTION. Mais évidemment nos managers (et notre client) voudraient un peu plus qu’un « sur le papier, c’est ça, trust me bro ».

Donc la question "c’est possible de reproduire exactement comme dans l’espace, plutôt que de juste exécuter une instruction illégale à la main ?". Avec le vrai logiciel qui était dans l’espace, pas un logiciel de test ?

Bien sûr, il suffit d’attendre d’avoir un bit flip, sur le banc, juste au bon endroit, au bon moment. Vous avez combien de siècles devant vous ? Ou alors est-ce qu’on peut mettre le banc à côté d’un réacteur nucléaire ? Ça devrait accélérer les choses (du bon côté du mur de confinement. Ici, “bon”, ça veut dire mauvais pour les humains)

On va quand même regarder si on peut provoquer un bit flip autrement. Bon, a priori, en interne, au logiciel, on ne sait pas comment faire. La doc du processeur (qui vient avec l’edac) ne nous aide pas non plus. On demande à ceux qui nous ont dit que « chez eux, ça marche » qui nous répondent que la trap de l’edac, ils ne l’ont jamais testé, c’est juste une revue de code.

Bon, on envoie quand même un courriel au fabricant du proc, au cas où. Réponse rapide « je reviens vers vous dès que je sais ». Quelques jours (2, 3 semaines ?) plus tard : "Ah oui, c’est possible. D’ailleurs c’est documenté. Page WSYZ sur 5000, il y a **un* paragraphe qui explique comment faire*".

Le TL/DR du paragraphe : Il est possible de désactiver l’EDAC en écriture. Par contre il faut faire des choses spécifiques, donc on a pas de commande prévue pour le faire “simplement” depuis l’extérieur, il faudrait une nouvelle fonction.

void generer_bit_flip(int address, int valeur) {
*address = valeur; // écrit la valeur correcte avec l’edac normal
manipulate_specific_register_to_disable_edac(); // on a dû écrire la fonction, c’est pas aussi simple
*address = valeur ^ 0x00000001; // écrit la valeur avec un bit changé, mais sans changer le checksum enregistré
manipulate_specific_register_to_enable_edac();
}

Ça tombe bien, le logiciel qui est dans l’espace a deux fonctionnalités qu’on a testé, mais jamais en vrai avec un truc vraiment utile

  1. on peut patcher la mémoire et écrire ce qu’on veut, où on veut (code, données)
  2. on a plusieurs “fonctions” périodiques qui ne font rien, et qui sont prévues pour être patchées si on veut ajouter quelque chose (via la fonction de patch plus haut)

Donc on peut créer une fonction comme ça (en gros)

void generer_bit_flip(int address, int valeur) {
static int actif = TRUE;



if (actif) {
*address = valeur; // écrit la valeur correcte avec l’edac normal
manipulate_specific_register_to_disable_edac(); // ou a dû écrire la fonction, c’est pas aussi simple
*address = valeur ^ 0x00000001; // écrit la valeur avec un bit changé, mais sans changer le checksum enregistré
manipulate_specific_register_to_enable_edac();
actif = FALSE; // on ne veut le faire qu’une fois
}
}

Une fois qu’on a la fonction, on la compile. Ensuite on charge le logiciel normal sur le banc, on se met en conditions « avant l’anomalie », on uploade la fonction, on l’active et…

Le banc change de mode, passe du mode 2, au mode 3, sur le calculateur redondant. On vérifie le contexte, même signature que l’anomalie en vol. C’est bon on a fini. (Ouf, mon journal est déjà trop long)

La correction (Over-The-Air, mais sans l’air)

Oui, non, pas exactement. On a une explication, il faut une correction maintenant. Bon, c’est simple. Pour lire une adresse alignée sur 4, il suffit de mettre deux bits à 0. Finalement, voilà le patch

address = address & ~0x3 ; ** Cette ligne est le patch **
disable_edac_trap: ;
load [adresse], reg ;
enable_edac_trap: ;
store reg, [adresse] ;

Oui, c’est un patch d’une instruction dans le binaire. (Techniquement, 5 instructions, parce qu’il faut décaler les 4 instructions existantes de 1, mais on avait des noop en dessous, donc ça rentre)

La dernière question, c’est quelle stratégie d’ update appliquer. On a techniquement quatre familles de satellites à considérer :

  1. les satellites « pré-existants », qui utilisent l’ancien compilateur, sans packed et déjà dans l’espace.
  2. le satellite qui a eu l’anomalie.
  3. les 5 autres satellites de la grappe.
  4. les futurs satellites, non lancés.

Ce qui a été décidé : La première catégorie : Techniquement, on pourrait discuter du fait qu’il y a un bug ou non. Mais même si on considère qu’il y a un bug, il ne peut pas être déclenché. Donc on ne touche à rien. La catégorie 4, c’est facile. Ils sont au sol, on fait une nouvelle version complète du logiciel, on reflashe la rom en entier, et on vérifie.

Il reste les deux autres catégories. Bon la seule différence, c’est qu’un, toujours en mode 3, tourne pour l’instant sur le calculateur redondant (on peut revenir en mode 2, manuellement, si on veut). Donc on décide « on va faire la même chose », et on va corriger le problème (on aurait pu ne rien faire et dire « bah, si ça arrive, on connaît et on revient à chaque fois manuellement en mode 2 »)

Là encore, même si on corrige, on a plusieurs choix :

  1. Mettre à jour la ROM. En fait non, les ROM, parce que chaque calculateur a la sienne. Et le nominal ne peut pas écrire la ROM du redondant, et inversement. (Dès lors, si on veut patcher, qu’est-ce qu’on patche ? Le deux ROM ? Faut-il reconfigurer à la main pour rebooter sur le redondant ?)
  2. utiliser un mécanisme prévu pour « patcher, mais sans patcher la ROM ».

La solution 2, retenue, c’est un mécanisme (déjà dans le logiciel) qui permet de mettre les infos dans une autre mémoire (partagée par les deux calculateurs). Au boot, la ROM est copiée dans la RAM (on exécute le code depuis la RAM), et « avant de démarrer » on vient regarder dans cette table, si l’on doit patcher la RAM. Cela donne quelque chose comme :

ROM (logiciel original) --> Copie vers la RAM --> RAM (logiciel original) --> fonction de patch au boot, vient modifier la RAM --> RAM (trap corrigée) --> boot du logiciel.

Conclusion

Qu’est-ce que je retiens principalement ?

  • quand on me dit que du code fonctionne, donc qu’il est correct… j’ai un doute
  • Ce n’est pas parce que la doc explique quelque chose qu’on peut le trouver. Surtout quand elle fait 5000 pages… Il ne faut pas hésiter à demander

Voila, en quelques pages, une vieille histoire qui m’a marqué. Je suis probablement une des personnes qui a participé à un des patchs le plus haut du monde (plus de 1 000 km d’altitude)

Bon en vrai, la NASA fait des mises à jour logicielles sur des rovers sur Mars, donc c’est clairement pas le record mais c’est pas trop mal (ils ont même peut-être des mises à jour sur leurs sondes plus loin de la terre)

Note : cette histoire date maintenant d’il y a plus de dix ans. Il y a donc forcément des simplifications, des imprécisions, et probablement des erreurs. Aucun satellite n’a été maltraité pendant cette enquête. Il y en a bien un qui est tombé à terre, mais ça c’était avant le lancement.

Aller plus loin

  • # Intéressant

    Posté par  (Mastodon) . Évalué à 3 (+3/-1).

    Merci du partage.

    Je chipote mais les exemples d’assembleur sont du macro-assembleur.
    On me murmure dans l'oreille qu'il n'y a plus que ça depuis longtemps.
    Bon sang le monde change, j'ai commencé en programmant en hexadécimal…

    • [^] # Re: Intéressant

      Posté par  . Évalué à 7 (+4/-0).

      Les vrais programmeurs codent avec des rayons cosmiques.

      Les vrai programmeurs xkcd

      Le lien xkcd : https://xkcd.com/378/

    • [^] # Re: Intéressant

      Posté par  (Mastodon) . Évalué à 2 (+0/-0).

      Pour être honnête, c'est même pas vraiment très formel, c'est une approximation de l'assembleur (ou macro assembleur) tel qu'il me reste en mémoire, longtemps après. Je n'ai plus vu ce genre de choses sur cette famille d'architecture de près depuis au moins 10 ans ;-)

      Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

  • # Pourquoi le matériel ne corrige pas directement la mémoire ?

    Posté par  . Évalué à 2 (+1/-0). Dernière modification le 10 janvier 2026 à 14:44.

    (Merci !)

    Pour donner au logiciel l'occasion de journaliser l'erreur, ou prendre une décision s'il y a trop d'erreurs, par exemple ?

    • [^] # Re: Pourquoi le matériel ne corrige pas directement la mémoire ?

      Posté par  (site web personnel) . Évalué à 4 (+2/-0).

      ça permet d'envoyer un sysadmin ;-)

      cf. https://xkcd.com/705/

      et en français : https://xkcd.lapin.org/index.php?number=705

    • [^] # Re: Pourquoi le matériel ne corrige pas directement la mémoire ?

      Posté par  (Mastodon) . Évalué à 7 (+5/-0).

      Je ne suis pas sûr/je n'ai jamais sû la raison principale, mais dans ma tête, il y a au moins ces raisons

      • ce n'est vérifié qu'à la lecture, et seul le logiciel déclenche cette lecture (mais ça pourrait effectivement être automatiquement corrigé sans rien dire)
      • ça permet de choisir ce qu'on fait au niveau logiciel. On est informé de l'erreur, et si on veut faire autre chose que corriger, on pourrait. Cela dit, à part corriger, je ne vois pas ce qu'on pourrait faire. Cela dit, ça donnait la flexibilité. Je n'exclus pas qu'on ait eu la possibilité d'activer un mode de correction auto.
      • Mais effectivement en plus de corriger, on journalisait (compter + enregistrer la dernière adresse corrigée, qui était envoyée en télémétrie. Techniquement, rien qu'avec ça j'avais pu déterminer l'organisation "2D" de la mémoire, car il n'était pas rare d'avoir plusieurs adresse "en ligne droite" corrompues par un rayonnement. On avait souvent quelques adresse du type 0xBASE, 0xBASE+N, 0xBASE+2*N corrigée "rapidement" l'une derrière l'autre.
      • au final, on ne pouvait corriger qu'un bit flip par mot (de 4 octets), donc en cas d'erreur multiple, il fallait quand même une stratégie (logicielle aussi dans notre cas) de toute façon pour gérer ça. Enfin, "stratégie", c'était "on reboot", si on peut appeler ça une stratégie ;-)

      Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

      • [^] # Re: Pourquoi le matériel ne corrige pas directement la mémoire ?

        Posté par  (site web personnel, Mastodon) . Évalué à 8 (+5/-0).

        Une raison possible est que la correction que ferait le matériel n'est pas forcément la bonne.

        En gros, le principe des codes détecteurs et correcteurs d'erreur, c'est de trouver le code (la valeur du message et de ses octets de contrôle) "le plus proche" de ce qui est trouvé dans la mémoire.

        Si il y a effectivement un seul bit qui avait changé de valeur, la correction est forcément la bonne. Mais s'il y avait 2, ou 4, ou 31 bits sur 32 de changés? Dans certains cas on peut toujours détecter qu'il y a une erreur, mais la correction proposée par le matériel n'est pas la bonne: elle donne une valeur dont la somme de contrôle est valide, mais pas celle d'origine.

        Pour prendre un exemple très simple, imaginons un système de parité en lignes et colonnes.

        Le message qu'on veut envoyer est:

        00
        01
        

        On ajoute sur chaque ligne et chaque colonne un bit de telle façon que il y ait toujours un nombre pair de 1 dans chaque ligne et colonne:

        00 0
        01 1
        
        01 1
        

        Maintenant, si il y a un seul bit corrompu, on détecte tout de suite quelle ligne et quelle colonne ne sont pas bonnes, et on peut corriger. Par exemple:

        00 0
        00 1 <- Erreur sur cette ligne
        
        01 1
        
         ^- Erreur sur cette colonne
        

        Mais si on a 2 bits qui sont changés, on ne sait plus. Par exemple si on reçoit:

        00 0
        00 0 <- Deux bits 1 sont devenus des 0 ici
        
        01 1
        

        On détecte une erreur sur 2 colonnes et sur aucune ligne.

        Il y a plusieurs corrections possibles en changeant 2 bits:

        00 0
        00 0
        
        00 0 <- On peut corriger ici
        
        00 0
        01 1 <- On peut corriger ici
        
        01 1
        
        01 1 <- Ou alors on peut corriger ici
        00 0
        
        01 1
        

        Et s'il y a encore plus d'erreurs, on peut se trouver dans un cas où la correction la plus évidente (changer 1 seul bit) ne correspond pas au message initial (il fallait en fait changer 3 bits ailleurs).

        Ce code de parité matriciel permet de corriger toutes les erreurs de 1 bit. Il permet de détecter toutes les erreurs de 1, 2 ou 3 bits. Au delà, dans certains cas il ne détecte même pas forcément qu'il y a une erreur.

        Bien sûr, ce n'est pas un code très performant, on sait faire beaucoup mieux, mais le principe reste le même.

        Pour plus d'info là dessus, voir le principe du calcul de distance de Hamming (combien de bits il faut changer pour passer d'un code à un autre) et les garanties apportées par le code correcteur (une distance de Hamming minimale entre 2 codes valides). Si il y a plus d'erreurs que cette distance minimale, on va se retrouver plus "proche" d'un autre code que celui initial.

        Le même code détecteur/correcteur est donc plus performant pour détecter les erreurs (il suffit de voir qu'on a pas un mot de code valide), que pour les corriger (il faut en plus retrouver quel était le mot de code de départ).

        On utilise le mode de correction dans le cas où la donnée n'est pas récupérable autrement (lecture depuis un support de stockage, transmission radio (ou filaire) temps réel, …). Mais ici on a un meilleur moyen: redémarrer le satellite et réinitialiser toute la mémoire. Donc, pas la peine de prendre le risque de tenter une correction qui ne serait peut-être pas la bonne.

    • [^] # Re: Pourquoi le matériel ne corrige pas directement la mémoire ?

      Posté par  . Évalué à 2 (+1/-0).

      Il y a des contrôleurs mémoire qui font cette ré-écriture (opération dite de "scrubbing") automatiquement (sinon le handler EDAC devra s'en charger) quand une erreur détectée/corrigée à la volée sur un load (data) ou fetch (instruction).

      Certains font même en sorte que des données (ou du code) peu utilisé ne passent pas au travers et que des erreurs single-bit/corrigibles soient alors "rattrapées par la patrouille", faute d'usage fréquent (détection/correction ne se font sinon que sur une lecture en mémoire rappelons le!), avant de potentiellement s'aggraver avec le temps en erreurs multi-bit alors non corrigibles: Cela s'appelle le "patrol scrub".

      On peut aussi le faire en soft, mais que le matériel s'en charge est toujours mieux car cela n'occupe pas le processeur d'une part mais en prime le contrôleur mémoire est le mieux placé pour caser des tranches mémoire de lecture (et correction éventuelle) à un moment optimal/ou on a de la bande passante vers la DDR et donc ne pas impacter les perfs.

      Pour ce patrol scrubbing, quand on l'active on a généralement un objectif de périodicité du parcours de la mémoire complet. Chez Intel c'est par défaut 24h par exemple.

      Et même en cas d'erreur non corrigible, il est possible de pousser plus loin mais c'est alors des fonctionnalités OS: La zone est elle mappée par le kernel ou un process, donc utilisée? Si ce n'est pas le cas, aucun impact et on peut continuer. On peut aussi avoir encore une copie valide de la donnée, au hasard dans un cache (L1/L2 ou plateforme) et dans ce cas on a une copie valide à simplement flusher en mémoire. Au delà, si c'est dans un process applicatif on peut choisir de le tuer en moindre mal d'un reboot complet, qui dans les autres cas en règle générale s'imposera.

  • # Ça me fait penser à une problème similaire sur Voyager 1

    Posté par  . Évalué à 8 (+6/-0).

    Tout d'abord, merci pour ce journal, dans lequel on voyage très haut dans l'espace mais aussi très bas dans le processeur, passionnant, avec la bonne dose de suspens !

    Ça me fait penser à cette vidéo : How We Diagnosed and Fixed the 2023 Voyager 1 Anomaly from 15 Billion Miles Away, qu'on trouve sur Internet. Par mal de terrain commun avec ton histoire.

    Vers le début, le présentateur ménage bien ses effets :
    - le programme n'est stocké qu'en RAM, semble-t-il, pas de ROM, et une des banques mémoire est HS depuis quelques années;
    - les commandes envoyées ont 45 heures de temps de réponse;
    - ensuite il montre un bout de code assembleur et dit "on a pas le vrai code source natif, c'est sans doute un scan OCR d'une copie papier, heureusement qu'il il y a des commentaires" (là j'ai été vraiment sidéré).

    Sinon, pour les bitflips, ça m'a étonné que vous ne puissiez pas stopper le processeur pour patcher la mémoire avec un truc comme du JTAG ou que sais-je. C'est parce que ces processeurs sont minimaux pour limiter les bugs, et que y'a pas ça dedans, une autre raison ?

  • # Extra - terrestre

    Posté par  (site web personnel) . Évalué à 3 (+1/-0).

    Merci pour ce journal sorti de l'espace ! On sent la passion transpirer à la lecture de ces lignes, c'est un joyeux mélange de story-telling et de technique, un régal pour les yeux.

    Et je ne peux que me sentir bienheureux à pousser mes patchs sur la CI « pour voir » si tout marche bien, quand on voit les précautions à prendre quand on interagit avec du (coûteux ?) matériel qui tourne à un millier de kilomètres au dessus de nos tête.

  • # Merci pour ce partage!

    Posté par  . Évalué à 4 (+3/-0). Dernière modification le 12 janvier 2026 à 11:06.

    J'en retiendrais aussi les provisions patch/callback périodique qui ont été bien utiles pour la reproduction et le patch sans ajouter le risque de flinguer la ROM… a noter que les compilateurs peuvent proposer des possibilités de cet ordre (par exemple gcc et son -finstrument-functions permettant d'appeler une fonction d'instru, par défaut un simple retour à l'appelant, à chaque appel de fonction de son code ; impact perf mais en général dans ce contexte c'est pas forcément le principal souci).

    Le truc qui fait bien tilter qd même: L'EDAC qui n'a eu qu'une revue de code (qui a dit que ça ne servait bien trop souvent qu'à se chamailler sur la forme sans trouver les pb de fond?) pour test par le fournisseur! Dans ce contexte, aie-aie-aie, mais je reconnais bien là les pratiques toujours en cours sur la gestion des soft "3rd party". Aie confiance, crois en moi ((c) Le Livre de la Jungle!)…

    Les mecs n'ont pas trop cherché quand même car il y a forcément un moyen de tester cela (pour les propres besoins du fondeur), même si ici le processeur ne semblait pas implémenter une véritable injection d'erreur comme c'est le cas sur tout ce que j'ai pu voir (à base PPC/x86/ARM) comme contrôleur DDRx.

    Surtout que 5000 pages, en fait, c'est pas grand chose de nos jours: Les premiers SoC PowerPC sur lesquels j'ai travaillé à la fin des années 90 (famille MPC860) devaient en être à peu près à ce chiffre pour la totalité du contenu des différents contrôleurs intégrés hors jeu d'instruction. Les derniers (famille QorIQ), on devait en avoir 1 de ce volume par IP conséquente intégrée (contrôleurs, différents accélérateurs, système de trace/debug intégré…) et peut-être 5 ou 6 en tout! Là clairement, le boulot de généraliste type support/validation devenait assez difficile sans un bon carnet d'adresse en interne et chez les FAE du fondeur.

    Maintenant, les pb apparus après tout changement touchant au build (version, options), j'ai également qq souvenirs… Jusqu'à même tomber sur un errata processeur alors non documenté, via un changement dans l'allocation des registres généraux, qui faisait au gré des build parfois tomber dans une instruction qui fournissait un résultat dans le registre d'à côté de celui de l'opcode (correct) généré.

    • [^] # Re: Merci pour ce partage!

      Posté par  (Mastodon) . Évalué à 4 (+2/-0).

      Sur le test de l'EDAC, j'ai peut être mal exprimé les choses, mais

      1. je suis sûr que la partie matérielle était bien testée, mais ça venait d'ailleurs (Atmel, je crois. ou plus haut dans la chaine encore) Potentiellement on pourrait raler et dire que la contrainte de ne pas pouvoir lire 4 octets non alignés, c'est nul, mais bon, c'est comme ça et c'est bien documenté, c'est juste une instruction illégale.
      2. pour la partie logicielle de la correction en cas de détection, c'était du logiciel interne (à l'entreprise pour laquelle je travaillait, l'OEM du satellite). Et là
        • effectivement, sur notre projet, ça n'a pas été retesté. C'était du COTS interne.
        • je pense que c'était bien revu (et peut être testé) dans le cadre du projet initial (qui n'avait pas de données non alignées)

      Par contre, du coup l'hypothèse "ça marchera parce que ça marche sur d'autres projets" était incorrecte car les conditions d'utilisations étaient différentes. Dit comme ça, ça ressemble beaucoup à Ariane 501, avec des conséquences bien moins dramatiques dans "mon" satellite :-)

      Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

  • # Si si: même process au quotidien^^

    Posté par  . Évalué à 3 (+1/-0).

    les moyens sont potentiellement différents de ce que vous avez dans d’autres environnements

    Il y a cette fameuse histoire de Deep Space 1 qui embarquait du code Lisp et que les dévs ont débuggué avec un évaluateur de commandes… comme en local, sauf qu'il fallait attendre 1 heure d'aller-retour.

    https://corecursive.com/lisp-in-space-with-ron-garret/#debugging-code-in-space

    we had a REPL running on the spacecraft and we could interact with the spacecraft through that REPL. Now accessing that REPL was not just a matter of sitting down at a terminal typing at it because to communicate with that REPL, you had to go through the Deep Space network and deal with this hour long round trip light time.

    Yeah. We were sending up S-Expressions.

  • # Merci !

    Posté par  (site web personnel) . Évalué à 5 (+4/-0).

    C’est pour ce genre d’articles que, 20 ans après, je lis toujours ce site. Merci !

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.