- 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
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égaleEn 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
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é
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
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
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
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.
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
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
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
Sur les Mac actuels, ce genre de chose semble désormais impossible
La documentation technique d’Apple, de nos jours, est lamentable
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
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 »
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
C’est le résultat d’un test très rapide portant uniquement sur ce mot d’instruction précis