La emulación es un área multifacética. Aquí están las ideas básicas y los componentes funcionales. Voy a romperlo en pedazos y luego completar los detalles a través de ediciones. Muchas de las cosas que voy a describir requerirán el conocimiento del funcionamiento interno de los procesadores; el conocimiento del ensamblaje es necesario. Si soy demasiado vago en ciertas cosas, haga preguntas para que pueda continuar mejorando esta respuesta.
Idea básica:
La emulación funciona manejando el comportamiento del procesador y los componentes individuales. Usted construye cada pieza individual del sistema y luego conecta las piezas de manera muy similar a los cables en el hardware.
Emulación del procesador:
Hay tres formas de manejar la emulación del procesador:
- Interpretación
- Recompilación dinámica
- Recompilación estática
Con todas estas rutas, tiene el mismo objetivo general: ejecutar un código para modificar el estado del procesador e interactuar con el 'hardware'. El estado del procesador es un conglomerado de registros del procesador, controladores de interrupciones, etc. para un objetivo de procesador dado. Para el 6502, tendría una serie de números enteros de 8 bits que representan registros: A
, X
, Y
, P
, y S
; también tendrías un PC
registro de 16 bits .
Con la interpretación, comienza en el IP
(puntero de instrucciones, también llamado PC
contador de programas) y lee las instrucciones de la memoria. Su código analiza esta instrucción y utiliza esta información para alterar el estado del procesador según lo especificado por su procesador. El problema central con la interpretación es que es muy lento; cada vez que maneja una instrucción dada, debe decodificarla y realizar la operación requerida.
Con la recompilación dinámica, itera sobre el código de forma muy similar a la interpretación, pero en lugar de solo ejecutar códigos de operación, crea una lista de operaciones. Una vez que alcanza una instrucción de bifurcación, compila esta lista de operaciones en el código de máquina para su plataforma host, luego almacena en caché este código compilado y lo ejecuta. Luego, cuando golpeas un grupo de instrucciones dado nuevamente, solo tienes que ejecutar el código desde el caché. (Por cierto, la mayoría de las personas en realidad no hacen una lista de instrucciones, sino que las compilan en código máquina sobre la marcha; esto hace que sea más difícil de optimizar, pero eso está fuera del alcance de esta respuesta, a menos que haya suficientes personas interesadas)
Con la recompilación estática, haces lo mismo que en la recompilación dinámica, pero sigues las ramas. Termina creando un fragmento de código que representa todo el código del programa, que luego puede ejecutarse sin más interferencias. Este sería un gran mecanismo si no fuera por los siguientes problemas:
- Para empezar, el código que no está en el programa (por ejemplo, comprimido, cifrado, generado / modificado en tiempo de ejecución, etc.) no se volverá a compilar, por lo que no se ejecutará
- Se ha demostrado que encontrar todo el código en un binario dado es equivalente al problema de detención
Estos se combinan para hacer que la recompilación estática sea completamente inviable en el 99% de los casos. Para obtener más información, Michael Steil ha realizado una gran investigación sobre la compilación estática, la mejor que he visto.
El otro lado de la emulación del procesador es la forma en que interactúa con el hardware. Esto realmente tiene dos lados:
- Sincronización del procesador
- Manejo de interrupciones
Sincronización del procesador:
Ciertas plataformas, especialmente las consolas más antiguas como NES, SNES, etc., requieren que su emulador tenga un tiempo estricto para ser completamente compatible. Con el NES, tiene la PPU (unidad de procesamiento de píxeles) que requiere que la CPU coloque píxeles en su memoria en momentos precisos. Si usa la interpretación, puede contar fácilmente los ciclos y emular el tiempo adecuado; con la compilación dinámica / estática, las cosas son / mucho / más complejas.
Manejo de interrupciones:
Las interrupciones son el mecanismo principal que la CPU comunica con el hardware. En general, sus componentes de hardware le dirán a la CPU qué interrupciones le importan. Esto es bastante sencillo: cuando su código arroja una interrupción determinada, mira la tabla de manejo de interrupciones y llama a la devolución de llamada adecuada.
Emulación de hardware:
Hay dos lados para emular un dispositivo de hardware dado:
- Emulando la funcionalidad del dispositivo
- Emulando las interfaces reales del dispositivo
Tome el caso de un disco duro. La funcionalidad se emula creando el almacenamiento de respaldo, las rutinas de lectura / escritura / formato, etc. Esta parte es generalmente muy sencilla.
La interfaz real del dispositivo es un poco más compleja. En general, esto es una combinación de registros mapeados de memoria (por ejemplo, partes de la memoria que el dispositivo busca cambios para hacer señalización) e interrumpe. Para un disco duro, es posible que tenga un área asignada de memoria donde coloque comandos de lectura, escrituras, etc., y luego vuelva a leer estos datos.
Me gustaría entrar en más detalles, pero hay un millón de formas en que puedes hacerlo. Si tiene alguna pregunta específica aquí, no dude en preguntar y agregaré la información.
Recursos:
Creo que he dado una buena introducción aquí, pero hay un montón de áreas adicionales. Estoy más que feliz de ayudar con cualquier pregunta; He sido muy vago en la mayoría de esto simplemente debido a la inmensa complejidad.
Enlaces obligatorios de Wikipedia:
Recursos generales de emulación:
- Zophar : aquí es donde comencé con la emulación, primero descargué emuladores y finalmente saqueé sus inmensos archivos de documentación. Este es el mejor recurso absoluto que puede tener.
- NGEmu : no hay muchos recursos directos, pero sus foros son inmejorables.
- RomHacking.net - La sección de documentos contiene recursos sobre arquitectura de máquinas para consolas populares
Proyectos de emulador de referencia:
- IronBabel : esta es una plataforma de emulación para .NET, escrita en Nemerle y recompila el código a C # sobre la marcha. Descargo de responsabilidad: este es mi proyecto, así que perdón por el enchufe descarado.
- BSnes : un increíble emulador de SNES con el objetivo de una precisión de ciclo perfecto.
- MAME : el emulador de arcade. Gran referencia
- 6502asm.com - Este es un emulador JavaScript 6502 con un pequeño foro genial.
- dynarec'd 6502asm - Este es un pequeño truco que hice durante un día o dos. Tomé el emulador existente de 6502asm.com y lo cambié para recompilar dinámicamente el código a JavaScript para aumentos masivos de velocidad.
Referencias de la compilación del procesador:
- La investigación sobre la compilación estática realizada por Michael Steil (mencionada anteriormente) culminó en este documento y puede encontrar la fuente y tal aquí .
Apéndice:
Ha pasado más de un año desde que se envió esta respuesta y, con toda la atención que ha estado recibiendo, pensé que era hora de actualizar algunas cosas.
Quizás lo más emocionante de la emulación en este momento es libcpu , iniciado por el ya mencionado Michael Steil. Es una biblioteca destinada a admitir una gran cantidad de núcleos de CPU, que utilizan LLVM para la compilación (¡estática y dinámica!). Tiene un gran potencial, y creo que hará grandes cosas para la emulación.
emu-docs también me ha llamado la atención, que alberga un gran depósito de documentación del sistema, que es muy útil para fines de emulación. No he pasado mucho tiempo allí, pero parece que tienen muchos recursos excelentes.
Me alegra que esta publicación haya sido útil, y espero poder sacarme de quicio y terminar mi libro sobre el tema para fin de año / principios del próximo año.