- La multiplicación de punto flotante de la VU de la PS2 tiene un error de operación de 1 bit, por lo que, con ciertos valores,
1 * Xpuede terminar siendo distinto deX - Según el manual para desarrolladores de la VU, se garantiza la exactitud de
X * 1, pero no existe la misma garantía para1 * X, y esa diferencia se convierte en una señal de detección de emuladores - El ejemplo usa 129.5f, uno de los valores problemáticos encontrados por fuerza bruta, para comprobar la diferencia de comportamiento entre una PS2 real y los emuladores
- La implementación tiene una estructura simple: en modo macro de VU0 multiplica
129.5fpor1y luego solo compara si el resultado difiere de la entrada original - PCSX2, Play!, DobieStation y hps2x64 actualmente no emulan este comportamiento, y la dificultad de detección se evalúa en 1/5
Error de 1 bit en la multiplicación de la VU de PS2
- Este método es el segundo elemento de la serie sobre detección de emuladores de PS2, y puede usarse en VU1, en modo micro de VU0 y en modo macro de VU0
- El ejemplo usa modo macro de VU0 para simplificar la implementación
- Como VU0 se usa como un coprocesador, puede ejecutarse directamente desde la CPU EE
- No hace falta manejar un programa VU separado
- En las instrucciones de multiplicación como
MULyMULidel manual para desarrolladores de la VU hay una nota que indica que existe un error de operación de 1 bit1 * Xpuede diferir del valor originalX- Si se usa
VF[fs]como multiplicando, se garantiza la exactitud del resultado en la formaX * 1
- No se ha confirmado con exactitud por qué se pierde el bit
Valor de detección y forma de implementación
- Para detectar este error se necesita un número que provoque el problema, y la forma más sencilla de buscarlo es por fuerza bruta
- El autor creó en el pasado una lista de los primeros 250 números que provocan el problema en incrementos de 0.5, y publicó esa lista en un gist
- El código de ejemplo usa 129.5f como número objetivo de detección
- Con
QMTC2configura129.5fenVF1 - Con
VADDwcrea1enVF2 - Con
VMULcalculaVF1 = 1 * 129.5f - Con
QMFC2trae el resultado al lado de EE y lo compara con la entrada
- Con
- El valor de retorno es
in[0] != out[0], y si el valor original y el resultado de la multiplicación difieren, se determina que existe el error de multiplicación de la VU
Impacto por emulador
- Actualmente PCSX2, Play!, DobieStation y hps2x64 no emulan este comportamiento de multiplicación de la VU de PS2
- Como solo hay que multiplicar un número por
1y revisar el resultado, la dificultad de este método de detección es de nivel 1/5
1 comentarios
Comentarios de Hacker News
En un ARM real, por el pipeline, mientras ejecuta desde PC y decodifica PC+4, lee PC+8, así que la instrucción recién guardada no debería tener efecto. Un emulador que no emule el pipeline de hardware terminaría ejecutando esa instrucción
Un artículo que lo explica con más detalle junto con varias técnicas antiemulación de 2004: https://mgba.io//2014/12/28/classic-nes/
Supongo que durante algunos ciclos el valor del registro quedaba indefinido. Escribir código assembly ajustadísimo para optimizarlo en chips así era bastante horrible, como jugar un clon de Zachtronics con un gusto especialmente malo
Mucho más tarde alguien encontró otro caso límite que no se detectaba: una instrucción repetitiva de cadenas que se sobrescribía a sí misma
https://silviocesare.wordpress.com/2009/02/02/anti-debugging...
x86 tiene ese mecanismo, aunque no sé bien si al final lo eliminaron en las variantes de 64 bits
No solo hay que conocer todos los comportamientos peculiares del hardware y el software originales, sino reproducirlos tal cual, por más extraños que sean. Eso ya es difícil de por sí, pero además hay que considerar el impacto en el rendimiento
Usar un recompilador JIT no puede ser perfectamente exacto ciclo por ciclo respecto del hardware original, pero por lo general no importa, salvo que el código del juego esté hecho intencionalmente para romper emuladores
Dolphin también tuvo que manejar este equilibrio cuando algunos juegos comerciales de Wii incluyeron código antiemulador que abusaba de detalles del comportamiento de caché de la CPU real de Wii. En teoría se podría haber emulado la caché de la CPU real para ejecutar el juego sin problemas, pero el sobrecosto de rendimiento probablemente habría sido del orden de 10 veces más lento, volviéndolo injugable, así que se optó por un parche de evasión
https://dolphin-emu.org/blog/2017/02/01/dolphin-progress-rep...
Da la impresión de que prácticamente hay que entender electrónica y magia profunda de programación
Al comenzar, y en la mayoría de los casos incluso al terminar, no hace falta entender magia profunda. En general se mira la especificación y se implementa lo que dice. Sí hace falta capacidad para estructurar el código de forma que no se vuelva un desastre, pero hay patrones comunes, y después de hacer uno o dos emuladores se vuelve mucho más fácil
Casi nunca hace falta entender electrónica. Lo que se emula es el comportamiento. Si se descubre un bug en el comportamiento del hardware original, normalmente basta con agregar un tratamiento especial en el emulador. Saber electrónica puede ayudar a entender por qué ocurre ese comportamiento, pero es más un interés histórico que algo práctico
Tiene sus dificultades propias. Cuando aparece un problema, normalmente terminas depurando tres cosas al mismo tiempo: tu comprensión del hardware, la implementación del emulador y el juego que se está emulando. Puede ser difícil acotar la causa exacta. Aun así, recomiendo armar algo aproximado primero. No es elegante, pero todos los emuladores están llenos de tratamientos especiales para hacer funcionar de algún modo juegos populares. Si con unos hacks sucios el juego corre, haz eso. No hace falta implementar con exactitud el comportamiento del hardware original; hay que lograr que el juego funcione
Una CPU de 8 bits es una máquina de estados simple con unos pocos bytes de estado, es decir, registros. Lee el programa byte por byte y simula lo que hace la CPU después de leer ese byte. Son operaciones muy simples, como sumar y restar números, o leer y guardar bytes
http://www.6502.org/users/obelisk/6502/registers.html
http://www.6502.org/users/obelisk/6502/instructions.html
Un emulador de CPU 6502 lee los siguientes bytes del programa, interpreta esos bytes como instrucciones y ejecuta la instrucción. En el proceso actualiza algunos registros o contadores de la CPU, realiza operaciones aritméticas o de bits y, si hace falta, lee o guarda un byte de datos de una ubicación a otra. Repite este proceso en un bucle infinito
Esto es una simulación del ciclo de búsqueda-decodificación-ejecución
https://en.wikipedia.org/wiki/Instruction_cycle
Hace años porté de UNIX a Classic Macintosh un intérprete de 6502 para reproducir archivos de música SID. La precisión a nivel de ciclos de reloj no importaba, siempre que corriera lo suficientemente rápido
Funcionaba llamando código C desde el intérprete
Todavía me dan ganas de intentarlo, pero no tengo tiempo
Por lo demás, el comentario hermano de @xcv123 está totalmente en lo cierto
Si algún día se vuelve posible clonar la PS2 con un FPGA, averiguar cómo ocurría este comportamiento sería un proyecto divertido para alguien
Que sea una versión FPGA de la PS2 no garantiza que no implemente el mismo bug, o uno parecido
La coma flotante por software sería lenta, pero la solución habitual probablemente seguiría la del emulador de PS2 de la PS4: una lista blanca, juego por juego, de secciones de código donde se permite usar la ruta de coma flotante por software
Me tomó demasiado tiempo darme cuenta de que esto hablaba de la PlayStation 2 y no del puerto Personal System/2 para conectar mouse y teclado
Las siglas de tres letras pueden hacer muy difícil encontrar contexto, porque si pones solo la sigla en Google, con bastante frecuencia la mayoría de los resultados son casi irrelevantes