Windows における例外ハンドリングとか 64-bit プロセス固有のあれそれとか

すごくまとまってました.個人的に印象深かったのはこの辺.

実は、x64 版 Windows 用のバイナリにおいては、fs:[0] に例外ハンドリング構造体へのポインタを設定するなどという行為は一切行いません。というより、コード上には、例外をハンドルする如何なる追加コードも存在しません。これは、例外ハンドリングを行わない場合のプログラムには一切オーバーヘッドが存在しないということを意味します。

Windows の細かい部分まで見てみると,64-bit プロセスのみ影響を受ける仕様というのがいくつかあって,今回のケースもそのひとつですかね.64-bit アプリケーションの場合,「従来から存在するバイナリ」というのが存在しないので,互換性に悩まされることなく 32-bit 時代の仕様をリセットできる,と.
Windows の 64-bit 移行に伴うドライバの変更とかは有名でしょうから,アプリケーション制作者に関係しそうな部分で,64-bit アプリケーションにのみ適用される変更からいくつか挙げてみると,

  • UAC に伴う自動昇格 (install.exe とか setup.exe の自動昇格) は,64-bit プロセスには適用されない*1
  • UAC に伴うファイルとレジストリの仮想化は,(デフォルトでは?) 64-bit プロセスに適用されない.
  • 64-bit プロセス向けの関数呼び出し規約が統一された*2.64-bit をターゲットとした場合,__cdecl, __stdcall, __fastcall, __thiscall, __stdcall はいずれも無視される.
  • Windows 7 で導入された User Mode Scheduling (UMS) は,64-bit プロセスでのみ使用可能*3.
  • (ハードウェア) DEP は64-bit プロセスで常にオンである./NXCOMPAT:NO リンカオプションを使用してビルドしても効果がない*4
  • Vista (以降?) の 64-bit プロセスでは HeapEnableTerminationOnCorruption がデフォルトでオンになる*5
  • いくつかの Legacy なライブラリは 64-bit 版が存在しない.例えば DirectMusic performance layer, DirectPlay 4 以前, DirectDraw 6 以前, Direct3D 8 以前, and DirectInput 7 以前の 64-bit 版は存在しない*6

てな感じですかね.

WOW64 と異種バイナリの混在

ついでに思い出したのが,Windows でユーザ作成の 64-bit バイナリと 32-bit バイナリが単一プロセスで混在できないという WOW64 の問題を,メモリレイアウト由来としている Wikipedia のこの記述.さすがに問題を単純化しすぎてるんじゃないでしょーか.

問題点

Windowsの64ビットABIは、そのままWin32の32ビットABIを64ビットに拡張した物である。従って、64ビットABIのアプリケーションは8TBのアドレス空間を独占的に使える様になっている。ここに一つの問題点がある。32ビット ABIのコードを格納可能な仮想空間下位4GBが64ビットABIに独占されてしまった事である。この為、32ビットABIを格納する場所が無く、32ビットアプリケーションはもとより、DLLやOCXをロードして呼び出すことも不可能となってしまった。そのため、マイクロソフトは、32ビットABIのコードと64ビットABIのコードとの相互な呼び出しを禁止している(COMインターフェースを経由して相互乗り入れは可能であるが、x64アーキテクチャで本来可能であったシームレスな32ビットコードと64ビットコードの相互呼び出し機能は全く生かされていない)。この顕著な例として、Internet Explorerの振舞があげられる。32ビットのActiveXコンポーネントを検出すると、64ビット版Internet Explorerは処理を中断して32ビット版Internet Explorerに処理を引き継ぐ。32ビットアプリケーションと64ビットアプリケーションの間には、実行ファイル以外のコンポーネント群を互いに利用することができない深い溝がある。

「Windowsの64ビットABIは、そのままWin32の32ビットABIを64ビットに拡張した物である。」て時点でたぶん外していて,例外処理に関する ABI が違うじゃんと.

リーフ関数 [関数を呼び出したり、任意のスタック空間に自身を割り当てたりしない関数] 以外のすべての関数には、不揮発性レジスタを回復するために、それらを正しくアンワインドする方法をオペレーティング システムに対して記述したデータで注釈を付ける必要があります。このデータは xdata または ehdata と呼ばれ、pdata から指されます。Prolog と epilog は、xdata で正しく記述されるように大幅に制限されます。スタック ポインタは、リーフ関数を除いて、epilog または prolog の一部でない任意のコード領域で 16 バイトに配置される必要があります。prolog および epilog 関数の正しい構造の詳細については、「プロローグとエピローグ」を参照してください。例外処理および例外処理/アンワインディング pdata および xdata の詳細については、「例外処理 (x64)」を参照してください。

仮にメモリレイアウトを工夫して 64-bit コードから 32-bit コードを (またはその逆を) 呼び出せたとして,そこで例外起きたらどうすりゃいいんでしょうね? さらにその応用編として,64-bit コードから 32-bit コードを呼び出し,さらに 64-bit コードにコールバックされた状態で例外が発生した場合も.
もちろん上に挙げたような 32-bit プロセスと 64-bit プロセスの違いもあります.UMS を使う DLL が 32-bit プロセスにロードされた場合,果たして UMS をサポートすべきか否かとか.
他にも API をフックするコードに,プロセス環境ブロック (PEB) やスレッド環境ブロック (TEB) を見に行くコード,自分でコールスタックを遡っちゃうコード,プロセス内でひとつしか存在しないと仮定して書かれた DLL の 32-bit 版と 64-bit 版が同時にロードされてしまうケース,環境変数は 64-bit モジュールと 32-bit モジュールで分かれていた方が良いのか否か,等々.
そんなあれそれで,本気で異種バイナリの混在させたかったら,メモリレイアウト云々は所詮と氷山の一角と思うわけです.

*1:[http://msdn.microsoft.com/ja-jp/magazine/cc163486.aspx:title=アプリケーションで Windows Vista のユーザー アカウント制御を有効に活用する - MSDN Magazine 2007 年 1 月号]

*2:[http://msdn.microsoft.com/ja-jp/library/ms235286.aspx:title=x64 呼び出し規約の概要 - MSDN ライブラリ]

*3:[http://msdn.microsoft.com/en-us/library/dd627187.aspx:title=User-Mode Scheduling - MSDN ライブラリ]

*4:[http://d.hatena.ne.jp/egggarden/20091018/1255844431:title=実行可能属性のページとDEP - やや温め納豆]

*5:[http://blogs.msdn.com/michael_howard/archive/2008/02/18/faq-about-heapsetinformation-in-windows-vista-and-heap-based-buffer-overruns.aspx:title=FAQ about HeapSetInformation in Windows Vista and Heap Based Buffer Overruns - Michael Howard's Web Log]

*6:[http://msdn.microsoft.com/en-us/library/ee418798.aspx:title=64-bit programming for Game Developers - MSDN ライブラリ]