コンピューティングの世界では、DLL 地獄とは、Microsoft Windowsオペレーティングシステム[1]、特に単一のメモリ空間で実行される従来の16 ビット版で使用されるダイナミックリンクライブラリ(DLL)を操作するときに発生する複雑な問題を指す用語です。
DLL 地獄は、アプリケーションが起動しない、または正しく動作しないなど、さまざまな形で現れる可能性があります。
DLL 地獄は、一般的な概念である依存関係地獄の Windows エコシステム固有の形式です。
問題
DLL は、Microsoft の共有ライブラリの実装です。共有ライブラリを使用すると、共通コードをラッパー (DLL) にバンドルできます。DLL は、システム上のすべてのアプリケーション ソフトウェアで、メモリに複数のコピーをロードすることなく使用できます。簡単な例としては、多くのプログラムで広く使用されているGUIテキスト エディターが挙げられます。このコードを DLL に配置すると、システム上のすべてのアプリケーションで、メモリを消費することなく使用できます。これは、機能的には同じですが、コードを直接アプリケーションにコピーする静的ライブラリとは対照的です。この場合、すべてのアプリケーションは、使用するすべてのライブラリのサイズだけ大きくなり、これは最近のプログラムでは非常に大きくなる可能性があります。
問題は、コンピュータ上の DLL のバージョンが、プログラムの作成時に使用されたバージョンと異なる場合に発生します。DLL には下位互換性のための組み込みメカニズムがないため、DLL に小さな変更を加えただけでも、その内部構造が以前のバージョンと大きく異なることがあり、その DLL を使用しようとすると、通常はアプリケーションがクラッシュします。静的ライブラリでは、アプリケーションの構築に使用されたバージョンがライブラリ内に含まれているため、この問題を回避できます。そのため、システムの他の場所に新しいバージョンが存在しても、アプリケーションには影響しません。
バージョンの非互換性の主な原因は、DLL ファイルの構造です。ファイルには、DLL 内に含まれる個々のメソッド (プロシージャ、ルーチンなど) のディレクトリと、それらが取得および返すデータのタイプが含まれています。DLL コードに小さな変更を加えただけでも、このディレクトリが再編成される可能性があります。その場合、ディレクトリ内の 4 番目の項目であると信じて特定のメソッドを呼び出すアプリケーションは、完全に異なる互換性のないルーチンを呼び出すことになり、通常はアプリケーションがクラッシュすることになります。
DLL では、特にシステムに多数のアプリケーションをインストールおよびアンインストールした後によく発生する問題がいくつかあります。これらの問題には、DLL バージョン間の競合、必要な DLL の取得の難しさ、不要な DLL コピーが多数あることなどがあります。
これらの問題の解決策は、Microsoft が DLL システムを作成していたときからすでにわかっていました。[引用が必要]これらは、 .NET の代替である「アセンブリ」 に組み込まれています。
互換性のないバージョン
ライブラリの特定のバージョンは、それを使用する一部のプログラムとは互換性があるが、他のプログラムとは互換性がない場合があります。Windows は、C++ ライブラリとオブジェクトのリンクと埋め込み(OLE) オブジェクトの動的リンクを重視しているため、特にこの問題に対して脆弱です。C++ クラスは多くのメソッドをエクスポートし、新しい仮想メソッドなどのクラスへの 1 つの変更により、以前のバージョンに対して構築されたプログラムとの互換性がなくなる可能性があります。オブジェクトのリンクと埋め込みには、これを防ぐための非常に厳格なルールがあります。インターフェイスは安定している必要があり、メモリ マネージャーは共有されません。ただし、クラスのセマンティクスは変更される可能性があるため、これだけでは不十分です。1 つのアプリケーションのバグを修正すると、別のアプリケーションの機能が削除される可能性があります。Windows 2000より前のWindows は、COMクラス テーブルがすべてのユーザーとプロセスで共有されていたため、この問題に対して脆弱でした。システム上で特定のグローバル COM クラス ID を持つと宣言できるのは、1 つの DLL/EXE 内の 1 つの COM オブジェクトだけです。プログラムでそのクラスのインスタンスを作成する必要がある場合、そのクラスは、現在集中登録されている実装を取得しました。その結果、共通オブジェクトの新しいバージョンをインストールするプログラムをインストールすると、以前にインストールされた他のプログラムが意図せず破損する可能性があります。
DLL ストンピング
よくある厄介な問題は、新しくインストールされたプログラムが、動作中のシステム DLL を以前の互換性のないバージョンで上書きするときに発生します。この初期の例としては、Windows 3.1のctl3d.dllおよびライブラリがあります。これは、Microsoft が作成したライブラリで、サードパーティの発行者がソフトウェアとともに配布していましたが、各発行者は最新バージョンではなく、独自に開発したバージョンを配布していました。[2] DLL ストンピングは、次の理由で発生します。
ctl3dv2.dll
- マイクロソフトは過去に、RAMとディスク容量が限られた共有メモリOSでコードを効率的に共有する方法として、ランタイムDLLを共有システムコンポーネント[3](当初はC:\WINDOWSとC:\WINDOWS\SYSTEM)として配布していました。その結果、サードパーティの開発者も同様の方法でこれらを配布しました。
- アプリケーション インストーラーは通常、システム ディレクトリに DLL をインストールしたり、システム レジストリを編集して新しい DLL をCOMオブジェクトとして登録したりできる特権セキュリティ コンテキストで実行されます。したがって、インストーラーが適切に作成されていないか、正しく構成されていない場合、Windows の旧バージョンではシステム ライブラリがダウングレードされる可能性があります。この場合、Windows ファイル保護またはWindows リソース保護では変更がロールバックされません。Windows Vista 以降では、「信頼されたインストーラー」アカウントのみがコア オペレーティング システム ライブラリに変更を加えることができます。
- Windows アプリケーションは、独自のインストール プログラムに OS アップデートを含めることが許可されていました。つまり、多くの Microsoft DLL は再配布可能であり、特定のライブラリのサービスが必要な場合、アプリケーションにそれらを組み込むことができるということです。
- Windows Installer以前は、Windows インストーラは歴史的に商用製品でした。多くの人が独自のインストーラを作成しようとしましたが、その過程でバージョン管理の問題を見落としたり、誤って処理したりしていました。[引用が必要]
- 一部の開発環境では、コンパイルされたライブラリにバージョン リソースが自動的に追加されないため、多くの開発者がこの点を見落としていました。正しいバージョン管理の代わりに、ファイルの日付をチェックしたり、既存のファイルを上書きしたり、DLL がすでにインストールされている場合はコピー操作をスキップしたりすることしかできませんでした。[引用が必要]
- 場合によっては、OS自体がDLLを削除したり、古いバージョンや廃止されたバージョンに置き換えたりすることがあります。たとえば、Windows 2000では、カラープリンタの後に白黒プリンタがインストールされた場合、カラー対応DLLの上に白黒プリンタDLLがインストールされます。[4]
COM登録が正しくありません
COMや Windows の他の部分では、サイドバイサイドのレジストリフリーアセンブリが導入される前は、 [5]レジストリを使用して、使用する基礎となる DLL を決定していました。モジュールの異なるバージョンが登録されている場合、予期される DLL ではなくこの DLL が読み込まれます。このシナリオは、同じライブラリの異なるバージョンを登録する競合するインストールによって発生する可能性があり、その場合は最後のインストールが優先されます。
共有インメモリモジュール
16 ビット バージョンの Windows (およびWindows 上の Windows ) では、特定の DLL のインスタンスが 1 つだけロードされます。すべてのアプリケーションは、その DLL を使用しているアプリケーションがなくなり、メモリからアンロードされるまで、同じメモリ内コピーを参照します。(32 ビット バージョンと 64 ビット バージョンの Windows では、異なる実行可能ファイルがまったく同じディレクトリからモジュールをロードする場合にのみ、プロセス間共有が発生します。スタックではなくコードは、「メモリ マッピング」と呼ばれるプロセスを通じてプロセス間で共有されます。) したがって、必要な DLL が、システム ディレクトリやアプリケーション ディレクトリなど、見つかることが予想されるディレクトリにある場合でも、別のアプリケーションが 3 番目のディレクトリから互換性のないバージョンで起動されている場合は、これらのインスタンスのどちらも使用されません。この問題は、アプリケーションが特定の順序で起動された場合にのみ発生する 16 ビット アプリケーション エラーとして現れることがあります。
保守性の欠如
DLL ストンピング問題と直接矛盾する点: DLL の更新が、それを使用するすべてのアプリケーションに影響しない場合、 DLL を「サービス」すること、つまり、DLL の現在のバージョンに存在する問題を排除することが非常に困難になります。(セキュリティ修正は特に切実で困難なケースです。) 実装者は、最新バージョンの DLL だけを修正するのではなく、理想的には修正を行い、DLL のすべてのリリース バージョンで互換性をテストする必要があります。
原因
DLL の非互換性の原因は次のとおりです:
- メモリ制約と、16 ビット バージョンの Windows におけるプロセス メモリ空間の分離の欠如の組み合わせ。
- DLL に対して強制された標準的なバージョン管理、命名、およびファイル システムの場所のスキーマが不足しています。
- ソフトウェアのインストールと削除(パッケージ管理)のための強制的な標準方法の欠如。
- DLLアプリケーション バイナリ インターフェイスの管理と保護に対する集中的な権威あるサポートが不足しているため、同じファイル名と内部バージョン番号を持つ互換性のない DLL がリリースされる可能性があります。
- 管理ツールが過度に単純化されているため、ユーザーや管理者が変更または問題のある DLL を識別できない。
- 開発者が共有モジュール内の関数の下位互換性を破壊している。
- Microsoft がオペレーティング システムのランタイム コンポーネントに対する帯域外更新をリリースします。
- 以前のバージョンの Windows では、同じライブラリの競合するバージョンを並行して実行できませんでした。
- 依存する DLL を見つけるために、現在のディレクトリまたは
%PATH%環境変数に依存します (どちらも時間の経過とともに、またシステムごとに変化します) (明示的に構成されたディレクトリから DLL をロードするのではなく)。 - 開発者は、独自の新しいGUID を生成するのではなく、サンプル アプリケーションの ClassID をアプリケーションの COM インターフェイスに再利用します。
DLL 地獄は、Windows NT 以前のバージョンの Microsoft オペレーティング システムでは非常に一般的な現象でした。主な原因は、16 ビット オペレーティング システムではプロセスが独自のメモリ領域に制限されず、互換性のある共有モジュールの独自のバージョンをロードできなかったことです。アプリケーション インストーラーは、既存のシステム DLL を上書きする前に DLL のバージョン情報を確認する善良な市民であることが求められていました。アプリケーションの展開 (これには常に、依存するオペレーティング システム DLL の出荷が含まれます) を簡素化する標準ツールは、Microsoft およびその他のサード パーティ ツール ベンダーによって提供されました。Microsoft は、アプリケーション ベンダーに対して、Microsoft ロゴの使用を許可する前に、標準インストーラーを使用し、インストール プログラムが正しく動作することを認定されることさえ要求しました。善良な市民インストーラー アプローチでは、問題は軽減されませんでした。インターネットの人気の高まりにより、非準拠のアプリケーションを入手する機会が増えたためです。
マルウェアによる使用
完全修飾されていないDLLをWindowsオペレーティングシステムにロードできる曖昧さは、近年マルウェアによって悪用されており、 [いつ? ] Windows自体だけでなく、さまざまなソフトウェアベンダーのアプリケーションに影響を及ぼす新しい種類の脆弱性を生み出しています。[6]
ソリューション
長年にわたって、さまざまな形式の DLL 地獄が解決または緩和されてきました。
静的リンク
アプリケーションにおけるDLL地獄の簡単な解決法は、すべてのライブラリを静的にリンクすることです。つまり、指定された名前のシステムライブラリを選択するのではなく、プログラムで必要なライブラリバージョンを含めることです。 [7]これはC/C++アプリケーションでは一般的で、どのバージョンのがインストールされているかを気にする代わりにMFC42.DLL、アプリケーションは同じライブラリに対して静的にリンクされるようにコンパイルされます。これによりDLLが完全に排除され、Microsoft Foundation Classライブラリのように静的オプションを提供するライブラリのみを使用するスタンドアロンアプリケーションで可能になります。ただし、DLLの主な目的である、プログラム間でランタイムライブラリを共有してメモリオーバーヘッドを削減することは犠牲になります。複数のプログラムでライブラリコードを重複させると、ソフトウェアが肥大化し、セキュリティ修正プログラムや依存ソフトウェアの新しいバージョンの展開が複雑になります。
Windows ファイル保護
DLL 上書き問題 ( Microsoft ではDLL Stompingと呼ばれています) は、 Windows 2000で導入されたWindows ファイル保護(WFP) [8]によっていくらか軽減されました。[9]これにより、許可されていないアプリケーションがシステム DLL を上書きするのを防止できます (ただし、上書きを許可する特定のWindows APIを使用している場合は除きます)。Microsoft からの更新プログラムが既存のアプリケーションと互換性がないというリスクは依然として存在する可能性がありますが、このリスクは、現在のバージョンの Windows ではサイドバイサイド アセンブリの使用によって軽減されるのが一般的です。
サードパーティのアプリケーションは、インストーラーに正規の Windows アップデートをバンドルするか、インストール中にWindows ファイル保護サービスを無効にし、Windows Vista 以降ではシステム ファイルの所有権を取得して自分自身にアクセス権を付与しない限り、OS ファイルを侵害することはできません。SFCユーティリティは、いつでもこれらの変更を元に戻すことができます。
競合するDLLを同時に実行する
ここでの解決策は、ディスク上とメモリ内の両方で、アプリケーションごとに同じ DLL の異なるコピーを持つというものです。
競合に対する簡単な手動の解決策は、問題のある DLL の異なるバージョンを、システム全体の共通フォルダではなく、アプリケーションのフォルダに配置することでした。これは、アプリケーションが 32 ビットまたは 64 ビットであり、DLL が共有メモリを使用していない限り、一般的に機能します。16 ビット アプリケーションの場合、2 つのアプリケーションを 16 ビット プラットフォームで同時に実行したり、32 ビット オペレーティング システムの同じ 16 ビット仮想マシンで実行したりすることはできません。Windows 98 SE/2000 より前のバージョンの Windows では、すべてのアプリケーションに対して COM オブジェクトの単一のレジストリがあったため、 OLEによってこれが防止されていました。
Windows 98 SE/2000 では、サイドバイサイド アセンブリと呼ばれるソリューションが導入されました。 [10]これは、DLL を必要とするアプリケーションごとに別々の DLL のコピーをロードします (これにより、競合する DLL を必要とするアプリケーションを同時に実行できるようになります)。このアプローチでは、アプリケーションがモジュールの一意のバージョンをアドレス空間にロードできるようにすることで競合を排除すると同時に、メモリ マッピング技術を使用して同じモジュールを使用する異なるプロセス間で共通コードを共有することで、アプリケーション間で DLL を共有する主な利点 (つまり、メモリ使用量の削減) を維持します。ただし、複数のプロセス間で共有データを使用する DLL では、このアプローチを採用できません。[11] 1 つのマイナス面は、DLL の孤立したインスタンスが自動プロセス中に更新されない可能性があることです。
ポータブルアプリケーション
アプリケーションアーキテクチャとランタイム環境によっては、すべてのプログラムが必要なDLLの独自のプライベートコピーをバンドルするため、ポータブルアプリケーションはDLLの問題を軽減する効果的な方法となる場合があります。 [9] このメカニズムは、アプリケーションが依存DLLをロードするときにパスを完全に修飾しないことと、オペレーティングシステムが共有場所の前に実行可能ディレクトリを検索することを前提としています。[12] ただし、この手法はマルウェアに悪用される可能性があり、[13]プライベートDLLが共有DLLと同じようにセキュリティパッチで最新の状態に保たれていない場合、柔軟性の向上はセキュリティを犠牲にする可能性もあります。
アプリケーション仮想化により、アプリケーションを「バブル」内で実行することもできるため、DLL ファイルをオペレーティング システムに直接インストールする必要がなくなります。
その他の対策
DLL 地獄を回避するための他の対策もあり、そのいくつかは同時に使用する必要があるかもしれません。問題を軽減するのに役立つ他の機能は次のとおりです。
- インストール ツールは、Windows 開発の主要環境の 1 つであるMicrosoft Visual Studioにバンドルされるようになりました。これらのツールは、DLL のインストール前にバージョン チェックを実行し、.MSI インストールに定義済みのインストール パッケージを含めることができます。これにより、サード パーティ アプリケーションは、これらのコンポーネント用の独自のインストーラーを作成しなくても、OS コンポーネントの更新を統合できます。
- システムの復元は、レジストリの損傷を含む不適切なインストールからシステムを回復できます。これによって問題が防止されるわけではありませんが、回復が容易になります。
- WinSxS ( Windows Side-by-Side ) ディレクトリ。これにより、同じライブラリの複数のバージョンが共存できるようになります。
- 32 ビット バージョンの Windows で別のメモリ スペースで 16 ビット アプリケーションを実行し、2 つのアプリケーションが同じ DLL の競合するバージョンを同時に使用できるようにします。
- Windows ファイル保護を含む Windows バージョンを使用します。2000 年にリリースされたWindows MeとWindows 2000 は、この形式のシステム ファイル保護をサポートしています。Windows XPとWindows Server 2003も同様です。Windows Vista と Windows Server 2008 では、これに代わるWindows リソース保護が導入され、システム ファイルが変更されないように保護する別の方法が採用されています。
- 登録不要の COM: Windows XP では、 「登録不要の COM 」と呼ばれる新しい COM オブジェクト登録モードが導入されました。この機能により、COM オブジェクトをインストールする必要のあるアプリケーションは、必要なすべての COM レジストリ情報を、グローバル システム レジストリではなく、アプリケーション自身のディレクトリに格納できます。したがって、複数のアプリケーションによって同じ DLL の複数のバージョンが同時に登録されるメカニズムが提供されます (Microsoft はこれを「サイドバイサイド アセンブリ」と呼んでいます[14] )。登録不要の COM を使用すると、DLL 地獄を大幅に回避できます。唯一の制限は、少なくともWindows XP以降のバージョンの Windows が必要であり、EXE COM サーバーや、 MDAC、MSXML、DirectX、Internet Explorerなどのシステム全体のコンポーネントには使用できないことです。
- DLL の依存関係を追跡できる 優れたパッケージ管理システムを搭載したオペレーティング システムを出荷することで、パッケージ マネージャーの使用が促進され、DLL の手動インストールが抑制されます。Windows Me、Windows 2000、およびそれ以降のすべてのバージョンに含まれるWindows インストーラーは、この機能を提供します。
- DLL の競合解決とソフトウェア配布のための中央データベースまたは権限を持つこと。ライブラリへの変更はこの権限に送信できるため、開発されたブランチで互換性が維持されることを確認できます。古いソフトウェアの一部が現在のライブラリと互換性がない場合は、権限によって互換性インターフェイスを提供したり、古いバージョンを別のパッケージとしてバンドルしたりできます。
- ソフトウェア開発者がライブラリをカスタマイズする必要があり、メインのライブラリ リリースに必要な変更が組み込まれそうにない場合は、カスタマイズされた DLL をプログラムのプライベート使用のために出荷するか (通常はプログラムのプライベート ディレクトリに配置する)、カスタマイズされたライブラリに対してプログラムを静的にリンクすることができます。
- DLL はアプリケーションやシステムのコンポーネントをモジュール化する場合や、サードパーティ ライブラリとして使用する場合に最適ですが、メモリが制約ではなくなった最新のシステムでは、すべてのケースで DLL の使用が必須というわけではありません。たとえば、アプリケーションで他の場所では使用されないライブラリが必要な場合は、静的にリンクすることで、スペースを犠牲にすることなく、速度を向上させることができます。
- Windows Vista 以降では、特別なTrustedInstallerサービスを使用してオペレーティング システム ファイルをインストールします。SYSTEM を含む他のユーザー アカウントには、コア システム バイナリを上書きするアクセス権がありません。Windows 7 では、この機能がレジストリの重要な部分に拡張されています。
参照
参考文献
- ^ 「DLL 地獄の回避: Microsoft .NET Framework でのアプリケーション メタデータの導入」。Microsoft。2000 年 10 月。
- ^ 「Microsoft サポート技術情報にある CTL3D.DLL 記事の概要」。Microsoft。
- ^ Visual C++ 2005 および Visual C++ .NET の共有 C ランタイム コンポーネントの再配布。
- ^ KB 830490: HP Color LaserJet プリンタは、Windows 2000 SP4 ベースのコンピュータではグレースケールまたは白黒でのみ印刷されます。
- ^ Leslie Muller、Steve White (2005 年 7 月)。「COM コンポーネントの登録不要のアクティベーション: ウォークスルー」。Microsoft。
- ^ 「DLL プリロード攻撃を防ぐためのライブラリの安全なロード」。Microsoft。2011 年 6 月 11 日。2011 年 7 月 19 日閲覧。
- ^ Pfeiffer, Tim (1998-06-01). 「Windows DLL: 脅威か脅威か?」 Dr. Dobb's Journal。2010-08-07 にオリジナルからアーカイブ。2010-07-07に取得。
- ^ Windows ファイル保護と Windows。
- ^ ab Anderson, Rick (2000-01-11). 「DLL 地獄の終焉」. microsoft.com. 2001-06-05 時点のオリジナルよりアーカイブ。2010-07-07に取得。
- ^ 「アプリケーションでのサイドバイサイドコンポーネント共有の実装 (拡張版)」。Microsoft。2006 年 12 月 10 日時点のオリジナルよりアーカイブ。2013 年1 月 3 日に閲覧。
- ^ 「DLL 内のデータをアプリケーションまたは他の DLL と共有するにはどうすればよいですか?」。Microsoft。2008年 11 月 11 日閲覧。
- ^ 「DLL プリロード攻撃を防ぐためのライブラリの安全なロード」Microsoft 。2013 年2 月 16 日閲覧。
- ^ サイドバイサイドアセンブリ (Windows)
外部リンク
- Microsoft TechNet の DLL 地獄からの脱出
- MSDN の .NET Framework による展開の簡素化と DLL 地獄の解決
- DLL 地獄の回避: Microsoft .NET Framework でのアプリケーション メタデータの導入 ( Matt Pietrek著)
- DLL 地獄についての Dr. Dobb の解説 (LoadLibraryEx の詳細)
- ソフトウェアに関するジョエルの議論 2018-10-30 にWayback Machineでアーカイブ
- DLL 地獄に関する記事
