2 puntos por GN⁺ 2025-01-27 | 1 comentarios | Compartir por WhatsApp
  • La Mac Classic II estuvo a punto de escribir en una dirección incorrecta por un bug de salto fuera de rango en la ROM, pero la CPU real Motorola MC68030 ejecutó una instrucción no documentada y cambió el registro A1, evitando el fallo
  • El problema salió a la luz cuando en la emulación de la Classic II en MAME se activó el direccionamiento de 32 bits: aparecieron Sad Mac y Chimes of Death, mientras que con direccionamiento de 24 bits parecía arrancar normalmente
  • La ruta InstallSoundIntHandler de la ROM aplicaba el BoxFlag 17 de la Classic II a una tabla de saltos de 16 entradas y terminaba saltando a 0x40A43B94, una posición que originalmente estaba en medio de una instrucción MOVEA.L
  • La secuencia de bytes 0C EC 08 A9 00 04 se parecía a CAS D1,D2,$0004(A4), pero era una instrucción inválida de la familia CAS con bits reservados activados, y en un 68030 real cambió A1 de FFFF8FBA a 40A4BBB2
  • Tras verificarlo en una Classic II real con tres ROM personalizadas, al reemplazar esa instrucción por NOP el hardware también falló con Sad Mac; mientras tanto, MAME evita el problema con un parche de ROM hasta conocer el comportamiento exacto

Falla de arranque de la Classic II revelada en MAME

  • MAME es muy conocido como emulador de juegos arcade, pero también soporta la emulación de modelos Mac basados en 68000
  • Mientras se ajustaba en MAME la función para invocar el depurador con la combinación command+power, se encontró un comportamiento extraño en la Classic II
    • La Classic II tiene un botón físico de interrupción y también un microcontrolador “Egret” 68HC05 que maneja teclado, mouse y otros elementos
    • MacsBug incluye código que envía el comando para activar la combinación command+power
  • La Classic II arrancaba normalmente con direccionamiento de 24 bits, pero al activar el direccionamiento de 32 bits necesario para cargar MacsBug aparecían Sad Mac y Chimes of Death
  • Según la documentación de Apple Tech Info Library, el código Sad Mac 0000000F indica una excepción y 00000001 significa error de bus
    • En las Mac 68k, un error de bus normalmente indica un acceso a dirección inválida, como intentar acceder a una tarjeta de expansión inexistente

Seguimiento de la ROM: ruta de inicialización de la interrupción de sonido

  • En el depurador de MAME se puso un breakpoint en la dirección del manejador de error de bus 0x40A026F0 y se rastreó la ruta de llamada usando el mapa de símbolos de la ROM
    • Esa dirección aparece como GenExcps en el mapa de ROM
    • La rutina común de manejo de errores aparece con el nombre ToDeepShit
  • La instrucción que provocaba el error de bus era el siguiente código en 0x40A43B9C
move.b #$90, ($1c00,A1)
  • Este código estaba dentro de la rutina identificada como InstallSoundIntHandler en el mapa de ROM y está relacionado con la inicialización de la interrupción de sonido de la Classic II
  • La Classic II en realidad se parece más en arquitectura a la familia Macintosh LC que a la Macintosh Classic original, y usa un gate array EAGLE similar al chip V8 del LC
    • Por eso aparecen nombres como V8SndIntPatch1 dentro del código ROM
    • EAGLE incluye funciones equivalentes a una versión reducida del Apple Sound Chip (ASC)

La tabla de saltos incorrecta y el problema del registro A1

  • V8SndIntPatch1 revisa los atributos de hardware usando el selector gestaltHardwareAttr del trap Gestalt, es decir, 'hdwr'
    • Si no está el bit gestaltHasASC, regresa
    • En la Classic II, gestaltHasASC sí está activado
  • Después, el código lee el valor BoxFlag desde 0xCB3 en RAM y lo coloca en D0
    • En la Classic II, el valor de BoxFlag es 0x11, o sea 17
  • El código de salto problemático duplica D0 y lo usa como offset de salto relativo al PC
add.w d0,d0
jmp loc_40A43B72(pc,d0.w)
  • Como cada instrucción BRA.S ocupa 2 bytes, la estructura duplicaba el valor de BoxFlag para usarlo como índice en la tabla de saltos
  • Pero la tabla real solo tenía 16 entradas, correspondientes a BoxFlag 0~15, así que el valor 17 de la Classic II quedaba fuera de rango
    • El destino calculado del salto era 0x40A43B72 + 0x22 = 0x40A43B94
    • Esa posición no era el inicio de una instrucción prevista, sino la mitad de una instrucción MOVEA.L
  • La instrucción MOVEA.L original debía cargar una dirección válida en A1, pero al saltar al medio de la instrucción A1 conservó el valor 0xFFFF8FBA usado en el cálculo previo del salto relativo
  • Luego, al ejecutarse move.b #$90, ($1c00,A1), la dirección de escritura se volvía 0xFFFF8FBA + 0x1C00 = 0xFFFFABBA, una dirección inválida en la Classic II

Comportamiento no documentado del 68030

  • La secuencia de bytes a la que llegaba el salto fuera de rango era la siguiente
0C EC 08 A9 00 04
  • Ni IDA ni GNU objdump pudieron desensamblar correctamente esa posición como una instrucción válida
  • Al ejecutar la misma secuencia de bytes en MacsBug de una Macintosh IIci, se mostró como algo parecido a CAS.W D1,D2,$0004(A4), pero después de ejecutarla el valor de A1 cambiaba
    • A1 pasaba de 0xFFFF8FBA a un valor que parecía una dirección de RAM
    • Se observó que el nuevo valor de A1 variaba según el A1 original, A7 y el contador de programa
  • Según el Motorola M68000 Family Programmer’s Reference Manual, la primera palabra se parece a una instrucción CAS, pero en la segunda palabra hay 3 bits que deberían ser 0 y están puestos en 1
  • Por lo tanto, esta instrucción no era una CAS válida documentada, sino una instrucción inválida con bits reservados activados
  • El 68030 real no la trató como una excepción de instrucción ilegal, sino que realizó un ciclo de bus read-modify-write sobre A4+4
    • Si A4 se configuraba con una dirección inválida, MacsBug mostraba un error de bus durante el read-modify-write en A4+4
    • A1 también cambiaba, aunque una CAS válida no debería modificarlo

Verificación en hardware real de Classic II

  • Para comprobar la hipótesis, se compró y reparó una Classic II fabricada en 1991 y luego se reemplazó la ROM por una EEPROM programable SST29EE010
    • Los capacitores SMD de las Mac antiguas pueden provocar fugas corrosivas, así que se retiraron y repararon antes de encenderla
    • En vez de usar el CRT y la placa analógica, se hizo funcionar la placa lógica por separado y se verificó la pantalla con un conversor VGA basado en Raspberry Pi Pico
  • Se insertó en espacio libre de la ROM código en ensamblador 68030 para mostrar el valor de A1 en pantalla, y se prepararon tres ROM personalizadas
    • ROM personalizada 1: reemplaza el MOVE.B en 0x40A43B9C, que en MAME causaba Sad Mac, por el código que muestra A1
    • ROM personalizada 2: reemplaza en 0x40A43B94 la instrucción parecida a CAS del destino del salto fuera de rango por el código que muestra A1
    • ROM personalizada 3: reemplaza en 0x40A43B94 la instrucción parecida a CAS por NOP
  • La ROM personalizada 1 mostró que ese código sí se ejecuta también en hardware real y que A1 en el punto donde MAME fallaba vale 0x40A4BBB2
    • Ese valor es una dirección de ROM, así que no sería un destino apropiado para escritura, pero aun así intentar escribir ahí no provoca error de bus
  • La ROM personalizada 2 mostró que justo antes del salto fuera de rango A1 vale 0xFFFF8FBA, el mismo valor observado en MAME
    • Eso confirma que el salto fuera de rango realmente ocurre
    • Y encaja con la hipótesis de que la instrucción parecida a CAS cambia A1 después
  • La ROM personalizada 3 mostró que, al eliminar la instrucción parecida a CAS, la Classic II real también falla con Sad Mac
    • En esta prueba, el hardware mostró el mismo Sad Mac incluso en modo de 24 bits
    • Que MAME no generara error de bus por la escritura inválida en modo de 24 bits significaba que su comportamiento era más permisivo que el del hardware real

Problemas pendientes en MAME y la emulación

  • El comportamiento descubierto es un caso en el que una instrucción no documentada del MC68030 realiza un ciclo de bus read-modify-write y además modifica el registro A1
  • El bug de la ROM de la Classic II quedó oculto gracias a ese comportamiento, y por eso la máquina real funcionaba normalmente
  • El mismo fragmento de código también apareció en la ROM más nueva de la Macintosh IIvx, pero ahí la tabla de saltos fue ampliada y el caso de la Classic II salta directamente a RTS
  • Por este comportamiento, parece posible que todavía no exista un emulador o implementación clon 100% perfecta del Motorola MC68030
    • Ejecutar esta instrucción y revisar el resultado en A1 podría servir para crear un pequeño programa que distinga un 68030 físico de un emulador
  • Hasta que se descubra el comportamiento exacto de la instrucción, MAME permite el arranque aplicando un parche al bug de la ROM de la Classic II
  • Por separado, la combinación command+power sí funciona en una Classic II real cuando MacsBug está instalado, pero en MAME todavía no funciona

1 comentarios

 
GN⁺ 2025-01-27
Comentarios en Hacker News
  • El comportamiento observado del MC68030 podría no ser una instrucción “real” creada intencionalmente por los diseñadores del CPU, sino el resultado de que la lógica interna del CPU ejecutara algo por accidente al recibir una entrada don’t-care ante una instrucción ilegal
    Normalmente, el CPU debería detectar una instrucción ilegal y lanzar una excepción, pero en algunos casos parece que no lo hace
    En las páginas 8-9 de https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf se indica que las instrucciones ilegales se definen según el patrón de bits de la “primera palabra”
    Esta instrucción tiene 3 palabras y la primera es válida, mientras que los bits extraños están en la segunda, así que es muy probable que el 68030 no validara la segunda palabra y siguiera adelante con la lógica de implementación de la instrucción CAS

    • El CPU falso que aprendí en una clase universitaria de arquitectura de computadoras dividía los códigos de instrucción en varios campos de bits, y ciertos bits activaban operaciones primitivas como el sumador
      Los códigos de instrucción “válidos” eran solo combinaciones lo bastante útiles como para documentarlas, y las combinaciones “inválidas” eran combinaciones inútiles o sin sentido
      Visto así, los códigos de instrucción ilegales o misteriosos no son una excepción sorprendente, sino algo casi inevitable
    • No soy especialista en 68k, pero parece que solo la primera palabra cuenta como código de instrucción, y la segunda sirve para elegir el registro D que usa CAS
      Normalmente uno esperaría que los bits en 0 sin significado en la documentación se ignoraran por completo
      Pero en el 68030 puede que esos bits sí tuvieran que ser 0, y que, igual que con los códigos de instrucción ilegales del 6502, al activarse encendieran lógica cableada para otra instrucción
  • CAS siempre fue un dolor de cabeza, y probablemente fue la instrucción que más reportes de bugs de emulación generó
    En un viejo King of Fighters también había un bug que revisaba “mal” el carry flag de la instrucción SBCD, y lo usaban para reducir el temporizador de la ronda y detectar su final
    Aunque sea un comportamiento no documentado, si no se emulan los flags de estado aritmético en operaciones decimales codificadas en binario, el temporizador de ronda de KOF sigue ciclando de 00 a 99 y nunca termina
    SNK realmente eran los dioses del chip 68000

    • Como fan de las arcades retro y del 68k, me gustaría oír más sobre cómo SNK llevó el 68K al límite
  • Vaya viaje tan largo
    Hoy en día ya no tengo la paciencia para meterme tan hondo en la madriguera del conejo como el autor, pero conecto mucho con esa sensación de logro que hay entre creer que entiendes algo y realmente entenderlo

    • Como autor, tiendo a caer en este tipo de análisis profundos cuando descubro algo así
      Aunque tome mucho tiempo, cuando llegas hasta el final de verdad vale la pena
  • Incluso ahora, más de 30 años después, siempre me sorprende lo eficaz que podía ser la UI del depurador de Mac con una resolución de pantalla tan pequeña
    Realmente se nota la artesanía

  • Casi todos los CPU tienen instrucciones no documentadas, y el 68k no es la excepción
    Lo que pasa es que la mayoría de la gente con suficiente interés y conocimiento de bajo nivel en esa época estaba enfocada en x86/PC, y podría decirse que x86/PC era una arquitectura mucho más abierta y estable que la del lado de Apple
    El microcódigo de los 8088 y 8086 fue desensamblado y bastante estudiado hace algunos años, y según entiendo también hubo intentos de simulación a nivel de transistores
    La estructura del espacio de códigos de instrucción de x86 también se ha explorado en detalle en documentos como estos
    http://ref.x86asm.net/geek.html
    https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...
    Todavía no se sabe exactamente qué hace esta instrucción
    En pruebas limitadas, parecía que el valor resultante en A1 cambiaba según el valor original de A1, el valor de A7 y el program counter, pero no está claro
    Si alguien hiciera un programa que probara distintos valores de registros y contenidos de memoria para inferir el comportamiento exacto, también podría mejorarse la emulación
    Por ahora, MAME está parcheando este bug del ROM para que arranque el Classic II, al menos hasta que alguien considere que vale la pena investigarlo a fondo
    Personalmente, creo que sí vale la pena aclararlo para lograr una emulación precisa
    No conozco mucho 68k, pero los bits de la instrucción dan pistas. Mi hipótesis es que los bits 5:3 de la segunda palabra parecen otro campo de modo; en vez de que el modo 000 seleccione un registro Dn, 101 vuelve a seleccionar (d16, An), y el campo Dc con 001 parece interpretarse como A1

    • Corrección menor: por supuesto, 68k no era la arquitectura de Apple
      El 68030 era un muy buen CPU de propósito general en esa época, y también se usó en la Amiga 3000, Atari Falcon, Sun 3, NeXT Cube y otros
    • Cuando el Mac Classic era relevante, la PC todavía no era tan dominante en la computación doméstica, y tampoco había tanta gente en el mundo x86/PC investigando estas cosas
      En la demoscene todavía seguían muy metidos con las computadoras domésticas de 8 bits, y quienes usaban sistemas domésticos de 16 bits se enfocaban en Atari y Amiga
      PC y x86 despegaron de verdad en el hogar cuando VGA y las tarjetas de sonido pasaron a formar parte de la configuración estándar de la PC
  • Cuando era niño tenía una Amiga 2000 con procesador 68000
    Me emocioné muchísimo cuando salieron noticias del 68020 y el 68030, y después me pasó lo mismo con las arquitecturas RISC
    Si hacías que la Amiga dijera con voz robótica una frase como “Hello, how are you?”, tus amigos se sorprendían como si hubieras ido a la luna y vuelto
    Nunca imaginé que menos de 40 años después estaríamos conversando con computadoras en lenguaje natural mediante LLM, y automatizando casi cualquier cosa con Python, VS Code y LLM como herramientas
    Vivimos tiempos de locos

  • Extraño la familia 68000
    Eran chips realmente excelentes

    • Extraño el ensamblador del 68000
      Era claro y lógico, como siempre debería ser la gran ingeniería, y con solo hojearlo podías entender de inmediato qué estaba pasando
      En cambio, el ensamblador x86 casi siempre parece un montón de trucos ingeniosos salidos de una mala noche en vela, puro desorden
      Eso de restarle un registro a sí mismo para obtener 0 ya es demasiado
  • Da curiosidad si esto era una protección anticopia para impedir que corriera en cierto sistema, o si es algo que pasa en todos los 68030

    • Por lo que dice el texto, no parece que fuera protección anticopia
      Faltaba en esa tabla de saltos la entrada correspondiente a esa máquina, y probablemente quienes trabajaron en la ROM simplemente olvidaron agregar la nueva entrada; al parecer funcionó aun sin esa entrada por pura casualidad
    • Apple ya venía intentando poco a poco la obsolescencia programada
  • En las Mac actuales, algo así parece imposible de aquí en adelante
    La documentación técnica de Apple hoy en día es pésima

    • Creo que la razón por la que Apple no ofrece documentación de hardware a ese nivel es que las Mac modernas no tienen una capacidad de expansión equivalente
      Ya no exponen como conector un bus del sistema donde el usuario pudiera insertar tarjetas y los desarrolladores externos tuvieran que interactuar directamente con el hardware, como antes
      En las Mac modernas están USB y Thunderbolt, que pueden manejarse desde programas en espacio de usuario
      Claro, eso no quita que parte de la documentación de las API recientes de macOS sea muy deficiente
      Cuando llegas a una página vieja con un encabezado en degradado azul que dice “Apple documentation archive”, sabes que encontraste algo genial
    • En el entorno moderno, creo que es mucho menos probable que un bug así llegue al firmware o software publicado
      El espacio de direcciones es muchísimo más grande y la mayoría de las direcciones no están mapeadas, así que una operación de memoria sobre una dirección basura por lo general fallaría, y una instrucción inválida probablemente también
      Un bug que lea una tabla de saltos con un índice fuera de rango todavía puede existir en el software moderno, pero en un caso así es mucho más probable que el proceso se caiga en vez de seguir corriendo
      Como referencia, WebAssembly tiene un único espacio de direcciones lineal y todas las direcciones permiten lectura y escritura, así que este tipo de bug es más posible
      Si una dirección basura cae dentro del rango, puede hacer una lectura, escritura o CAS incorrectos y aun así seguir de largo
      Aun así, una instrucción inválida como la del artículo haría que fallara la carga del módulo WASM, así que no es exactamente un equivalente 1:1 de este problema en la ROM de Mac
  • Da curiosidad si los 040/060 también soportan esta “instrucción no documentada”

    • En el 040 sí parece hacer algo donde participa D1
      No tocó A1 para nada, y no hice más pruebas, pero podría ser que simplemente lo trate como un CAS normal
      La primera vez que ejecuté paso a paso esa instrucción en MacsBug de una LC 475, dio error de sistema, pero después estuvo bien
    • Lo revisé en una Amiga con 060, y esa instrucción no parece modificar ningún registro A
      Eso sí, fue una prueba muy rápida exactamente con esa palabra de instrucción