Article by: Amir Mujacic
私たちは Sentry のゲームエンジンとネイティブ SDK のアップデートをリリースしましたが、Windows でビルドしたゲームを Linux 上で Wine / Proton 互換レイヤーを使って意図的にテストしている場合を除いて、おそらくほとんどの開発者はこれまで気づいていなかったと思います。それこそが狙いです。
ゲームエンジン SDK の改善に取り組んでいる最中、謎の問題を調査する中で得た知見は、Wine やその他の互換レイヤーを介して Linux 上で動作するあらゆる Windows アプリケーションにも当てはまります。そしてそこに至るまでの経緯自体が語る価値のあるストーリーだったのです。
謎:なぜクラッシュレポートが届かないのか
きっかけは、あるゲームスタジオからのサポートチケットでした。
「Sentry は Windows ではうまく動いているのですが、Steam Deck のクラッシュでは何も届きません。」
おかしい… 私たちはテスト用ゲームを SteamOS 上で動かしましたが、Linux ビルドをテストすれば、問題なく動作します。次のステップは、多くのゲームと同じように互換レイヤーを介して、SteamOS 上でテスト用ゲームを動かすことでした。非致命のエラーはきちんと拾える。ログも例外もパフォーマンスデータも一通り見えます。ところが実際のクラッシュだけは、反応ゼロ…。
最初の直感:アップロードパイプラインを確認しました。ネットワークの問題?認証?いいえ。非クラッシュイベントに関してはすべて正常に見えました。
そこで Steam Deck のテストデバイス上のローカルストレージを確認しました。
すると 15 個のクラッシュダンプがありました… それぞれが 500MB 超えで、デモゲームなのに、ヒープメモリがダンプされていない状態としては異常な大きさです。
参考までに言えば、同じゲームの通常の Windows クラッシュダンプは 50〜80KB 程度です。何かが壊滅的におかしかったのです。

サイズ制限というつながり
つまり、クラッシュ自体は実際に検知され、バックエンドで処理されていることは分かりました。ミニダンプも見つけましたが、どこが「ミニ」なのかという代物でした。数百メガバイトに達しており、現行の Sentry のミニダンプサイズ上限を超えていました。豆知識として言えば:この上限は引き上げ予定で、あの異常なミニダンプも将来的には取り込まれることになります(ノイズは大量に含まれますが)。ただ、その話は一旦置いておきましょう。
ウサギ穴の奥へ:スレッドスタックが 11MB はおかしい
私たちは、その巨大ミニダンプのひとつを分解して調べました。たいていのクラッシュレポート形式には、何を取得したかに関するメタデータが含まれますが、その奥底に決定的証拠が埋まっていました。

待って。11MB のスタック?たった 1 スレッドで?
Windows では、スレッドスタックは通常 1MB が予約され、そのうち実際にコミットされて使われているのは 4〜64KB 程度です。今回のダンプでは、各スレッドで 10MB 以上がキャプチャされていました。典型的なゲームでは 50 スレッド以上あるため、スタックだけで 500MB 超になり、さらにヒープメモリ、読み込まれたモジュール、その他の状態が加わることになります。
何かが Crashpad(Chromium プロジェクトのユーティリティをフォークしたもので、デスクトップでのデフォルトのクラッシュレポートバックエンド)に、あまりにも多くのメモリをキャプチャするよう指示しているようでした。
TEB 調査
Crashpad は、Windows が維持するスレッドごとのデータ構造である Windows Thread Environment Block(TEB)を読み取ることで、どれだけのスタックをキャプチャするかを決定します。具体的には次のとおりです。

Proton 上で Crashpad がどんな TEB 値を読んでいるか確認するためにログを追加したところ、原因が見えました。Wine の TEB 実装が、実際には StackLimit と StackBase を非常に大きな値に設定しており、その結果 Crashpad はスタックの底から頂上まで、つまりコミット(実際に使用)された領域だけでなく、ユーザーモードのアドレス空間ほぼ全体をキャプチャしようとしていたのです。
これは Wine のバグではありません。Wine の互換レイヤーの既知の制約です。TEB は複雑な内部構造であり、Wine は内部構造の正確さよりも、実際の Windows API との互換性を優先しています。ほとんどの Windows ソフトウェアは TEB を直接読めないため、通常は問題になりません。
しかし、クラッシュレポーターは? TEB を確実に読みます。
Windows のミニダンプで得た気づき
この時点で、何が壊れているかは分かっていましたが、どう直すかを見つける必要がありました。
私たちは、Windows 自身がどのようにミニダンプを生成しているか(ネイティブな MiniDumpWriteDump API)について調査を始め、Wine におけるこの API の実装を掘り下げたところ、興味深い点が見つかりました。
Wine のミニダンプコードも、TEB の値を無条件には信頼していないのです。
その代わりに、クラッシュコンテキストから取得した実際のスタックポインタ(x64 なら RSP、x86 なら ESP)を使用しています。

これは完全に理にかなっています。スタックポインタは「いま使用中のメモリ」を指しています。x86/x64 ではスタックは下方向に成長するため、スタックポインタより「下」側(アドレスとしては高位側)がアクティブなスタックです。スタックポインタより「上」側は未使用、もしくはすでにアンワインド済みです。
TEB の StackLimit の代わりにスタックポインタを使うことで、TEB の値が誤っていても Wine のミニダンプ生成は正しく動作します。
同じことを Crashpad でも実装できるはずです。
修正策:スタックポインタを信頼する

私たちはスタック全体にわたり、三層の解決策を実装しました。
1. Crashpad:スマートなスタックキャプチャモード
Crashpad に新しいオプションを追加し、TEB ベースから SP ベースのスタックキャプチャへ切り替えられるようにしました。有効化すると、クラッシュハンドラは保存されたクラッシュコンテキストからスタックポインタを抽出し、妥当なスタック範囲を計算します。

2. Sentry Native:簡単な API
この機能を Sentry Native の C API で公開しました。これは当社のすべてのゲームエンジン統合を支えています。

この単一の API が、Unreal、Unity、Godot、そしてカスタムエンジンまで、すべてのエンジンでの互換性を可能にする土台です。
3. ゲームエンジン SDK:自動検出
とはいえ、手動設定は開発者体験として良くありません。私たちはゲームエンジン SDK において、実行時に Wine/Proton を検出して自動化しました。Wine/Proton を検出した場合、SDK は自動的に以下を行います。
- スタックキャプチャ調整を有効化
- 検出時に OS 名を SteamOS(+バージョン)に設定
- 実行時の Win/Proton(+バージョン)のコンテキストとタグを追加
自動検出により、すべてのイベントに豊富なプラットフォームコンテキストが含まれるため、プラットフォーム別(ネイティブ Windows / Proton / Wine)にクラッシュをフィルタリングし、Wine 固有の問題を個別に追跡し、プレイヤーのプラットフォーム分布を把握し、クラッシュがゲームのバグか Wine の互換性問題かを識別できます。Wine/Proton タグで絞り込めば、Wine 固有の問題を調査できます。
設定不要:どのエンジンでゲームをリリースしても、Wine/Proton 上でクラッシュレポートが機能します。

Steam Deck を超えて:すべての Linux 互換レイヤーへ
今回の発見は Steam Deck 上で起きたものですが、Wine や互換レイヤー上で Linux 上で動作するあらゆる Windows アプリケーションに影響します。Proton、Wine Staging、CrossOver、Lutris、Bottles を含みます。これは、ゲームプラットフォーム(Steam Deck、デスクトップ Linux、CrossOver 経由の macOS、SteamOS を搭載した ROG Ally / Legion Go)、エンタープライズ用途(プロダクティビティアプリ、レガシーソフトウェア、開発ツール)、および CI/CD パイプラインにまで及びます。
優れたクラッシュレポーティングは、プレイヤーがどのプラットフォームを選ぶかや、どのエンジンであなたが構築するかに依存すべきではありません。ハイエンド Windows PC でも、Proton 上で動く Steam Deck でも、Wine を使った Linux デスクトップでも、そしてあなたが Unreal、Unity、Godot、あるいはカスタムエンジンを使っていようとも、より良いゲームを出荷するために役立つ実行可能なクラッシュレポートを受け取ることができるべきです。
互換レイヤー上でのアプリケーションの動作がネイティブ Windows とはどのように異なるのかを理解し、SDK スタック全体でアプローチを適応させることで、Sentry は Linux におけるクロスプラットフォームゲームのための最も信頼できるクラッシュレポーティングソリューションになりました。
この修正はすでに Unreal Engine SDK と Sentry Native SDK(カスタムゲームエンジン用)に反映済みです。Unity と Godot SDK についても近日対応予定です。
質問やフィードバックがありますか?ぜひ Discord で議論に参加してください。Sentry を初めて使う方は、無料でお試しください。
Original Page: Not so “mini”-dumps: How we found missing crashes on SteamOS
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。


