- Die VU-Gleitkomma-Multiplikation der PS2 hat einen 1-Bit-Rechenfehler, sodass bei bestimmten Werten
1 * XvonXabweichen kann - Laut VU-Entwicklerhandbuch ist bei
X * 1die Genauigkeit garantiert, aber für1 * Xnicht; dieser Unterschied dient als Signal zur Emulator-Erkennung - Das Beispiel verwendet 129.5f aus per Brute Force gefundenen problematischen Werten, um den Verhaltensunterschied zwischen echter PS2 und Emulatoren zu prüfen
- Die Implementierung ist einfach aufgebaut: Im VU0-Makromodus werden
129.5fund1multipliziert, danach wird nur geprüft, ob sich Eingabewert und Ergebnis unterscheiden - PCSX2, Play!, DobieStation und hps2x64 emulieren dieses Verhalten derzeit nicht; die Schwierigkeit der Erkennung wird mit 1/5 bewertet
1-Bit-Fehler bei der PS2-VU-Multiplikation
- Diese Methode ist der zweite Eintrag in einer Serie zur PS2-Emulator-Erkennung und kann in VU1, im VU0-Mikromodus und im VU0-Makromodus verwendet werden
- Das Beispiel nutzt den VU0-Makromodus, um die Implementierung einfach zu halten
- Dabei wird VU0 wie ein Coprozessor verwendet und kann daher direkt von der EE-CPU ausgeführt werden
- Es ist nicht nötig, ein separates VU-Programm zu handhaben
- Im VU-Entwicklerhandbuch gibt es bei Multiplikationsbefehlen wie
MULundMULieinen Hinweis auf einen 1-Bit-Rechenfehler1 * Xkann vom ursprünglichen WertXabweichen- Wenn
VF[fs]als Multiplikand verwendet wird, ist die Genauigkeit des Ergebnisses in der FormX * 1garantiert
- Warum genau dabei ein Bit verloren geht, ist nicht eindeutig geklärt
Erkennungswert und Implementierung
- Um diesen Fehler zu erkennen, wird eine Zahl benötigt, die das Problem auslöst; die einfachste Suchmethode ist Brute Force
- Der Autor hat früher eine Liste der ersten 250 Zahlen erstellt, die das Problem in 0,5er-Schritten auslösen, und diese Liste in einem gist veröffentlicht
- Der Beispielcode verwendet 129.5f als zu erkennenden Zahlenwert
- Mit
QMTC2wirdVF1auf129.5fgesetzt - Mit
VADDwwird inVF2der Wert1erzeugt - Mit
VMULwirdVF1 = 1 * 129.5fberechnet - Mit
QMFC2wird das Ergebnis zurück auf die EE-Seite geholt und mit der Eingabe verglichen
- Mit
- Der Rückgabewert ist
in[0] != out[0]; wenn sich Ursprungswert und Multiplikationsergebnis unterscheiden, wird davon ausgegangen, dass der VU-Multiplikationsfehler vorliegt
Auswirkungen auf einzelne Emulatoren
- PCSX2, Play!, DobieStation und hps2x64 emulieren dieses PS2-VU-Multiplikationsverhalten derzeit nicht
- Da nur eine Zahl mit
1multipliziert und anschließend nur das Ergebnis geprüft werden muss, wird die Schwierigkeit dieser Erkennungsmethode mit 1/5 eingestuft
1 Kommentare
Hacker-News-Kommentare
Echte ARM-Hardware liest wegen der Pipeline PC+8, während sie bei PC ausführt und PC+4 dekodiert; die neu gespeicherte Instruktion sollte also keine Auswirkung haben. Ein Emulator, der die Hardware-Pipeline nicht emuliert, würde diese Instruktion ausführen
Ein Artikel, der das zusammen mit mehreren Anti-Emulations-Techniken aus dem Jahr 2004 ausführlicher erklärt: https://mgba.io//2014/12/28/classic-nes/
Vermutlich war der Registerwert für ein paar Zyklen undefiniert. Eng optimierten Assemblercode für so einen Chip zu schreiben, war ziemlich furchtbar – es fühlte sich an wie ein Zachtronics-Klon mit besonders schlechtem Geschmack
Viel später fand jemand einen weiteren, nicht erkannten Grenzfall: eine wiederholte String-Instruktion, die sich selbst überschreibt
https://silviocesare.wordpress.com/2009/02/02/anti-debugging...
x86 hat so eine Vorrichtung; ich bin mir aber nicht sicher, ob sie in den 64-Bit-Varianten letztlich entfernt wurde
Man muss nicht nur jedes ungewöhnliche Verhalten der Originalhardware und -software kennen, sondern es auch exakt reproduzieren, egal wie merkwürdig es ist. Das ist schon für sich genommen schwierig, und dann kommen noch die Auswirkungen auf die Performance hinzu
Der Einsatz eines JIT-Recompilers kann nicht perfekt zyklusgenau zur Originalhardware sein, ist aber meistens kein Problem, solange der Spielcode nicht absichtlich so geschrieben wurde, dass er Emulatoren kaputtmacht
Auch Dolphin musste diese Balance finden, als einige kommerzielle Wii-Spiele Anti-Emulator-Code enthielten, der Details des Cache-Verhaltens der echten Wii-CPU ausnutzte. Theoretisch hätte man den echten CPU-Cache emulieren können, um die Spiele sauber laufen zu lassen, aber der Performance-Overhead hätte vermutlich etwa eine Verlangsamung um den Faktor 10 bedeutet und sie unspielbar gemacht; deshalb entschied man sich für Workaround-Patches
https://dolphin-emu.org/blog/2017/02/01/dolphin-progress-rep...
Im Grunde scheint man Elektronik und tiefe Programmiermagie verstehen zu müssen
Am Anfang – und meistens auch am Ende – muss man keine tiefe Magie verstehen. Im Wesentlichen schaut man in die Spezifikation und implementiert, was dort steht. Man braucht die Fähigkeit, den Code so zu strukturieren, dass er nicht völlig entgleist, aber es gibt gängige Muster, und nachdem man ein oder zwei Emulatoren gebaut hat, wird es deutlich einfacher
Elektronik muss man auch nur selten verstehen. Emuliert wird nur das Verhalten. Wenn ein Bug im Verhalten der Originalhardware entdeckt wird, fügt man im Emulator normalerweise eine Sonderbehandlung ein. Elektronikwissen kann helfen zu verstehen, warum dieses Verhalten entstanden ist, aber das ist eher von historischem Interesse als praktisch notwendig
Es gibt eigene Schwierigkeiten. Wenn etwas schiefläuft, debuggt man normalerweise drei Dinge gleichzeitig: das Verständnis der Hardware, die Emulator-Implementierung und das emulierte Spiel. Die genaue Ursache einzugrenzen kann schwer sein. Trotzdem würde ich empfehlen, einfach erst einmal grob etwas zu bauen. Es ist nicht hübsch, aber alle Emulatoren sind voll von Sonderbehandlungen, nur damit beliebte Spiele irgendwie laufen. Wenn ein paar schmutzige Hacks ein Spiel zum Laufen bringen, dann macht man das eben so. Man muss das Verhalten der Originalhardware nicht exakt implementieren; man muss das Spiel zum Laufen bringen
Eine 8-Bit-CPU ist eine einfache Zustandsmaschine mit ein paar Bytes Zustand, also Registern. Man liest das Programm Byte für Byte und ahmt nach, was die CPU tut, nachdem sie dieses Byte gelesen hat. Das sind sehr einfache Operationen wie Zahlen addieren und subtrahieren oder Bytes lesen und speichern
http://www.6502.org/users/obelisk/6502/registers.html
http://www.6502.org/users/obelisk/6502/instructions.html
Ein 6502-CPU-Emulator liest die nächsten paar Bytes des Programms, interpretiert diese Bytes als Instruktionen und führt die Instruktion aus. Dabei aktualisiert er einige Register oder Zähler der CPU, führt arithmetische oder bitweise Operationen aus und liest oder speichert bei Bedarf ein Datenbyte von einer Stelle an eine andere. Diesen Prozess wiederholt er in einer Endlosschleife
Dies ist eine Simulation des Fetch-Decode-Execute-Zyklus
https://en.wikipedia.org/wiki/Instruction_cycle
Ich habe früher einmal einen 6502-Interpreter von UNIX auf Classic Macintosh portiert, um SID-Musikdateien abzuspielen. Taktzyklusgenauigkeit war nicht wichtig, solange es nur schnell genug lief
Es funktionierte so, dass aus dem Interpreter heraus C-Code aufgerufen wurde
Ich hätte immer noch Lust darauf, habe aber keine Zeit
Ansonsten trifft der Geschwisterkommentar von @xcv123 wirklich den Punkt
Falls es irgendwann möglich wird, die PS2 per FPGA nachzubauen, wäre es für jemanden ein spannendes Projekt herauszufinden, wie dieses Verhalten zustande kam
Es gibt keine Garantie, dass eine FPGA-Version der PS2 nicht denselben oder einen ähnlichen Bug implementiert
Software-Floating-Point wäre langsam, aber die übliche Lösung würde vermutlich dem PS2-Emulator der PS4 folgen: Codeabschnitte, in denen ein Software-Floating-Point-Pfad erlaubt ist, pro Spiel per Whitelist festlegen
Ich habe viel zu lange gebraucht, um herauszufinden, dass es hier um die PlayStation 2 geht und nicht um den Personal-System/2-Port, an den man Maus und Tastatur anschließt
Drei-Buchstaben-Abkürzungen können es wirklich schwer machen, den Kontext zu finden, weil eine Suche nur nach der Abkürzung bei Google ziemlich oft überwiegend nahezu irrelevante Ergebnisse liefert