2 Punkte von GN⁺ 2024-06-09 | 1 Kommentare | Auf WhatsApp teilen
  • Die VU-Gleitkomma-Multiplikation der PS2 hat einen 1-Bit-Rechenfehler, sodass bei bestimmten Werten 1 * X von X abweichen kann
  • Laut VU-Entwicklerhandbuch ist bei X * 1 die Genauigkeit garantiert, aber für 1 * X nicht; 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.5f und 1 multipliziert, 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 MUL und MULi einen Hinweis auf einen 1-Bit-Rechenfehler
    • 1 * X kann vom ursprünglichen Wert X abweichen
    • Wenn VF[fs] als Multiplikand verwendet wird, ist die Genauigkeit des Ergebnisses in der Form X * 1 garantiert
  • 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 QMTC2 wird VF1 auf 129.5f gesetzt
    • Mit VADDw wird in VF2 der Wert 1 erzeugt
    • Mit VMUL wird VF1 = 1 * 129.5f berechnet
    • Mit QMFC2 wird das Ergebnis zurück auf die EE-Seite geholt und mit der Eingabe verglichen
  • 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 1 multipliziert und anschließend nur das Ergebnis geprüft werden muss, wird die Schwierigkeit dieser Erkennungsmethode mit 1/5 eingestuft

1 Kommentare

 
GN⁺ 2024-06-09
Hacker-News-Kommentare
  • Der einfachste Trick, um alte ARM-Emulation zu erkennen, der wohl auch beim Kopierschutz des Game Boy Advance verwendet wurde: eine Trap-Instruktion an der Position PC+4, also direkt bei der nächsten Instruktion, speichern
    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/
    • Der digitale Signalprozessor TI320C40 von Texas Instruments hatte noch seltsamere Pipeline-Probleme: Es gab Branch-Delay-Slots (https://en.wikipedia.org/wiki/Delay_slot), bei denen eine oder mehrere Instruktionen nach einem Sprung noch vor dem eigentlichen Sprung ausgeführt wurden, und außerdem Load-Delay-Slots, bei denen ein in ein Register geladener Wert erst einige Instruktionen später sichtbar wurde
      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
    • Auch auf x86 erzeugte die Prefetch-Queue ein ähnliches Verhalten, aber als Intel ab dem Pentium damit begann, selbstmodifizierenden Code zu erkennen, hatten Änderungen an Instruktionen, die kurz vor der Ausführung standen, immer eine Wirkung
      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...
    • Manche Pipeline-CPUs erhalten die Kompatibilität mit selbstmodifizierendem Code, indem sie erkennen, wenn eine bereits in der Pipeline befindliche Instruktion überschrieben wird, und dann die Pipeline leeren
      x86 hat so eine Vorrichtung; ich bin mir aber nicht sicher, ob sie in den 64-Bit-Varianten letztlich entfernt wurde
    • Ist das der Grund, warum das ROM von Dragon Ball Z: The Legacy of Goku II in VisualBoyAdvance manchmal nicht lief?
  • Genau deshalb ist Emulation mit dem Ziel von 100 % Genauigkeit in der Branche ein Bereich echter Handwerkskunst
    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
    • Emulatoren müssen bei Genauigkeit realistisch sein. Bei der Emulation modernerer Systeme ist es normalerweise unmöglich, gleichzeitig 100 % Hardware-Genauigkeit und brauchbare Performance anzustreben; daher akzeptiert man Kompromisse, die technisch von echter Hardware abweichen, bei denen es praktisch aber kaum beobachtbare Unterschiede gibt
      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...
  • Wie steigt man eigentlich in Emulation ein? Das wirkt selbst innerhalb der Softwarewelt wie eine extrem schwierige Nische
    Im Grunde scheint man Elektronik und tiefe Programmiermagie verstehen zu müssen
    • Wie bei allem anderen: schrittweise anfangen
      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
    • Man kann damit anfangen, die Hardwaredokumentation zu lesen; die Maschine muss man nicht auf Ebene elektronischer Schaltungen verstehen. Da es keine Simulation digitaler Schaltungen ist, muss es nicht so kompliziert sein
      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

  • Es hängt davon ab, was man mit Emulation meint
    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
  • Als ich vor langer Zeit versucht habe, damit einzusteigen, lautete die Empfehlung, mit etwas sehr Einfachem und gut Dokumentiertem anzufangen und darauf aufzubauen
    Ich hätte immer noch Lust darauf, habe aber keine Zeit
  • Ein bisschen Eigenwerbung: Ich habe auf der letzten FOSDEM einen Vortrag zu diesem Thema gehalten: https://fosdem.org/2024/schedule/event/fosdem-2024-2146-how-...
    Ansonsten trifft der Geschwisterkommentar von @xcv123 wirklich den Punkt
  • Das ist ein interessantes Beispiel von der Sorte, um die man sich bei Software-Emulation vermutlich kaum kümmern sollte. Auch diesen Bug zu emulieren, würde es deutlich langsamer machen
    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
    • Auch FPGA-Implementierungen entstehen oft auf Grundlage von Code oder Dokumentation aus Software-Emulationsprojekten
      Es gibt keine Garantie, dass eine FPGA-Version der PS2 nicht denselben oder einen ähnlichen Bug implementiert
    • Es hängt davon ab, ob der Bug Spiele kaputtmacht
    • Diesen Bug zu beheben, wäre Teil der Behebung mehrerer anderer Floating-Point-Bugs, genauer gesagt von Problemen mit Rundung und Clamping
      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
    • Warum sollte man einen alten, miesen MIPS-CPU in einem vergleichsweise teuren FPGA emulieren wollen? Der Kern der Emulation alter Konsolen besteht doch darin, alte Spiele hardwareunabhängig auf Computern oder Smartphones spielbar zu machen
  • War noch jemand vom Titel verwirrt und fragte sich, warum eine Maus oder Tastatur Mathematik machen sollte?
    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
    • Das eine ist PS2, das andere PS/2
    • An die Leute, die runtergevotet haben: Nur weil es für euch klar ist, ist es nicht für alle klar
      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