Os emuladores analisam código binário em arquivos?

Os emuladores analisam código binário em arquivos?

Tenho visto alguns emuladores que afirmam que são executados e, mesmo que o façam, seu código-fonte mostra que eles não analisam diretamente cada 1 e 0 para determinar uma instrução.

Minha pergunta é: se o emulador deve emular os opcodes exatos que a CPU real faria, não seria necessário analisar o formato de opcode binário correto de um jogo para emular a CPU legitimamente (ou de todo)?

Por exemplo, no arquivo do jogo eu armazeno uma instrução, um byte, marcada da seguinte forma:

0000 1111

Meu programa deve verificar se esta instrução realmente significa (por exemplo, "adicionar um ao registrador A"), mas não seria necessário verificar cada zero e um no arquivo de texto para garantir isso?

Então, os emuladores analisariam bytes inteiros, mas os bytes inteiros, novamente, são oito bits, e os padrões flutuantes alteram a saída da operação.

Por exemplo, 0000 1111 pode significar adicionar um a A, mas 0000 1110 pode significar adicionar A com A.

Responder1

Exposição - tentando responder diretamente à pergunta

Se você estiver lendo o código-fonte de um emulador e ele não estiver lendo certos bits de um arquivo binário (executável) e ainda estiver executando o código fielmente, haverá três resultados possíveis:

  1. Você éerradoem pensar que o emulador não lê todos os bits do arquivo, ena verdade faz, e você está simplesmente enganado.
  2. Você écorreto, e o emulador não está lendo cada bit, porque é capaz de assumir certos fatos sobre o comportamento do programa que está emulando para não precisar ler cada bit para saber o que precisa fazer (talvez porque espera um determinado mecanismo de jogo a ser executado, ou um certo tipo de API gráfica, ou um certo tipo de API de som, etc.).
  3. Você écorreto, e o emulador não está lendo todos os bits, porque há certos bits do executável que simplesmente não são necessários para executar o programa corretamente. Eles podem ser "cruft" legados, ou metadados, ou qualquer outra coisa que seja simplesmente extra que não consiste na funcionalidade do programa.
  4. Você écorreto, e o emulador não está lendo cada bit, porque o emulador está traduzindo certas operações no código em operações de nível superior e está ignorando completamente as instruções específicas do processador/hardware de baixo nível. Por exemplo, se lhe pedirem para imitar exatamente o que uma pessoa está fazendo em uma gravação de vídeo dessa pessoa realizando uma operação complicada, e ela disser "Agora faça um furo na lateral da caixa", você ficaria bastante tentado a parar assista ao vídeo e use sua experiência existente de como fazer furos nas coisas, em vez de seguir os movimentos literais do cara no vídeo (supondo que você esteja equipado com uma furadeira adequada e geralmente tenha experiência na vida). Da mesma forma, se o emulador puder deduzir que o programa está pedindo para desenhar uma imagem 32x32 na tela em um determinado conjunto de coordenadas, ele poderá parar de ler o código assim que entender que imagem é, em que formato a imagem está, e onde desenhá-lo - não é necessário ver como o sistema emulado o está desenhando.

Como funcionam os emuladores

Um emulador que executa código para outra plataforma e/ou CPU (por exemplo,vinho) faz coisas em vários estágios. Algumas etapas são absolutamente necessárias para que o emulador funcione; outras etapas são opcionais e representam possibilidades de otimização de desempenho.

  • Obrigatório: "Analisando" o código executável (código de máquina, MSIL, bytecode Java, etc.)Análiseconsiste em:

    • Lendo cada bit do código executável.
    • Compreender o suficiente do layout/formato (sintaxe) e da finalidade (semântica) de cada bit/byte (ou qualquer outra unidade discreta de medida de informação que você queira usar) do código nativo, para entender o que ele está fazendo.
    • Para entendero queum programa diz, o emulador tem que entender osintaxedo formato binário e osemântica. A sintaxe consiste em coisas como "expressamos números inteiros assinados de 32 bits no formato Least Signed Bit"; a semântica consiste em coisas como "quando o código nativo contém um opcode52, isso significa fazer uma chamada de função."
    • Mnemônico(ajuda você a lembrar por que isso é necessário): Se eu me dediquei a seguir uma receita, se a ignorei totalmente e nemlerisso, é impossível que eu consiga seguir essa receita, a menos que eu tente aleatoriamente um monte de coisas esorteem seguir os mesmos passos que a receita exigiria. Da mesma forma, a menos que você tenha uma simulação de Monte Carlo aleatória que execute instruções aleatórias da CPU até ter a mesma funcionalidade do programa, qualquer emulador terá que entendero quediz o programa.

  • Obrigatório: "Traduzindo" o código analisado (geralmente algum tipo de modelo de dados abstrato, máquina de estado, árvore de sintaxe abstrata ou algo parecido) emalto nívelcomandos (por exemplo, instruções em C ou Java) ounível baixocomandos (por exemplo, instruções de CPU para um processador x86). Comandos de alto nível tendem a ser mais ideais. Por exemplo, se você analisar o fluxo de código de uma longa sequência de instruções da CPU e determinar em alto nível que o que está sendo solicitado é reproduzir um determinado arquivo MP3 do disco, você pode pular toda a emulação de nível de instrução e apenas usar seu software nativo. decodificador de MP3 da plataforma (que pode ser otimizado para o seu processador) para reproduzir o mesmo arquivo MP3. Por outro lado, se você "rastreasse" a execução do programa emulado tão literalmente quanto possível, isso seria mais lento e menos ideal, porque você estaria abrindo mão de grande parte da otimização da qual se beneficia ao executar instruções nativamente.

  • Opcional: "Otimizando" e analisando o fluxo de código de uma grande parte do código do programa emulado, ou de todo o programa, para determinar a sequência completa de execução e construindo um modelo muito detalhado e sofisticado de como seu emulador irá emular esse comportamento com as facilidades da plataforma nativa. O Wine faz isso até certo ponto, mas é ajudado pelo fato de que o código que ele está traduzindo é x86 para x86 (o que significa que em ambos os casos a CPU tem o mesmo conjunto de instruções, então tudo que você precisa fazer é conectar o Windows código para o ambiente externo baseado em UNIX e deixá-lo rodar "nativamente").


Analogia do bolo

Ao considerar o desempenho de um emulador, pense em quantas folhas de papel você precisaria para escrever instruções se estivesse assistindo alguém em um vídeo (com áudio) fazendo um bolo, nos seguintes cenários:

  • Se você nunca antes em sua vida moveu as mãos ou exercitou algum músculo do corpo;(dica: você precisaria de milhares de folhas de papel para documentar as etapas detalhadas do movimento da mão, coordenação olho-mão, inclinação, velocidade, posição, técnicas básicas como agarrar, segurar utensílios, amassar, etc.)

  • Se você tem controle motor básico (consegue andar e se alimentar sozinho), mas nunca antes na vida preparou qualquer alimento;(dica: você precisaria de dezenas de folhas de papel para documentar as etapas individuais e provavelmente precisaria de muita prática para pegar o jeito de coisas como amassar e segurar utensílios desconhecidos, mas seria capaz de documentar isso em muito menos tempo que o caso anterior)

  • Se você nunca fez um bolo antes na vida, mas já preparou alguma comida antes;(dica: você precisaria de algumas folhas de papel, mas não mais que 10; você já estaria familiarizado com medir ingredientes, mexer, etc.)

  • Se você já fez um bolo muitas vezes e está familiarizado com o processo, mas não sabe como fazer esse tipo/sabor específico de bolo(dica: talvez você precise de meia folha de papel para anotar os ingredientes básicos e o tempo que leva no forno, e pronto).

Basicamente, nesses níveis crescentes de “competência do emulador”, o emulador pode fazer mais coisas de nível superior “nativamente” (usando rotinas e procedimentos que já conhece) e tem que fazer menos “rastreamento” (usando rotinas e procedimentos que é seguindo literalmente do programa emulado).

Para colocar essa analogia em termos computacionais, você pode imaginar um emulador queemula o hardware realque o programa emulado seria executado e "rastrearia" fielmente o comportamento desse hardware, talvez até no nível do hardware (circuito); este seriamuitolento em comparação com um emulador que analisa o programa com um nível de sofisticação que entende quando está tentando reproduzir um arquivo de som e pode reproduzir "nativamente" esse arquivo de som sem precisar rastrear as instruções do programa emulado para fazê-lo.


Sobre "rastreamento" (também conhecido como mimetismo mecânico) versus "execução nativa"

Uma última coisa: o rastreamento é lento principalmente porque você precisa usar muita memória para "replicar" componentes muito detalhados e intrincados do que você está emulando e, em vez de apenas executar as instruções na CPU do host, você precisa executarinstruções que executam as instruções(vê o nível de indireção?), o que leva à ineficiência. Se você se esforçasse e emulasse o hardware físico de um sistema de computador, bem como o programa, estaria emulando a CPU, a placa-mãe, a placa de som, etc., que por sua vez "rastrearia" a execução do programa como seu O emulador "rastreia" a execução da CPU e, com tantos níveis de rastreamento, tudo seria extremamente lento e complicado.

Aqui está um exemplo detalhado de onde um emulador não precisaria ler cada bit/byte do programa de entrada para emulá-lo.

Digamos que conhecemos uma API escrita em C ou C++ (os detalhes não são importantes) para um ambiente de software emulado, onde esta API possui uma função void playSound(string fileName). Digamos que sabemos que a semântica desta função é abrir o arquivo no disco, ler seu conteúdo, descobrir em que codificação o arquivo está (MP3? WAV? alguma outra coisa?), e então reproduzi-lo nos alto-falantes no taxa de amostragem e pitch normais/esperados. Se lermos, no código nativo, um conjunto de instruções que diz "entre na rotina playSound para começar a reproduzir o som /home/hello/foo.mp3", podemos parar de ler o código do programa ali mesmo e usar nossoterRotina (otimizada!) Para abrir nativamente esse arquivo de som e reproduzi-lo. Precisamos seguir o processador emulado no nível de instrução? Não, realmente não sabemos, não se confiarmos que sabemos o que a API faz.


Surge uma grande diferença! (problemas em terrenos de alto nível)

É claro que, ao ler um monte de instruções e “inferir” um plano de execução de alto nível, como no exemplo acima, você corre o risco de não conseguirprecisamenteimitar o comportamento do programa original em execução no hardware original. Digamos, por exemplo, que o hardware original pudesse ter limitações de hardware que permitiam reproduzir apenas 8 arquivos de som simultaneamente. Bem, se o seu novo computador pode reproduzir 128 arquivos de som simultaneamente, e você está emulando a playSoundrotina em alto nível, o que o impede de reproduzir mais de 8 arquivos de som por vez? Isso pode causar... comportamento estranho (para melhor ou para pior) na versão emulada do programa. Esses casos podem ser resolvidos por meio de testes cuidadosos ou talvez entendendo muito bem o ambiente de execução original.

Por exemplo, o DOSBox possui um recurso que permite limitar intencionalmente a velocidade de execução do programa DOS emulado, porque alguns programas DOS seriam executados incorretamente se pudessem rodar em velocidade máxima; na verdade, eles dependiam do tempo de clock da CPU para executar na velocidade esperada. Este tipo de "recurso" que limita intencionalmente o ambiente de execução pode ser usado para fornecer uma boa compensação entrefidelidadede execução (isto é, fazer o programa emulado funcionar corretamente) eeficiênciade execução (ou seja, construir uma representação do programa que seja de alto nível o suficiente para que possa ser emulado de forma eficiente com um mínimo de rastreamento).

Responder2

Minha pergunta é: se o emulador deve emular os opcodes exatos que a CPU real faria, não seria necessário analisar o formato de opcode binário correto de um jogo para emular a CPU legitimamente (ou de todo)?

"Analisar" significa "analisar o texto e descobrir o que significa". A sintaxe semelhante ao texto e à linguagem é complexa porque não apenas "palavras" individuais significam coisas, mas o significado pode mudar de acordo com sua posição e proximidade com outras palavras.

Provavelmente, o termo mais preciso a ser aplicado em relação ao que uma CPU faz com um fluxo de instruções (que é muito mais simples que a análise) é "decodificar" - e sim, um emulador deve "decodificar" instruções da mesma maneira. Eu acho que "análise" não é um termo tão ruim, dada a complexidade do conjunto de instruções x86, se você estiver falando sobre isso.

Meu programa deve verificar se esta instrução realmente significa (por exemplo, "adicionar um ao registrador A"), mas não seria necessário verificar cada zero e um no arquivo de texto para garantir isso?

As CPUs realmente não verificam as instruções. Qualquer CPU possui um registro interno que aponta para um local de memória na RAM. A CPU lê, lê mais alguns bytes, se necessário, e então tenta executá-lo. CPUs modernas iniciarão um “processo de exceção” se a instrução for ilegal.

CPUs antigas, como o antigo 6502 de 8 bits, nem faziam isso - algumas instruções ilegais travavam a CPU, outras faziam coisas estranhas e não documentadas. (Algumas pesquisas revelarão uma série de instruções x86 não documentadas encontradas em todas as CPUs do panteão x86 - por exemplo, a CPUIDinstrução existia em algumas CPUs 486 antes da Intel documentá-la oficialmente.)

Um emulador de CPU bom e preciso apenas mastiga cegamente um fluxo de instruções, como uma CPU real faz - embora possa fazer coisas diferentes em condições que causariam um travamento do sistema, como exibir uma caixa de diálogo informando que sua CPU virtual está morta. de bloquear seu emulador.

Além disso, você diz "é necessário verificar cada zero e um no arquivo de texto", mas normalmente não alimenta arquivos de texto dos emuladores. Um emulador de CPU precisa de memória emulada - e como muitas plataformas usam E/S mapeada em memória, dispositivos de E/S emulados também. Emular coisas como o firmware exige que você tenha imagens binárias desse firmware - sejam cópias legais do firmware real ou substitua o firmware de código aberto. Emular uma plataforma inteira (como a "plataforma PC" da qual a CPU x86 é um componente) requer mais do que emulação de CPU.

informação relacionada