A emulação é uma área multifacetada. Aqui estão as idéias básicas e os componentes funcionais. Vou dividi-lo em pedaços e depois preencher os detalhes através de edições. Muitas das coisas que vou descrever exigirão conhecimento do funcionamento interno dos processadores - é necessário conhecimento de montagem. Se eu sou um pouco vago em certas coisas, faça perguntas para que eu possa continuar a melhorar esta resposta.
Ideia básica:
A emulação funciona manipulando o comportamento do processador e dos componentes individuais. Você constrói cada peça individual do sistema e as conecta da mesma forma que os fios do hardware.
Emulação do processador:
Existem três maneiras de lidar com a emulação de processador:
- Interpretação
- Recompilação dinâmica
- Recompilação estática
Com todos esses caminhos, você tem o mesmo objetivo geral: executar um pedaço de código para modificar o estado do processador e interagir com o 'hardware'. O estado do processador é uma conglomeração dos registros do processador, manipuladores de interrupção etc. para um determinado destino do processador. Para o 6502, você teria um número de inteiros de 8 bits que representam registros: A
, X
, Y
, P
, e S
; você também teria um PC
registro de 16 bits .
Com a interpretação, você começa no IP
(ponteiro da instrução - também chamado de PC
contador de programa) e lê a instrução da memória. Seu código analisa essas instruções e usa essas informações para alterar o estado do processador, conforme especificado pelo seu processador. O principal problema com a interpretação é que é muito lento; toda vez que você lida com uma determinada instrução, é necessário decodificá-la e executar a operação necessária.
Com a recompilação dinâmica, você repete o código da mesma forma que a interpretação, mas, em vez de apenas executar opcodes, cria uma lista de operações. Depois de chegar a uma instrução de ramificação, você compila essa lista de operações para codificar a máquina para sua plataforma host, depois armazena em cache esse código compilado e o executa. Então, quando você atingir um determinado grupo de instruções novamente, precisará executar o código apenas do cache. (Na verdade, a maioria das pessoas não faz uma lista de instruções, mas as compila para o código da máquina em tempo real - isso torna mais difícil a otimização, mas está fora do escopo desta resposta, a menos que haja um número suficiente de pessoas interessadas)
Com a recompilação estática, você faz o mesmo que na recompilação dinâmica, mas segue as ramificações. Você acaba criando um pedaço de código que representa todo o código do programa, que pode ser executado sem mais interferências. Esse seria um ótimo mecanismo se não fosse pelos seguintes problemas:
- O código que não está no programa para começar (por exemplo, compactado, criptografado, gerado / modificado em tempo de execução, etc.) não será recompilado e, portanto, não será executado
- Está provado que encontrar todo o código em um dado binário é equivalente ao problema da parada
Eles se combinam para tornar a recompilação estática completamente inviável em 99% dos casos. Para mais informações, Michael Steil fez uma grande pesquisa sobre recompilação estática - a melhor que já vi.
O outro lado da emulação de processador é a maneira como você interage com o hardware. Isso realmente tem dois lados:
- Tempo do processador
- Manuseio de interrupção
Tempo do processador:
Certas plataformas - especialmente os consoles mais antigos, como o NES, SNES etc. - exigem que seu emulador tenha um tempo estrito para ser completamente compatível. Com o NES, você possui a PPU (unidade de processamento de pixels), que exige que a CPU coloque pixels na memória em momentos precisos. Se você usa a interpretação, pode contar facilmente ciclos e emular o tempo adequado; com a recompilação dinâmica / estática, as coisas são muito mais complexas.
Manuseio de interrupção:
Interrupções são o principal mecanismo que a CPU se comunica com o hardware. Geralmente, seus componentes de hardware informam à CPU o que a interrompe. Isso é bem direto - quando seu código lança uma determinada interrupção, você olha para a tabela do manipulador de interrupções e chama o retorno de chamada apropriado.
Emulação de hardware:
Existem dois lados para emular um determinado dispositivo de hardware:
- Emulando a funcionalidade do dispositivo
- Emulando as interfaces reais do dispositivo
Veja o caso de um disco rígido. A funcionalidade é emulada através da criação de armazenamento de backup, rotinas de leitura / gravação / formato, etc. Essa parte geralmente é muito simples.
A interface real do dispositivo é um pouco mais complexa. Geralmente, é uma combinação de registros mapeados na memória (por exemplo, partes da memória que o dispositivo observa para alterações na sinalização) e interrompe. Para um disco rígido, você pode ter uma área mapeada na memória onde coloca comandos de leitura, gravações etc., e depois lê esses dados novamente.
Eu entraria em mais detalhes, mas há um milhão de maneiras pelas quais você pode seguir com isso. Se você tiver alguma dúvida específica aqui, não hesite em perguntar e eu adicionarei as informações.
Recursos:
Acho que dei uma introdução muito boa aqui, mas há várias áreas adicionais. Estou mais do que feliz em ajudar com qualquer dúvida; Eu tenho sido muito vago na maior parte disso simplesmente devido à imensa complexidade.
Links obrigatórios da Wikipedia:
Recursos gerais de emulação:
- Zophar - Foi aqui que comecei com a emulação, primeiro baixando emuladores e, eventualmente, pilhando seus imensos arquivos de documentação. Este é o melhor recurso absoluto que você pode ter.
- NGEmu - Não há muitos recursos diretos, mas seus fóruns são imbatíveis.
- RomHacking.net - A seção de documentos contém recursos sobre arquitetura de máquina para consoles populares
Projetos de emulador para referência:
- IronBabel - Esta é uma plataforma de emulação para .NET, escrita em Nemerle e recompila o código para C # rapidamente . Disclaimer: Este é o meu projeto, então perdoe o plugue sem vergonha.
- BSnes - Um emulador SNES incrível com o objetivo de precisão de ciclo perfeito.
- MAME - O emulador de arcade. Ótima referência.
- 6502asm.com - Este é um emulador JavaScript 6502 com um pequeno fórum interessante.
- dynarec'd 6502asm - Este é um pequeno truque que fiz ao longo de um dia ou dois. Peguei o emulador existente no 6502asm.com e mudei-o para recompilar dinamicamente o código para JavaScript para obter grandes aumentos de velocidade.
Referências de recompilação do processador:
- A pesquisa sobre recompilação estática realizada por Michael Steil (referenciada acima) culminou neste artigo e você pode encontrar fontes e outras aqui .
Termo aditivo:
Já faz mais de um ano que essa resposta foi enviada e com toda a atenção que recebi, achei que era hora de atualizar algumas coisas.
Talvez a coisa mais emocionante na emulação agora seja o libcpu , iniciado pelo mencionado Michael Steil. É uma biblioteca destinada a suportar um grande número de núcleos de CPU, que usam o LLVM para recompilação (estática e dinâmica!). Ele tem um enorme potencial e acho que fará grandes coisas para emular.
O emu-docs também foi trazido à minha atenção, que abriga um ótimo repositório de documentação do sistema, que é muito útil para fins de emulação. Não passei muito tempo lá, mas parece que eles têm muitos recursos excelentes.
Fico feliz que este post tenha sido útil e espero poder sair do meu lugar e terminar meu livro sobre o assunto até o final do ano / início do próximo ano.