2 pontos por GN⁺ 2025-01-27 | 1 comentários | Compartilhar no WhatsApp
  • O Mac Classic II chegou perto de escrever em um endereço incorreto por causa de um bug de salto fora do intervalo na ROM, mas a CPU Motorola MC68030 real executou uma instrução não documentada e alterou o registrador A1, evitando o travamento
  • O problema apareceu na emulação do Classic II no MAME quando o endereçamento de 32 bits foi ativado, exibindo Sad Mac e Chimes of Death; com endereçamento de 24 bits, parecia inicializar normalmente
  • O caminho InstallSoundIntHandler na ROM aplicava o BoxFlag 17 do Classic II a uma tabela de desvios com 16 entradas e saltava para 0x40A43B94, posição que originalmente ficava no meio de uma instrução MOVEA.L
  • A sequência de bytes 0C EC 08 A9 00 04 parecia algo como CAS D1,D2,$0004(A4), mas era uma instrução inválida da família CAS com bits reservados ativados; no 68030 real, ela alterava A1 de FFFF8FBA para 40A4BBB2
  • A verificação com três ROMs customizadas em um Classic II real mostrou que, ao remover essa instrução e substituí-la por NOP, o hardware também falhava com Sad Mac; até descobrir o comportamento correto, o MAME contornou a inicialização com um patch na ROM

Falha de inicialização do Classic II revelada no MAME

  • O MAME é mais conhecido como emulador de jogos de arcade, mas também oferece suporte à emulação de modelos de Mac baseados em 68000
  • Durante uma modificação no MAME para ajustar o recurso que chama o depurador com a combinação command+power, foi descoberto um comportamento estranho no Classic II
    • O Classic II tem um botão físico de interrupção e também um microcontrolador “Egret” 68HC05 que cuida de teclado, mouse etc.
    • O MacsBug contém código para enviar o comando que ativa a combinação command+power
  • O Classic II inicializava normalmente em endereçamento de 24 bits, mas ao ativar o endereçamento de 32 bits necessário para carregar o MacsBug, surgiam o Sad Mac e os Chimes of Death
  • Segundo a documentação da Apple Tech Info Library, o código Sad Mac 0000000F indica exceção, e 00000001 indica erro de barramento
    • Em Macs 68k, erro de barramento normalmente significa acesso a endereço inválido, como tentar acessar uma placa de expansão inexistente

Rastreamento da ROM: caminho de inicialização da interrupção de som

  • No depurador do MAME, foi colocado um breakpoint no endereço do handler de erro de barramento 0x40A026F0, e o caminho de chamadas foi rastreado usando o mapa de símbolos da ROM
    • Esse endereço aparece como GenExcps no mapa da ROM
    • A rotina comum de tratamento de erro aparece com o nome ToDeepShit
  • A instrução que provocava o erro de barramento era o código a seguir em 0x40A43B9C
move.b #$90, ($1c00,A1)
  • Esse código estava dentro da rotina identificada no mapa da ROM como InstallSoundIntHandler e estava relacionado à inicialização da interrupção de som do Classic II
  • O Classic II, na verdade, é estruturalmente mais próximo da linha Macintosh LC do que do Macintosh Classic original, usando uma gate array EAGLE semelhante ao chip V8 do LC
    • É por isso que nomes como V8SndIntPatch1 aparecem no código da ROM
    • O EAGLE inclui funcionalidades equivalentes a uma versão reduzida do Apple Sound Chip (ASC)

Tabela de desvios incorreta e o problema do registrador A1

  • V8SndIntPatch1 verifica os atributos de hardware usando o seletor gestaltHardwareAttr do trap Gestalt, isto é, 'hdwr'
    • Se o bit gestaltHasASC não estiver presente, ele retorna
    • No Classic II, gestaltHasASC está definido
  • Em seguida, o código lê o valor de BoxFlag em 0xCB3 na RAM e o coloca em D0
    • No Classic II, o valor de BoxFlag é 0x11, ou seja, 17
  • O desvio problemático dobrava D0 e o usava como offset de salto relativo ao PC
add.w d0,d0
jmp loc_40A43B72(pc,d0.w)
  • Como cada instrução BRA.S ocupa 2 bytes, a estrutura dobrava o valor de BoxFlag para usá-lo como índice na tabela de desvios
  • Porém, a tabela real tinha apenas 16 entradas, correspondentes aos BoxFlags 0 a 15; o valor 17 do Classic II ficava fora do intervalo
    • O destino calculado do salto era 0x40A43B72 + 0x22 = 0x40A43B94
    • Essa posição não era o início de uma instrução pretendida, mas o meio de uma instrução MOVEA.L
  • A MOVEA.L original deveria carregar um endereço correto em A1, mas como o salto caiu no meio da instrução, A1 manteve o valor 0xFFFF8FBA usado no cálculo anterior do salto relativo
  • Em seguida, quando move.b #$90, ($1c00,A1) era executada, o endereço de escrita virava 0xFFFF8FBA + 0x1C00 = 0xFFFFABBA, um endereço inválido no Classic II

Comportamento não documentado do 68030

  • A sequência de bytes alcançada pelo salto fora do intervalo era a seguinte
0C EC 08 A9 00 04
  • Nem o IDA nem o GNU objdump conseguiram desmontar corretamente a instrução nessa posição
  • Ao executar a mesma sequência de bytes no MacsBug de um Macintosh IIci, ela foi exibida como algo parecido com CAS.W D1,D2,$0004(A4), mas o valor de A1 mudava após a execução
    • A1 passava de 0xFFFF8FBA para um valor que parecia um endereço de RAM
    • Foi observado que o novo valor de A1 variava conforme os valores originais de A1, A7 e do contador de programa
  • Segundo o Motorola M68000 Family Programmer’s Reference Manual, a primeira word parece uma instrução CAS, mas na segunda word há 3 bits que deveriam ser 0 e estão definidos como 1
  • Portanto, essa instrução não era um CAS válido documentado, mas uma instrução inválida com bits reservados ativados
  • O 68030 real não tratava essa instrução como exceção de illegal instruction e executava um ciclo de barramento read-modify-write sobre A4+4
    • Se A4 fosse configurado com um endereço inválido, o MacsBug mostrava erro de barramento durante o read-modify-write em A4+4
    • A1 também era alterado, embora um CAS normal não devesse modificá-lo

Verificação em hardware real do Classic II

  • Para testar a hipótese, foi comprado e reparado um Classic II fabricado em 1991, e a ROM foi substituída por uma EEPROM SST29EE010 programável
    • Capacitores SMD em Macs antigos podem vazar substâncias corrosivas, então eles foram removidos e reparados antes de ligar o equipamento
    • Em vez de usar o CRT e a placa analógica, a placa lógica foi acionada separadamente, e a imagem foi verificada com um conversor VGA baseado em Raspberry Pi Pico
  • Foi inserido em um espaço vazio da ROM um código em assembly 68030 para mostrar o valor de A1 na tela, e três ROMs customizadas foram produzidas
    • ROM customizada 1: substitui a MOVE.B em 0x40A43B9C, que causava Sad Mac no MAME, pelo código de exibição de A1
    • ROM customizada 2: substitui a instrução parecida com CAS em 0x40A43B94, destino do salto fora do intervalo, pelo código de exibição de A1
    • ROM customizada 3: substitui a instrução parecida com CAS em 0x40A43B94 por NOP
  • A ROM customizada 1 mostrou que esse código realmente é executado no hardware real e que A1 no ponto em que o MAME travava vale 0x40A4BBB2
    • Esse valor é um endereço de ROM, então não é adequado como destino de escrita, mas mesmo assim a tentativa de escrita não gera erro de barramento
  • A ROM customizada 2 mostrou que, imediatamente antes do salto fora do intervalo, A1 tinha o mesmo valor visto no MAME: 0xFFFF8FBA
    • O salto fora do intervalo realmente acontece
    • Isso bate com a hipótese de que a instrução parecida com CAS altera A1 em seguida
  • A ROM customizada 3 mostrou que, ao remover a instrução parecida com CAS, o Classic II real também falha com Sad Mac
    • Nesse teste, o hardware apresentou o mesmo Sad Mac até em modo de 24 bits
    • O fato de o MAME não gerar erro de barramento para essa escrita incorreta no modo de 24 bits significava que ele estava sendo mais permissivo do que o hardware real

Problemas que continuam no MAME e na emulação

  • O comportamento descoberto é um caso em que uma instrução MC68030 não documentada executa um ciclo de barramento read-modify-write e ainda altera o registrador A1
  • O bug na ROM do Classic II ficou escondido graças a esse comportamento, e por isso a máquina real funcionava normalmente
  • O mesmo trecho de código também foi encontrado em ROMs mais novas do Macintosh IIvx, mas ali a tabela de saltos foi ampliada e o caso do Classic II salta diretamente para RTS
  • Por causa disso, parece provável que ainda não exista um emulador ou implementação clonada 100% perfeita do Motorola MC68030
    • Dá para criar um pequeno código que execute essa instrução e verifique o resultado em A1 para distinguir um 68030 físico de um emulador
  • Até que o comportamento exato da instrução seja esclarecido, o MAME permite a inicialização aplicando um patch no bug da ROM do Classic II
  • Separadamente, a combinação command+power funciona em um Classic II real quando o MacsBug está instalado, mas ainda não funciona no MAME

1 comentários

 
GN⁺ 2025-01-27
Opiniões no Hacker News
  • O comportamento descoberto no MC68030 talvez seja menos uma instrução “real” criada intencionalmente pelos projetistas da CPU e mais o resultado de a lógica interna da CPU ter sido executada por acaso, com entradas don’t-care, diante de uma instrução ilegal
    Normalmente, a CPU deveria detectar uma instrução ilegal e gerar uma exceção, mas parece que em certos casos isso não acontece
    Nas páginas 8-9 de https://www.nxp.com/docs/en/reference-manual/MC68030UM.pdf, diz-se que uma instrução ilegal é definida pelo padrão de bits da “primeira word”
    Esta instrução tem 3 words, a primeira word é normal, e os bits estranhos estão na segunda word; portanto, é bem provável que o 68030 não tenha validado a segunda word e tenha simplesmente seguido em frente com a lógica de implementação da instrução CAS

    • A CPU de brinquedo que aprendi em uma disciplina de arquitetura de computadores na faculdade dividia o código de instrução em vários campos de bits, e determinados bits ativavam operações primitivas, como um somador
      Os códigos de instrução “válidos” eram apenas combinações úteis o suficiente para serem documentadas, enquanto as combinações “inválidas” eram inúteis ou sem sentido
      Visto assim, códigos de instrução ilegais/misteriosos não são uma exceção surpreendente, mas algo quase inevitável
    • Não sou especialista em 68k, mas parece que apenas a primeira word é tratada como código de instrução, enquanto a segunda word serve para escolher o registrador D a ser usado pelo CAS
      Normalmente, eu esperaria que os bits 0 que não têm significado na documentação fossem completamente ignorados
      Mas, no 68030, talvez esses bits precisem ser 0; e, como nos opcodes ilegais do 6502, quando esses bits são ligados, alguma lógica hardwired de outra instrução pode acabar sendo ativada
  • CAS sempre foi uma dor de cabeça, e provavelmente foi a instrução que mais recebeu relatos de bugs em emulação
    No antigo King of Fighters também havia um bug que verificava “errado” o carry flag da instrução SBCD, usando isso para diminuir o timer do round e encerrar o round
    Mesmo sendo um comportamento não documentado, se os flags de estado aritmético em operações BCD não forem emulados, o timer de round do KOF fica circulando de 00 para 99 indefinidamente e nunca termina
    A SNK realmente era formada por deuses do chip 68000

    • Como fã de arcades retrô e de 68k, eu adoraria ouvir mais sobre como a SNK explorou o 68K ao limite
  • É uma jornada realmente longa
    Hoje em dia não tenho a paciência de descer tão fundo na toca do coelho como o autor, mas me identifico muito com a sensação de realização que vem da diferença entre achar que se sabe algo e realmente saber

    • Como autor, quando descubro algo assim, tendo a cair em uma análise aprofundada bem grande
      Mesmo tomando muito tempo, quando se vai até o fim é realmente recompensador
  • Mesmo mais de 30 anos depois, sempre me surpreende como a UI do debugger do Mac conseguia ser tão eficaz em uma resolução de tela tão pequena
    Dá mesmo para sentir o nível de artesanato ali

  • Quase toda CPU tem instruções não documentadas, e o 68k não é exceção
    A questão é que, na época, a maioria das pessoas com interesse suficiente e conhecimento de baixo nível estava concentrada em x86/PC, e dá para dizer que x86/PC era uma arquitetura muito mais aberta e estável do que o lado da Apple
    O microcódigo do 8088 e do 8086 foi desassemblado alguns anos atrás e bastante estudado, e sei que também houve tentativas de simulação em nível de transistor
    A estrutura do espaço de opcodes x86 também já foi explorada em detalhes em documentos como estes
    http://ref.x86asm.net/geek.html
    https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781...
    Ainda não sabemos exatamente o que essa instrução faz
    Em testes limitados, o valor resultante de A1 parecia variar conforme o valor original de A1, o valor de A7 e o contador de programa, mas não tenho certeza
    Se alguém criar um programa que experimente vários valores de registradores e conteúdos de memória para inferir o comportamento exato, a emulação também poderá ficar mais precisa
    Por enquanto, até alguém considerar que vale a pena investigar, o MAME está corrigindo esse bug da ROM para fazer o Classic II inicializar
    Pessoalmente, acho que vale a pena esclarecer isso em nome de uma emulação precisa
    Não sou familiarizado com 68k, mas os bits da instrução dão pistas. A hipótese é que os bits 5:3 da segunda word parecem outro campo de modo; em vez de o modo 000 selecionar o registrador Dn, o 101 selecionaria novamente (d16, An), e o campo Dc contendo 001 parece ser interpretado como A1

    • Uma pequena correção: o 68k obviamente não era uma arquitetura da Apple
      O 68030 era uma CPU de uso geral bastante boa na época e também foi usado no Amiga 3000, Atari Falcon, Sun 3, NeXT Cube etc.
    • Na época em que o Mac Classic era relevante, o PC ainda não era tão dominante na computação doméstica, e não havia tanta gente investigando esse tipo de coisa no lado x86/PC
      Na demoscene, muita gente ainda se mantinha nos computadores domésticos de 8 bits, e quem usava sistemas domésticos de 16 bits se concentrava no Atari e no Amiga
      O PC e o x86 só começaram a decolar de verdade em casa depois que VGA e placas de som passaram a fazer parte da configuração padrão de um PC
  • Quando eu era criança, tinha um Amiga 2000 com processador 68000
    Fiquei muito empolgado quando ouvi falar do 68020 ou do 68030, e depois foi a mesma coisa com as arquiteturas RISC
    Quando eu fazia o Amiga dizer frases como “Hello, how are you?” com aquela voz robótica, meus amigos ficavam maravilhados como se eu tivesse ido à Lua
    Eu jamais teria imaginado que, menos de 40 anos depois, estaríamos conversando com computadores em linguagem natural por meio de LLMs e automatizando praticamente qualquer coisa que quiséssemos usando Python, VS Code e LLMs como ferramentas
    É uma época realmente insana

  • Sinto falta da família 68000
    Eram chips realmente excelentes

    • Sinto falta do assembly do 68000
      Era claro e lógico, como toda boa engenharia deveria ser, então dava para entender imediatamente o que estava acontecendo só de passar os olhos
      Em contraste, assembly x86 parece, na maior parte, uma bagunça pura, como um monte de truques espertos saídos de uma noite mal dormida
      Subtrair um registrador dele mesmo para obter 0 é demais
  • Fico curioso se isso era uma proteção contra cópia para impedir que rodasse em determinados sistemas, ou se era um fenômeno que acontece em todos os 68030

    • Pelo conteúdo do texto, acho que não era proteção contra cópia
      A tabela de saltos envolvida aqui não tinha uma entrada para aquela máquina, e parece que os responsáveis pela ROM provavelmente esqueceram completamente de adicionar a nova entrada, mas, por puro acaso, funcionou mesmo sem a entrada na tabela
    • A Apple já vinha fazendo pequenas tentativas de obsolescência programada
  • Nos Macs de hoje, algo assim parece impossível daqui para frente
    A documentação técnica atual da Apple é péssima

    • Acho que a razão pela qual a Apple não fornece documentação de hardware nesse nível é que os Macs modernos não têm uma capacidade de expansão equivalente
      Eles não expõem, por meio de conectores, um barramento de sistema no qual o usuário pudesse encaixar placas e desenvolvedores terceiros precisassem interagir diretamente com o hardware, como antigamente
      Nos Macs modernos, há USB e Thunderbolt, que programas em espaço de usuário podem lidar
      Claro, não nego que a documentação de algumas APIs recentes do macOS seja muito ruim
      Quando você chega a uma página antiga com um degradê azul no cabeçalho e o texto “Apple documentation archive”, sabe que encontrou algo legal
    • Acho que, no ambiente moderno, a probabilidade de um bug desses entrar em firmware ou software lançado é muito menor
      O espaço de endereçamento é muito maior e a maioria dos endereços não é mapeada, então uma operação de memória em um endereço lixo geralmente falharia, e uma instrução inválida provavelmente também falharia
      O bug em si, de ler uma tabela de saltos com um índice fora do intervalo, ainda pode acontecer em software moderno, mas é mais provável que o processo morra do que continue rodando como aqui
      Como observação, WebAssembly tem um único espaço de endereçamento linear e todos os endereços podem ser lidos e escritos, então esse tipo de bug é mais possível
      Se o endereço lixo estiver dentro do intervalo, dá para fazer uma leitura, uma escrita ou um CAS incorretos e ainda seguir em frente
      Ainda assim, uma instrução inválida como a do texto faria o carregamento do módulo WASM falhar, então não é exatamente igual a esse problema na ROM do Mac
  • Fico curioso se o 040/060 também suporta essa “instrução não documentada”

    • No 040, ela de fato parece fazer algo envolvendo D1
      A1 não foi tocado de forma alguma e, embora eu não tenha testado mais, talvez ela simplesmente seja tratada como um CAS normal
      Quando executei essa instrução passo a passo pela primeira vez no MacsBug de um LC 475, houve um erro de sistema, mas depois disso ficou tudo bem
    • Verifiquei em um Amiga com 060, e essa instrução não parece modificar nenhum registrador A
      Foi apenas um teste muito rápido exatamente com essa word de instrução