2 ポイント 投稿者 GN⁺ 2024-06-09 | 1件のコメント | WhatsAppで共有
  • PS2の VU浮動小数点乗算には1ビットの演算誤差があり、特定の値では 1 * XX と異なる場合がある
  • VU開発者マニュアルによると、X * 1 は正確性が保証されているが、1 * X には同じ保証がなく、この差が エミュレーター検出 のシグナルになる
  • 例では、ブルートフォースで見つけた問題発生値のうち 129.5f を使い、実機PS2とエミュレーターの挙動差を確認する
  • 実装は、VU0マクロモードで 129.5f1 を乗算した後、元の入力と結果が異なるかだけを比較する単純な構造
  • PCSX2、Play!、DobieStation、hps2x64は現在この挙動をエミュレートしておらず、検出難度は 1/5 と評価されている

PS2 VU乗算で生じる1ビット誤差

  • この方法はPS2エミュレーター検出シリーズの2番目の項目で、VU1、VU0マイクロモード、VU0マクロモードで利用できる
  • 例では実装を単純にするため VU0マクロモード を使用する
    • VU0をコプロセッサのように使うため、EE CPUから直接実行できる
    • 別途VUプログラムを扱う必要がない
  • VU開発者マニュアルの MULMULi などの乗算命令には 1ビットの演算誤差 があるという注記がある
    • 1 * X は元の値 X と異なる場合がある
    • VF[fs] を被乗数として使うと、X * 1 形式の結果の正確性が保証される
  • なぜビットが失われるのかは正確には確認されていない

検出値と実装方法

  • この誤差を検出するには問題を引き起こす数値が必要で、最も簡単な探索方法は ブルートフォース である
  • 筆者は過去に0.5刻みで問題を起こす最初の250個の数値リストを作成し、そのリストを gist で公開している
  • サンプルコードは 129.5f を検出対象の数値として使用する
    • QMTC2VF1129.5f を設定する
    • VADDwVF21 を作る
    • VMULVF1 = 1 * 129.5f を計算する
    • QMFC2 で結果をEE側へ取り込み、入力と比較する
  • 戻り値は in[0] != out[0] で、元の値と乗算結果が異なればVU乗算誤差が存在すると判断する

エミュレーター別の影響

  • 現在 PCSX2、Play!、DobieStation、hps2x64 はこのPS2 VU乗算の挙動をエミュレートしていない
  • 数値1つと 1 を乗算して結果だけを確認すればよいため、この検出方式の難度は 1/5 程度である

1件のコメント

 
GN⁺ 2024-06-09
Hacker Newsのコメント
  • 古いARMエミュレーションを検出する最も単純なトリックとして、Game Boy Advanceのコピー防止にも使われていたようなものがある: PC+4 の位置、つまり直後の命令にトラップ命令を保存する方法
    実際のARMはパイプラインのため、PCで実行しつつPC+4をデコードしている間にPC+8を読むので、新しく保存した命令は影響しないはず。ハードウェアのパイプラインをエミュレートしていないエミュレータなら、その命令を実行してしまう
    2004年のさまざまなエミュレーション妨害手法とともに、より詳しく説明した記事: https://mgba.io//2014/12/28/classic-nes/
    • Texas Instruments TI320C40 デジタル信号プロセッサは、パイプラインの問題がさらに奇妙だった。分岐の後にある1つ以上の命令が実際の分岐前に実行される分岐遅延スロット(https://en.wikipedia.org/wiki/Delay_slot)があり、レジスタに格納した値が数命令後になって初めて現れるロード遅延スロットもあった
      おそらく数サイクルの間、レジスタ値は未定義だったのだと思う。こういうチップ向けにぎちぎちに最適化したアセンブリコードを書くのは、ひときわ趣味の悪いZachtronicsクローンを遊んでいるようで、かなりひどいものだった
    • x86でもプリフェッチキューが似た挙動を生んでいたが、IntelがPentium以降のCPUで自己書き換えコードを検出するようにしたため、まもなく実行される命令を変更すると常に効果が出るようになった
      ずっと後になって、誰かが検出されない別の境界ケースを見つけた。それは自分自身を上書きする反復文字列命令だった
      https://silviocesare.wordpress.com/2009/02/02/anti-debugging...
    • 一部のパイプラインCPUは自己書き換えコードとの互換性を保っていて、パイプライン上にある命令を上書きするとそれを検出してパイプラインをフラッシュする
      x86にはそうした仕組みがあるが、64ビット版で最終的に取り除かれたのかはよく分からない
    • これがVisualBoyAdvanceでDragon Ball Z: The Legacy of Goku IIのROMが時々動かなかった理由なのかな?
  • 100%の正確性を目指すエミュレーションが、この業界で職人芸の領域とされる理由はまさにこれ
    元のハードウェアとソフトウェアのあらゆる特異な挙動を知っている必要があるだけでなく、どれほど奇妙でもそのまま再現しなければならない。それ自体も難しいが、性能への影響まで考えなければならない
    • エミュレータは正確性について現実的であるべき。より現代的なシステムをエミュレートする場合、100%ハードウェア精度と実用的な性能を同時に目標にするのは普通は不可能なので、実際のハードウェアとは技術的には異なるが、実際に観測できる差はほとんどない妥協を受け入れることになる
      JIT再コンパイラを使うことは、元のハードウェアと完全にサイクル単位で正確にはなり得ないが、ゲームコードが意図的にエミュレータを壊すよう作られている場合でなければ、たいてい問題にはならない
      Dolphinも、いくつかの市販Wiiゲームが実際のWii CPUのキャッシュ挙動の細部を悪用したエミュレータ対策コードを入れていたとき、このバランスに対処しなければならなかった。理論上は実CPUのキャッシュをエミュレートしてゲームを滑らかに動かすこともできたが、性能オーバーヘッドはおそらく10倍遅くなる水準で、遊べないものになっていたはずなので、回避パッチを選んだ
      https://dolphin-emu.org/blog/2017/02/01/dolphin-progress-rep...
  • エミュレーションにはどうやって入門するのだろう? ソフトウェアの中でも、とてつもなく難しいニッチ分野に見える
    事実上、電子工学と深いプログラミングの魔法を理解する必要があるように思える
    • ほかのことと同じで、段階的に始めればいい
      始めるときも、たいていは終えるときでさえも、深い魔法を理解する必要はない。基本的には仕様を見て、その通りに実装すればよい。めちゃくちゃにならないようにコード構造を作る能力は必要だが、よくあるパターンがあり、エミュレータを1つか2つ作ってみればずっと簡単になる
      電子工学を理解しなければならない場合もほとんどない。エミュレートするのは挙動だけだ。元のハードウェアの挙動にバグが見つかれば、普通はエミュレータに特別処理を入れればよい。そうした挙動がなぜ生じたのかを理解するのに電子工学の知識が役立つことはあるが、実用的というより歴史的な興味に近い
      固有の難しさはある。問題が起きると、たいていハードウェアへの理解、エミュレータ実装、エミュレート中のゲームという3つを同時にデバッグすることになる。正確な原因を絞り込むのは難しいことがある。それでも、とにかくざっくり作ってみることを勧める。きれいではないが、どのエミュレータも人気ゲームをどうにか動かすための特別処理でいっぱいだ。汚いハックをいくつか入れてゲームが動くなら、それでいい。元のハードウェアの挙動を正確に実装する必要はなく、ゲームが動くようにすればいい
    • ハードウェア文書を読むところから始めればよく、機械を電子回路レベルで理解する必要はない。デジタル回路シミュレーションではないので、そこまで複雑である必要はない
      8ビットCPUは、数バイトの状態、つまりレジスタだけを持つ単純な状態機械だ。プログラムを1バイトずつ読み、CPUがそのバイトを読んだ後に行う動作をまねればよい。数を足したり引いたり、バイトを読んで保存したりするような、とても単純な演算だ
      http://www.6502.org/users/obelisk/6502/registers.html
      http://www.6502.org/users/obelisk/6502/instructions.html
      6502 CPUエミュレータは、プログラムの次の数バイトを読み、そのバイト列を命令として解釈し、命令を実行する。この過程でCPUのレジスタやカウンタをいくつか更新し、算術演算やビット演算を行い、必要ならある位置から別の位置へデータ1バイトを読み書きする。この過程を無限ループで繰り返す

これがフェッチ・デコード・実行サイクルのシミュレーションであること
https://en.wikipedia.org/wiki/Instruction_cycle

  • エミュレーションという言葉で何を意味するかによる
    以前、SID音楽ファイルを再生するために6502インタプリタをUNIXからClassic Macintoshへ移植したことがある。十分に速く動けばよかったので、クロックサイクル精度は重要ではなかった
    インタプリタからCコードを呼び出す方式で動作していた
  • ずっと前に入門してみようとしたときは、ごく単純で文書化がしっかりしたものから始めて、そこから腕を磨けという指針だった
    今でもやってみたい気持ちはあるが、時間がない
  • 少し宣伝すると、前回のFOSDEMでこのテーマについて発表した: https://fosdem.org/2024/schedule/event/fosdem-2024-2146-how-...
    それ以外については、@xcv123の兄弟コメントが本当にその通り
  • ソフトウェアエミュレーションでは、おそらくあまり気にする価値のない類いの興味深い例だ。そのバグまでエミュレートすると、かなり遅くなるはず
    いつかFPGAでPS2を複製できるようになれば、この動作がどのように起きたのかを解明するのは、誰かにとって面白いプロジェクトになるだろう
    • FPGA実装も、ソフトウェアエミュレーションプロジェクトのコードやドキュメントを基に作られることが多い
      PS2のFPGA版だからといって、同じ、または似たようなバグを実装しない保証はない
    • そのバグがゲームを壊すかどうか次第
    • このバグを直すことは、他の多くの浮動小数点バグ、より具体的には丸めとクランプの問題を直す作業の一部になるだろう
      ソフトウェア浮動小数点は遅いだろうが、一般的な解法はおそらくPS4のPS2エミュレータに倣うことになる。ゲームごとに、ソフトウェア浮動小数点経路を許可するコード区間をホワイトリスト指定する方式だ
    • 比較的高価なFPGAで、古くてひどいMIPS CPUをエミュレートしたい理由は何だろう? 古いコンソールをエミュレートする要点は、ハードウェアに依存せず、コンピュータやスマートフォンで昔のゲームを遊べるようにすることだ
  • タイトルを見て、マウスやキーボードがなぜ数学をしなければならないのかと混乱した人は他にもいた?
    これがマウスとキーボードを接続するPersonal System/2ポートではなく、PlayStation 2の話だと分かるまでに時間がかかりすぎた
    • 一方はPS2で、もう一方はPS/2
    • 反対票を入れた人へ: 自分にとって明確だからといって、全員にとって明確なわけではない
      3文字の略語は文脈を見つけるのを本当に難しくすることがある。略語だけをGoogleに入れると、かなり頻繁にほとんど関係のない結果ばかりが出てくるからだ