2 points par GN⁺ 2025-01-27 | 1 commentaires | Partager sur WhatsApp
  • Le Mac Classic II a failli écrire à une adresse invalide à cause d’un bogue de saut hors limites dans sa ROM, mais le véritable CPU Motorola MC68030 a exécuté une instruction non documentée qui a modifié le registre A1 et évité le plantage
  • Le problème est apparu dans l’émulation du Classic II par MAME lorsque l’adressage 32 bits était activé : Sad Mac et les Chimes of Death se déclenchaient, alors qu’en adressage 24 bits la machine semblait démarrer normalement
  • Le chemin InstallSoundIntHandler de la ROM appliquait le BoxFlag 17 du Classic II à une table de branchement de 16 entrées, ce qui provoquait un saut vers 0x40A43B94, une position qui se trouvait en fait au milieu d’une instruction MOVEA.L
  • La séquence d’octets 0C EC 08 A9 00 04 ressemblait à CAS D1,D2,$0004(A4), mais c’était une instruction invalide de la famille CAS avec des bits réservés activés ; sur un vrai 68030, elle faisait passer A1 de FFFF8FBA à 40A4BBB2
  • Des tests avec trois ROM personnalisées sur un vrai Classic II ont confirmé que remplacer cette instruction par un NOP faisait aussi échouer le matériel avec un Sad Mac ; MAME contourne le démarrage via un patch de ROM en attendant de connaître le comportement exact

L’échec de démarrage du Classic II révélé par MAME

  • MAME est surtout connu comme émulateur de jeux d’arcade, mais il prend aussi en charge l’émulation de modèles de Mac basés sur le 68000
  • En corrigeant dans MAME la fonction qui appelle le débogueur avec la combinaison command+power, un comportement étrange a été découvert sur le Classic II
    • Le Classic II dispose d’un bouton d’interruption physique, ainsi que d’un microcontrôleur 68HC05 « Egret » qui gère notamment le clavier et la souris
    • MacsBug contient du code qui envoie une commande activant la combinaison command+power
  • Le Classic II démarrait normalement en adressage 24 bits, mais l’activation de l’adressage 32 bits, nécessaire au chargement de MacsBug, déclenchait un Sad Mac et les Chimes of Death
  • D’après la documentation Apple Tech Info Library, le code Sad Mac 0000000F indique une exception, et 00000001 une erreur de bus
    • Sur les Mac 68k, une erreur de bus signifie généralement un accès à une adresse invalide, comme une tentative d’accès à une carte d’extension inexistante

Traçage de la ROM : le chemin d’initialisation de l’interruption audio

  • Dans le débogueur de MAME, un point d’arrêt a été placé à l’adresse du gestionnaire d’erreur de bus 0x40A026F0, puis le chemin d’appel a été retracé à l’aide de la carte des symboles de la ROM
    • Cette adresse apparaît comme GenExcps dans la carte de la ROM
    • La routine commune de gestion des erreurs apparaît sous le nom ToDeepShit
  • L’instruction qui a provoqué l’erreur de bus était le code suivant à 0x40A43B9C
move.b #$90, ($1c00,A1)
  • Ce code se trouvait dans une routine indiquée comme InstallSoundIntHandler dans la carte de la ROM, et concerne l’initialisation des interruptions audio du Classic II
  • Le Classic II est structurellement plus proche de la famille Macintosh LC que du Macintosh Classic d’origine, et utilise un gate array EAGLE similaire à la puce V8 du LC
    • C’est pourquoi des noms comme V8SndIntPatch1 apparaissent dans le code de la ROM
    • EAGLE intègre des fonctions correspondant à une version réduite de l’Apple Sound Chip (ASC)

Table de branchement erronée et problème du registre A1

  • V8SndIntPatch1 vérifie les attributs matériels via le sélecteur gestaltHardwareAttr du trap Gestalt, c’est-à-dire 'hdwr'
    • S’il n’y a pas le bit gestaltHasASC, la routine retourne
    • Sur le Classic II, gestaltHasASC est défini
  • Ensuite, le code lit la valeur BoxFlag en RAM à 0xCB3 et la place dans D0
    • La valeur BoxFlag du Classic II est 0x11, soit 17
  • Le code de branchement problématique double D0 puis l’utilise comme offset de saut relatif au PC
add.w d0,d0
jmp loc_40A43B72(pc,d0.w)
  • Comme chaque instruction BRA.S fait 2 octets, la valeur de BoxFlag était doublée pour servir d’index dans la table de branchement
  • La table réelle ne contenait que 16 entrées correspondant aux BoxFlag 0 à 15, et la valeur 17 du Classic II sortait des limites
    • La cible de saut calculée était 0x40A43B72 + 0x22 = 0x40A43B94
    • Cette position n’était pas le début prévu d’une instruction, mais le milieu d’une instruction MOVEA.L
  • L’instruction MOVEA.L d’origine aurait dû charger une adresse correcte dans A1, mais le saut au milieu de l’instruction laissait A1 conserver la valeur utilisée par le calcul précédent du saut relatif, 0xFFFF8FBA
  • Lorsque move.b #$90, ($1c00,A1) s’exécute ensuite, l’adresse d’écriture devient 0xFFFF8FBA + 0x1C00 = 0xFFFFABBA, une adresse invalide sur le Classic II

Comportement non documenté du 68030

  • La séquence d’octets atteinte par le saut hors limites était la suivante
0C EC 08 A9 00 04
  • IDA et GNU objdump n’ont pas réussi à désassembler correctement une instruction à cet emplacement
  • En exécutant la même séquence d’octets dans MacsBug sur un Macintosh IIci, elle s’affichait comme CAS.W D1,D2,$0004(A4), mais la valeur de A1 changeait après exécution
    • A1 passait de 0xFFFF8FBA à une valeur ressemblant à une adresse RAM
    • Il a été observé que la nouvelle valeur de A1 variait selon la valeur initiale de A1, A7 et du compteur ordinal
  • D’après le Motorola M68000 Family Programmer’s Reference Manual, le premier mot ressemble à une instruction CAS, mais le deuxième mot contient trois bits qui devraient être à 0 et sont définis à 1
  • Cette instruction n’est donc pas une CAS documentée et valide, mais une instruction invalide avec des bits réservés activés
  • Un vrai 68030 ne traite pas cette instruction comme une exception illegal instruction, et effectue un cycle de bus read-modify-write sur A4+4
    • Si A4 est défini sur une adresse invalide, MacsBug signale une erreur de bus pendant le read-modify-write sur A4+4
    • A1, qui ne devrait pas être modifié par une CAS normale, est également changé

Vérification sur du vrai matériel Classic II

  • Pour vérifier l’hypothèse, un Classic II fabriqué en 1991 a été acheté puis réparé, avant de remplacer sa ROM par une EEPROM programmable SST29EE010
    • Les condensateurs CMS des vieux Mac peuvent provoquer des fuites corrosives ; ils ont donc été retirés et remplacés avant la mise sous tension
    • La carte logique a été alimentée séparément, sans CRT ni carte analogique, et l’affichage a été vérifié avec un convertisseur VGA basé sur un Raspberry Pi Pico
  • Du code assembleur 68030 affichant la valeur de A1 à l’écran a été placé dans l’espace libre de la ROM, puis trois ROM personnalisées ont été créées
    • Custom ROM 1 : remplacement du MOVE.B à 0x40A43B9C, qui provoquait le Sad Mac dans MAME, par le code d’affichage de A1
    • Custom ROM 2 : remplacement de l’instruction ressemblant à CAS à 0x40A43B94, cible du saut hors limites, par le code d’affichage de A1
    • Custom ROM 3 : remplacement de l’instruction ressemblant à CAS à 0x40A43B94 par un NOP
  • Custom ROM 1 montre que ce code s’exécute aussi sur le vrai matériel, et que A1 vaut 0x40A4BBB2 à l’endroit où MAME plantait
    • Cette valeur est une adresse ROM, donc pas une cible d’écriture appropriée, mais tenter d’y écrire ne provoque pas d’erreur de bus
  • Custom ROM 2 montre que, juste avant le saut hors limites, A1 vaut 0xFFFF8FBA, la même valeur que celle observée dans MAME
    • Le saut hors limites se produit réellement
    • Cela correspond à l’hypothèse selon laquelle l’instruction ressemblant à CAS modifie ensuite A1
  • Custom ROM 3 montre que si l’instruction ressemblant à CAS est supprimée, le vrai Classic II échoue lui aussi avec un Sad Mac
    • Dans ce test, le matériel affiche le même Sad Mac même en mode 24 bits
    • Le fait que MAME ne déclenchait pas d’erreur de bus sur l’écriture invalide en mode 24 bits était donc un comportement plus permissif que celui du vrai matériel

Les problèmes qui restent pour MAME et l’émulation

  • Le comportement découvert est un cas d’instruction MC68030 non documentée qui effectue un cycle de bus read-modify-write et modifie même le registre A1
  • Le bogue de la ROM du Classic II a été masqué grâce à ce comportement, et la vraie machine fonctionnait normalement
  • Le même fragment de code a été trouvé dans la ROM plus récente du Macintosh IIvx, mais dans cette ROM la table de saut a été agrandie, et le cas du Classic II saute directement vers RTS
  • À cause de ce comportement, il paraît probable qu’il n’existe actuellement aucun émulateur ou implémentation clone Motorola MC68030 parfaitement fidèle à 100 %
    • En exécutant cette instruction et en vérifiant le résultat de A1, il serait possible d’écrire un petit code distinguant un 68030 physique d’un émulateur
  • En attendant que le comportement exact de l’instruction soit élucidé, MAME permet le démarrage en patchant le bogue de la ROM du Classic II
  • Séparément, la combinaison command+power fonctionne sur un vrai Classic II lorsque MacsBug est installé, mais pas encore dans MAME

1 commentaires

 
GN⁺ 2025-01-27
Avis sur Hacker News
  • Le comportement observé du MC68030 pourrait être moins une « vraie » instruction conçue intentionnellement par les concepteurs du CPU qu’un résultat fortuit de la logique interne du CPU exécutée avec des entrées indifférentes (don’t-care) pour une instruction illégale
    En temps normal, le CPU devrait détecter une instruction illégale et lever une exception, mais il semble que, dans certains cas, ce ne soit pas le cas
    Aux pages 8-9 de https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf, les instructions illégales sont définies selon le motif de bits du « premier mot »
    Cette instruction compte trois mots, le premier est valide, et les bits étranges se trouvent dans le deuxième mot ; il est donc très probable que le 68030 n’ait pas validé ce deuxième mot et ait simplement poursuivi dans la logique d’implémentation de l’instruction CAS

    • Le faux CPU étudié dans mon cours d’architecture des ordinateurs à l’université découpait les codes d’instruction en plusieurs champs de bits, certains bits activant des opérations primitives comme un additionneur
      Les codes d’instruction « valides » n’étaient que des combinaisons assez utiles pour être documentées, tandis que les combinaisons « invalides » étaient inutiles ou dénuées de sens
      Vu comme ça, les opcodes illégaux/mystérieux ne sont pas une exception surprenante, mais presque une fatalité
    • Je ne suis pas spécialiste du 68k, mais il me semble que seul le premier mot est considéré comme le code d’instruction, tandis que le deuxième sert à choisir les registres D utilisés par CAS
      Normalement, on s’attendrait à ce que les bits à 0 sans signification dans la documentation soient complètement ignorés
      Mais sur le 68030, il se peut que ces bits doivent impérativement être à 0 et que, s’ils sont activés, ils déclenchent une autre logique câblée destinée à une autre instruction, comme pour les opcodes illégaux du 6502
  • CAS a toujours été une source de soucis, et c’est probablement l’instruction qui nous a valu le plus de rapports de bugs d’émulation
    Dans un ancien King of Fighters, il y avait aussi un bug qui vérifiait « mal » le flag de retenue de l’instruction SBCD, et qui l’utilisait pour décrémenter le timer du round et terminer le round
    Même s’il s’agit d’un comportement non documenté, si l’on n’émule pas les flags d’état arithmétiques des opérations en décimal codé binaire, le timer de round de KOF continue de boucler de 00 à 99 sans jamais se terminer
    SNK était vraiment un dieu de la puce 68000

    • En tant que fan de bornes d’arcade rétro et du 68k, j’aimerais beaucoup en savoir plus sur la façon dont SNK a poussé le 68K dans ses derniers retranchements
  • Quel très long périple
    Ces temps-ci, je n’ai plus la patience de descendre aussi profondément dans le terrier que l’auteur, mais je comprends très bien le sentiment d’accomplissement qui vient de l’écart entre penser savoir quelque chose et le savoir réellement

    • En tant qu’auteur, quand je tombe sur ce genre de chose, j’ai tendance à me laisser entraîner dans une sacrée analyse en profondeur
      Même si cela prend beaucoup de temps, aller jusqu’au bout est vraiment gratifiant
  • Même plus de 30 ans plus tard, je suis toujours étonné de voir à quel point l’interface de débogage du Mac pouvait être efficace sur une si petite résolution d’écran
    On y sent vraiment le travail d’artisan

  • Presque tous les CPU ont des instructions non documentées, et le 68k ne fait pas exception
    C’est simplement qu’à l’époque, la plupart des gens ayant assez d’intérêt et de connaissances bas niveau se concentraient sur x86/PC, qui était probablement une architecture bien plus ouverte et plus stable que du côté d’Apple
    Le microcode des 8088 et 8086 a été désassemblé il y a quelques années et largement étudié, et il me semble qu’il y a aussi eu des tentatives de simulation au niveau des transistors
    La structure de l’espace des opcodes x86 a également été explorée en détail dans des documents comme ceux-ci
    http://ref.x86asm.net/geek.html
    https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...
    On ne sait pas encore exactement ce que fait cette instruction
    Des tests limités semblent indiquer que la valeur résultante de A1 dépend de la valeur initiale de A1, de la valeur de A7 et du compteur ordinal, mais ce n’est pas certain
    Si quelqu’un écrivait un programme injectant différentes valeurs de registres et contenus mémoire pour en déduire précisément le comportement, l’émulation pourrait devenir plus exacte
    Pour l’instant, tant que personne ne juge que cela vaut la peine de creuser, MAME patche ce bug dans la ROM pour permettre au Classic II de démarrer
    Personnellement, je pense que cela vaut largement la peine d’être élucidé pour une émulation exacte
    Je ne suis pas familier du 68k, mais les bits de l’instruction donnent des indices. Mon hypothèse est que les bits 5:3 du deuxième mot ressemblent à un autre champ de mode ; au lieu que le mode 000 sélectionne un registre Dn, 101 sélectionnerait à nouveau (d16, An), et le champ Dc contenant 001 serait interprété comme A1

    • Petite correction : le 68k n’était évidemment pas une architecture Apple
      Le 68030 était un très bon CPU généraliste pour l’époque, également utilisé dans l’Amiga 3000, l’Atari Falcon, le Sun 3, le NeXT Cube, etc.
    • À l’époque où le Mac Classic avait encore de l’importance, le PC n’était pas encore si dominant dans l’informatique domestique, et il n’y avait pas non plus tant de gens qui fouillaient ce genre de choses côté x86/PC
      La demoscene restait encore accrochée aux ordinateurs domestiques 8 bits, et ceux qui utilisaient des systèmes domestiques 16 bits se concentraient sur l’Atari et l’Amiga
      C’est après que le VGA et les cartes son sont entrés dans la configuration PC standard que le PC et x86 ont vraiment décollé à la maison
  • Quand j’étais enfant, j’avais un Amiga 2000 avec un processeur 68000
    J’étais vraiment enthousiaste quand j’ai entendu parler des 68020 et 68030, puis des architectures RISC ensuite
    Quand on faisait dire à l’Amiga, avec sa voix robotique, des phrases comme « Hello, how are you? », mes amis étaient ébahis comme s’ils revenaient de la Lune
    Je n’aurais jamais imaginé qu’en moins de 40 ans, on parlerait en langage naturel avec des ordinateurs via des LLM, et qu’avec Python, VS Code et les LLM comme outils, on pourrait automatiser presque tout ce qu’on veut
    Quelle époque folle

  • La famille 68000 me manque
    C’étaient vraiment d’excellentes puces

    • L’assembleur 68000 me manque
      Il était clair et logique, comme devrait toujours l’être une excellente ingénierie : il suffisait de le parcourir pour comprendre immédiatement ce qui se passait
      À l’inverse, l’assembleur x86 ressemble le plus souvent à un pur bric-à-brac, comme des astuces intelligentes issues d’une mauvaise nuit blanche
      Soustraire un registre de lui-même pour obtenir 0, c’est vraiment abusé
  • Je me demande si c’était une protection anticopie destinée à empêcher l’exécution sur certains systèmes, ou si c’est un phénomène qui se produit sur tous les 68030

    • D’après le contenu de l’article, je ne pense pas que ce soit une protection anticopie
      L’entrée correspondant à cette machine manquait dans la table de sauts impliquée ici, et il semble probable que les personnes qui travaillaient sur la ROM aient tout simplement oublié d’ajouter la nouvelle entrée, mais que, par pur hasard, cela ait fonctionné même sans entrée dans la table
    • Apple commençait déjà à tenter un peu l’obsolescence programmée
  • Sur les Mac actuels, ce genre de chose semble désormais impossible
    La documentation technique d’Apple, de nos jours, est lamentable

    • À mon avis, si Apple ne fournit pas de documentation matérielle de ce niveau, c’est parce que les Mac modernes n’ont pas de capacités d’extension comparables
      Ils n’exposent plus, via des connecteurs, un bus système où l’utilisateur insérait des cartes et où des développeurs tiers devaient interagir directement avec le matériel, comme autrefois
      Sur les Mac modernes, on a USB et Thunderbolt, accessibles depuis des programmes en espace utilisateur
      Bien sûr, je ne nie pas que la documentation de certaines API macOS récentes soit très lacunaire
      Quand on tombe sur une vieille page avec un dégradé bleu dans l’en-tête et la mention « Apple documentation archive », on sait qu’on a mis la main sur quelque chose de bien
    • Dans un environnement moderne, je pense qu’il est bien moins probable qu’un bug de ce type se retrouve dans un firmware ou un logiciel publié
      L’espace d’adressage est beaucoup plus grand et la plupart des adresses ne sont pas mappées, donc une opération mémoire sur une adresse bidon échouerait généralement, et une instruction invalide échouerait probablement aussi
      Le bug consistant à lire une table de sauts avec un index hors limites peut tout à fait exister dans des logiciels modernes, mais le processus a plus de chances de mourir que de continuer à s’exécuter comme ici
      À noter que WebAssembly possède un unique espace d’adressage linéaire et que toutes les adresses peuvent être lues et écrites, ce qui rend ce type de bug plus plausible
      Si l’adresse bidon est dans les limites, on peut faire une lecture, une écriture ou un CAS erroné et continuer malgré tout
      En revanche, une instruction invalide comme dans l’article ferait échouer le chargement du module WASM ; ce n’est donc pas exactement équivalent au problème de cette ROM Mac
  • Je me demande si les 040/060 prennent eux aussi en charge cette « instruction non documentée »

    • Sur 040, elle semble effectivement faire quelque chose impliquant D1
      A1 n’a pas du tout été touché, et je n’ai pas testé davantage, mais il se pourrait qu’elle soit simplement traitée comme un CAS normal
      La première fois que j’ai exécuté cette instruction pas à pas dans MacsBug sur un LC 475, j’ai eu une erreur système, mais ensuite tout allait bien
    • Après vérification sur un Amiga équipé d’un 060, cette instruction ne semble modifier aucun registre A
      C’est le résultat d’un test très rapide portant uniquement sur ce mot d’instruction précis