DLLの闇 (3)

実際にプロセスを起動してプロファイリングを行う機能というのは,静的解析の考慮漏れに対するある意味での切り札です.そんな切り札も用意してくれている以上,あまり重箱の隅をつついても仕方がありませんが,一応 Dependency Walker の静的解析についても少し補足しておきましょう.
Dependency Walker がデフォルトで考慮しない要素がいくつかあります*1.これらの要素については,Dependency Walker の Module Search Orders を手で編集することで,かなりの割合を反映させることが出来ます.

  • DLL 検索でのカレントディレクトリの存在と,その優先順位
  • .local という拡張子を持ったファイルまたはディレクトリによる DLL/COM リダイレクション

また,暗黙のリンクではなく,LoadLibrary による実行時の明示的なロードも,静的解析が不可能に近い要素です.
『Advanced Windows 第4版(asin:4756138055)』第22章「DLLの注入とAPIフック」にあるような,複雑な方法による DLL の注入が行われていると,(静的な解析では)予期しない問題が発生する可能性があります.また DllMain 中から LoadLibrary を呼び出すといった,推奨されない行為を行ってしまっている例もしばしば見られます*2

以下の操作は、大部分の状況で、DllMain 関数内部で実行すると安全ではないと明確に認識されています。

  • 直接的または間接的な、LoadLibrary 関数、LoadLibraryEx 関数、または FreeLibrary 関数の呼び出し。
  • レジストリ関数の呼び出し。
  • Kernel32.dll 内に存在しないインポートされた関数の呼び出し。
  • 他のスレッドまたはプロセスとの通信。

この他,近年の Windows システム DLL で多用される DLL の遅延ロードも事態を複雑化させている要因の 1 つでしょう.
こうなってくると,正しい DLL が呼ばれているかどうかについてのテストフレームワークが欲しくなってきます.設定や状況のパターンが非常に多く毎回資料を調べるだけでは追いつかないのと,そもそも MSDN の書き方に曖昧な点が存在するという理由から,C# でも専用言語でも何でもいいですが,とにかくテストを自動化しないことには対処できない規模だと感じます.



Joseph M. Newcomer 氏は,複雑怪奇な LoadLibrary の挙動をテストするために,LoadLibrary Explorer というツールを作成されています.
http://www.flounder.com/loadlibrary_explorer.htm
このツールを使えば,複数のディレクトリにターゲット DLL を配置し,LoadLibrary API でどの DLL が読み込まれるかを,様々な設定で実験することが可能です.
氏はこのツールを使い,実際にドキュメントの不具合をいくつか発見したと書かれています.

Bug summary

Using the LoadLibrary Explorer, I have determined the following bugs appear in the Microsoft documentation:

  • Dynamic-Link Library Redirection, the use of the .local file or directory, does not work in Windows XP SP2 or Windows Server 2003.
  • SafeDllSearchMode does not work even for load-time dynamic linking
  • LOAD_WITH_ALTERED_SEARCH_PATH does not work; if the DLL is not found in the directory with the given path, it does not change the behavior of either implicit or explicit dynamic linking.

ただ,手元の環境で試した限り,これらの挙動はドキュメントの記述の曖昧さに原因があるようです.

  • MSDN にはどの時点で .local ファイルまたはディレクトリが存在している必要があるのか記述がない.(LoadLibrary Explorer は,プロセス起動後にこれらのファイルを作成する)
  • SafeDllSearchMode の設定変更後,システムを再起動する必要がある.(LoadLibrary Explorer は,レジストリ値を変更した直後に動作を検証している)
  • LoadLibraryEx の解説にある,以下の 2 つのルールの優先順位が不透明.
    • 絶対パスで渡されたファイルが存在しなかった場合 → 呼出しは失敗する
    • LOAD_WITH_ALTERED_SEARCH_PATH が指定されたとき,最初の検索ディレクトリにファイルが存在しなかった場合 → 次の検索ディレクトリを探す



他にも MSDN の記述には気になるところがあって,先ほど実験しているときも,こんな記述で随分と混乱してしまいました.

Known DLLs cannot be redirected. For a list of known DLLs, see the following registry key:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs. The system uses Windows File Protection to ensure that system DLLs such as these are not updated or deleted except by operating system (OS) updates such as service packs.

Windows 2000:
Known DLLs can be redirected in this OS.

If the application has a manifest, then any .local files are ignored.

Windows 2000:
Manifests do not affect DLL redirection.

私は最初,「Known DLLs に含まれる DLL は,.local によるリダイレクトから除外される」と思っていたのですが,Windows XP で試しても Windows Vista で試しても,リダイレクトは行われています.さらなる仕様変更が行われているのでなければ,この記述は,「リダイレクトは働くけど,リダイレクト可能なようには設計されていないよ」と読まざるを得ません.ドキュメントよりもアルゴリズムをあらわす擬似コードを書いてくれた方が楽だったかも知れません.

*1:.NET のアセンブリルックアップについても Dependency Walker では解析が難しいかと思います.ただし .NET の世界のみであれば,資料がしっかりしていますし,それ程苦労することはないでしょう.また,混合モードアセンブリの解析では Dependency Walker が役立つと予想されます.

*2:『Advanced Windows 第4版(asin:4756138055)』22.2 「レジストリを使ったDLLの注入」で紹介されているように,user32.dll がこのルールを破ってしまっているという例もあります.user32.dll は,DLL_PROCESS_ATTACH 通知を受け取ると,レジストリの [google:AppInit_DLLs] に指定された DLL をLoadLibrary で呼び出します.