内部デバイスに精通していること。NETFramework。 CLRがオブジェクトを作成する方法を見てみましょう

「Habrahabr」の読者の注意は、.NETの内部構造に関するKhan KommalapatiとTom Christianの記事の翻訳です。 Microsoftの Webサイトには代替の翻訳オプションがあります



この記事では以下について説明します。





使用されるテクノロジー:.NET Framework、C#



内容



  1. ブートローダーによって作成されたドメイン
  2. システムドメイン
  3. 共有ドメイン(共有)
  4. デフォルトドメイン
  5. ヒープローダー
  6. タイプの基本
  7. オブジェクトインスタンス
  8. メソッド表
  9. ベースインスタンスサイズ
  10. メソッドスロットテーブル
  11. メソッド記述子
  12. 仮想インターフェイスメソッドテーブルとインターフェイスマップのテーブル
  13. 仮想配布
  14. 静的変数
  15. イークラス
  16. おわりに




Common Runtime(CLR)は、Windows上でアプリケーションを構築するための主要なインフラストラクチャになりつつある(または既になっている)ため、その内部構造を深く理解することで、効率的な産業用アプリケーションを作成できます。



この記事では、オブジェクトインスタンスのレイアウト、メソッドテーブルのレイアウト、メソッドの配布、フロントエンドの配布、さまざまなデータ構造など、CLRの内部構造について説明します。



C#コードの非常に単純なフラグメントを使用します。プログラミング言語の構文の暗黙的な使用は、C#を意味します。 説明したデータ構造とアルゴリズムの一部は、Microsoft®.NET Frameworkの将来のバージョンで変更される予定ですが、概念的なフレームワークは同じままです。 VisualStudio®.NET 2003デバッガーとSon of Strike(SOS)デバッグ拡張機能を使用して、この記事で説明したデータ構造を表示します。 SOSは内部CLRデータをダウンロードし、目的の情報を表示、保存できるようにします。 適切なソースでデバッガプロセスにSOS.dllをロードする手順を参照してください。

SOS.dllをVisual Studio .NET 2003デバッガープロセスに読み込む方法については、「Son of Strike」サイドバーをご覧ください。



この記事では、共有ソースCLI(SSCLI)の実装に対応するクラスについて説明します。



図1の表は、必要な構造を検索しながら、SSCLIでメガバイトのコードを調べるのに役立ちます。



図1 SSCLIリンク
成分 SSCLIパス
Appdomain /sscli/clr/src/vm/appdomain.hpp
AppDomainStringLiteralMap /sscli/clr/src/vm/stringliteralmap.h
ベースドメイン /sscli/clr/src/vm/appdomain.hpp
クラスローダー /sscli/clr/src/vm/clsload.hpp
イークラス /sscli/clr/src/vm/class.h
フィールドデスク /sscli/clr/src/vm/field.h
Gcheap /sscli/clr/src/vm/gc.h
GlobalStringLiteralMap /sscli/clr/src/vm/stringliteralmap.h
取り扱い可能 /sscli/clr/src/vm/handletable.h
InterfaceVTableMapMgr /sscli/clr/src/vm/appdomain.hpp
ラージオブジェクトヒープ /sscli/clr/src/vm/gc.h
レイアウト種類 /sscli/clr/src/bcl/system/runtime/interopservices/layoutkind.cs
ローダーヒープ /sscli/clr/src/inc/utilcode.h
MethodDescs /sscli/clr/src/vm/method.hpp
メソッドテーブル /sscli/clr/src/vm/class.h
OBJECTREF /sscli/clr/src/vm/typehandle.h
セキュリティコンテキスト /sscli/clr/src/vm/security.h
SecurityDescriptor /sscli/clr/src/vm/security.h
共有ドメイン /sscli/clr/src/vm/appdomain.hpp
StructLayoutAttribute /sscli/clr/src/bcl/system/runtime/interopservices/attributes.cs
Syncablentry /sscli/clr/src/vm/syncblk.h
システム名前空間 / sscli / clr / src / bcl /システム
システムドメイン /sscli/clr/src/vm/appdomain.hpp
タイプハンドル /sscli/clr/src/vm/typehandle.h




先に進む前に注意する必要がある瞬間-この記事で提供される情報は、.NET Framework 1.1に対してのみ有効です(基本的には、さまざまな対話シナリオに存在するいくつかの注目すべき例外を考慮して、共有ソースCLI 1.0にも対応します)。 x86プラットフォームでのパフォーマンス。 .NET Frameworkの将来のバージョンでは情報が変更されているため、これらの内部構造への絶対リンクを使用してアプリケーションを構築しないでください。



CLRブートローダーによって作成されたドメイン



マネージコードの最初の行を開始する前に、3つのアプリケーションドメインが作成されます。 そのうちの2つはマネージコードでは利用できず、CLRホストからも見えません。 mscoree.dllおよびmscorwks.dllバス(またはマルチプロセッサシステムの場合はmscorsvr.dll)を使用してCLRをロードすることによってのみ作成できます。 図2からわかるように、これはシステムドメインと共有(共通)ドメインであり、1つのインスタンスにのみ存在できます。 3番目のドメインはデフォルトで、このアプリケーションドメインのインスタンスのみが名前を持っています。 コンソールアプリケーションなどの単純なCLRホストの場合、デフォルトのアプリケーションドメイン名には実行可能イメージの名前が含まれます。 AppDomain.CreateDomainメソッドを使用してマネージコードから、またはICORRuntimeHostインターフェイスを使用してアンマネージコードホストから追加のドメインを作成できます。



ASP.NETなどの複雑なホストは、提供されるWebサイトで実行されているアプリケーションの数に応じて、必要な数のドメインを作成します。





図2. CLRローダーによって作成されたドメイン



システムドメイン



システムドメインは、共有ドメイン(SharedDomain)およびデフォルトドメイン(Default)を作成および初期化します。 また、mscorlib.dllシステムライブラリを共有ドメインにダウンロードします。



システムドメインには、プロセス境界内で使用可能な文字列定数も含まれ、明示的にインターンされているか、明示的にインターンされていません。



CLRではアセンブリがこの機能を最適化できないため、文字列インターンは.NET Framework 1.1の全体主義的な最適化機能です。 同時に、メモリはすべてのアプリケーションドメインのすべての文字列リテラルに対して1つの文字列インスタンスのみを格納するために使用されます。



システムドメインは、各アプリケーションドメイン(AppDomain)でインターフェイスマップ(InterfaceVtableMaps)を作成するために使用されるプロセス境界内でインターフェイス識別子を生成する役割も果たします。



システムドメインは、プロセス内のすべてのドメインをトレースし、アプリケーションドメインをロードおよびアンロードする機能を提供します。



共有ドメイン(SharedDomain)



すべてのドメイン中立コードが共有ドメインにアップロードされます。 システムライブラリであるMscorlibは、すべてのアプリケーションドメイン(AppDomains)のユーザーコードに必要です。 このライブラリは、共有ドメインに自動的にアップロードされます。 Object、ValueType、Array、Enum、String、DelegateなどのSystem名前空間の基本型は、CLRブートローダーのロードプロセス中にこのドメインにプリロードされます。 CorBindToRuntimeExの呼び出し中にアプリケーションホストCLRによってLoaderOptimization属性を設定することにより、ユーザーコードをこのドメインにアップロードすることもできます。 コンソールアプリケーションは、System.LoaderOptimizationAttribute属性をアプリケーションのMainメソッドに追加することにより、共有ドメインにコードをアップロードできます。 共有ドメインは、ベースアドレスに対してインデックス付けされたアセンブリマップも管理します。マップは、既定のドメインに読み込まれたアセンブリとマネージコードで作成された他のアプリケーションドメインの一般的な依存関係を管理するための参照テーブルとして機能します。 デフォルトドメインは、ユーザーのプライベートコードをダウンロードするためにのみ使用されます。プライベートコードは、他のアプリケーションからアクセスできません。



デフォルトドメイン



デフォルトドメインは、アプリケーションコードが通常実行されるアプリケーションドメインのインスタンスです。 一部のアプリケーションでは追加のアプリケーションドメインを実行時に作成する必要がありますが(プラグインアーキテクチャや実行時に大量のコードを生成するアプリケーションなど)、ほとんどのアプリケーションは実行時に1つのドメインを作成します。 このドメインで実行されるすべてのコードは、ドメインレベルでコンテキスト的に制限されます。 複数のアプリケーションドメインがアプリケーションに作成されている場合、クロスドメインアクセスはすべて.NET Remotingプロキシを介して発生します。 System.ContextBoundObjectから継承した型を使用して、追加のドメイン内境界を作成できます。



各アプリケーションドメインには、独自のSecurityDescriptor、SecurityContext、DefaultContext、および独自のヒープローダー(高周波ヒープ、低周波ヒープ、スタブヒープ)があります。

記述子テーブル(ハンドルテーブル、ラージオブジェクトヒープハンドルテーブル)、Vtableインターフェイスマップマネージャー、およびアセンブリキャッシュ。



ローダーヒープ



LoaderHeapsは、ドメインの存続​​期間全体にわたって存在していたさまざまなCLRランタイムアーティファクトと最適化アーティファクトを読み込むように設計されています。 これらのヒープは、断片化を最小限に抑えるために予測可能なフラグメントによって増加します。 ローダーヒープは、ガベージコレクター(GC)ヒープ(または対称SMPマルチプロセッサーの場合はヒープセット)とは異なり、ガベージコレクターヒープにはオブジェクトインスタンスが含まれ、ローダーヒープにはシステムタイプが含まれます。 多くの場合、メソッドテーブル、メソッド記述子(MethodDescs)、フィールド記述子(FieldDescs)、インターフェイスマップなどの要求された構造は、頻繁なアクセスのヒープ(HighFrequencyHeap)にあります。 EEClassやクラスローダー(ClassLoader)、およびそれらのサービステーブルなどの呼び出しがよりまれな構造は、呼び出しの頻度が低いヒープ(LowFrequencyHeap)に配置されます。 サービスヒープ(StubHeap)には、コードコードアクセスセキュリティ(CAS)、シェルCOM呼び出し、およびP / Invoke呼び出しでアクセスセキュリティをサポートするブロックが含まれています。 ブートローダーのドメインとヒープを高レベルで調べた後、図3の単純なアプリケーションのコンテキストで物理的な組織をより詳しく見てみましょう。「mc.Method1();」でプログラムを停止し、拡張SumpデバッガーDumpDomainコマンドを使用してドメインダンプを作成します。 結果は次のとおりです。



!DumpDomain System Domain: 793e9d58, LowFrequencyHeap: 793e9dbc, HighFrequencyHeap: 793e9e14, StubHeap: 793e9e6c, Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40 </br> Shared Domain: 793eb278, LowFrequencyHeap: 793eb2dc, HighFrequencyHeap: 793eb334, StubHeap: 793eb38c, Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40 </br> Domain 1: 149100, LowFrequencyHeap: 00149164, HighFrequencyHeap: 001491bc, StubHeap: 00149214, Name: Sample1.exe, Assembly: 00164938 [Sample1], ClassLoader: 00164a78
      
      





図3 Sample1.exe
 using System; public interface MyInterface1 { void Method1(); void Method2(); } public interface MyInterface2 { void Method2(); void Method3(); } class MyClass : MyInterface1, MyInterface2 { public static string str = "MyString"; public static uint ui = 0xAAAAAAAA; public void Method1() { Console.WriteLine("Method1"); } public void Method2() { Console.WriteLine("Method2"); } public virtual void Method3() { Console.WriteLine("Method3"); } } class Program { static void Main() { MyClass mc = new MyClass(); MyInterface1 mi1 = mc; MyInterface2 mi2 = mc; int i = MyClass.str.Length; uint j = MyClass.ui; mc.Method1(); mi1.Method1(); mi1.Method2(); mi2.Method2(); mi2.Method3(); mc.Method3(); } }
      
      







コンソールアプリケーションSample1.exeは、「Sample1.exe」という名前のアプリケーションドメイン(AppDomain)にアップロードされます。 Mscorlib.dllは共有ドメイン(SharedDomain)に読み込まれますが、カーネルドメインライブラリとしてシステムドメイン(SystemDomain)にも表示されます。 高周波アクセスヒープ(HighFrequencyHeap)、低周波アクセスヒープ(LowFrequencyHeap)、およびスタブヒープ(StubHeap)は各ドメインにあります。 システムドメインと共有ドメインは同じクラスローダー(ClassLoader)を使用しますが、デフォルトドメイン(Default AppDomain)は独自のものを使用します。



コマンドの結果は、ローダーヒープの予約済みサイズと使用済みサイズを表示しません。 高周波アクセスヒープは最初に32 KBを予約し、4 KBを使用します。



低頻度アクセスヒープスタブヒープは、最初は8kbを予約し、4kbを占有します。



また、インターフェイスマップのヒープは表示されません(InterfaceVtableMap、以降IVMap)。各ドメインには、ドメイン初期化フェーズ中に独自のローダーヒープ上に作成されるインターフェイスマップがあります。 一連のインターフェースカード(IVMap)は4KBを予約し、最初は4KBを占有します。 次のセクションでタイプレイアウトを調べるときに、インターフェイスマップの重要性について説明します。



図2は、デフォルトのプロセスヒープ、ランタイムコンパイラヒープ(JITコード)、小さなオブジェクトのガベージコレクター(GC)ヒープ(SOH)、および大きなヒープ(LOH)(サイズが85,000バイト以上のオブジェクト)を示しています)それらとローダーヒープのセマンティックの違いを説明します。 JITまたはランタイムコンパイラは、x86アーキテクチャ用の命令を生成し、JITコードのヒープに格納します。 ガベージコレクタとラージオブジェクトは、ガベージコレクタによって処理されるヒープであり、管理オブジェクトはこれらのヒープ上に作成されます。



タイプの基本



タイプは、.NETのプログラミングの基本要素です。 C#では、クラス、構造体、およびインターフェイスのキーワードを使用して型を宣言できます。 ほとんどの型はプログラマー自身によって明示的に作成されますが、特別な相互作用の場合やリモートオブジェクトを呼び出すスクリプト(.NET Remoting)では、.NET CLRは型を暗黙的に生成します。 これらの生成されたタイプには、COMおよびランタイム呼び出し可能ラッパーおよび透過プロキシが含まれます。



オブジェクトへの参照を含むスタックの構造から始めて、.NETの基本型を調べます(原則として、スタックは、オブジェクトインスタンスが存在を開始する場所の1つです)。

図4のコードには、静的メソッドが呼び出されるコンソールエントリポイントを持つ単純なプログラムが含まれています。



Method1は、SmallClass型のインスタンスを作成します。これには、大きなLOHオブジェクトのヒープ内のオブジェクトのインスタンスの作成を示すために使用されるバイトの配列が含まれます。 コードは簡単ですが、議論に関与します。



図4大小のオブジェクト
 using System; class SmallClass { private byte[] _largeObj; public SmallClass(int size) { _largeObj = new byte[size]; _largeObj[0] = 0xAA; _largeObj[1] = 0xBB; _largeObj[2] = 0xCC; } public byte[] LargeObj { get { return this._largeObj; } } } class SimpleProgram { static void Main(string[] args) { SmallClass smallObj = SimpleProgram.Create(84930,10,15,20,25); return; } static SmallClass Create(int size1, int size2, int size3, int size4, int size5) { int objSize = size1 + size2 + size3 + size4 + size5; SmallClass smallObj = new SmallClass(objSize); return smallObj; } }
      
      









図5は、Createメソッドの「return smallObj;」行のブレークポイントで停止した一般的なfastcall呼び出しスタックのスナップショットを示しています。 (Fastcallは、可能な場合に引数がレジスタ内の関数に渡されることを定義する.NET呼び出し規則です。残りの引数は、スタックを右から左に渡され、呼び出された関数によってスタックからポップされます。

有効な型または値型objSizeのローカル変数は、スタックに直接配置されます。 smallObjなどの参照型変数は、固定占有サイズ(4ビットダブルワードDWORD)でスタックに格納され、通常のガベージコレクタヒープに配置されたオブジェクトのインスタンスのアドレスを含みます。



従来のC ++では、これはオブジェクトへのポインターです。 プログラミングの制御された世界では、これは参照またはオブジェクト参照です。 ただし、オブジェクトのインスタンスのアドレスは含まれています。 オブジェクトへのリンクで指定されたアドレスにあるデータ構造を表すObjectInstanceという用語を使用します。





図5. SimpleProgramスタックとヒープ



通常のガベージコレクタヒープ上のsmallObjオブジェクトのインスタンスには、サイズが85,000バイトの_largeObjを指すByte []が含まれます(図は、占有領域の実際のサイズである85016バイトを示しています)。 CLRは、小さなオブジェクトとは異なり、85,000バイト以上のオブジェクトを異なる方法で処理します。 ラージオブジェクトはラージオブジェクトヒープ(LOH)に配置され、スモールオブジェクトは通常のガベージコレクタヒープに作成され、オブジェクトの配置とガベージコレクションが最適化されます。 LOHは圧縮されず、通常のヒープはガベージコレクションごとに圧縮されます。 さらに、LOHはガベージコレクションが完了したときにのみクリーンアップされます。



smallObjインスタンスには、対応するタイプのメソッドのテーブル(MethodTable)を指すタイプ記述子が含まれています。 宣言されたものごとに1つのメソッドテーブルがあり、同じ型のオブジェクトのすべてのインスタンスは同じメソッドテーブルを指します。 また、記述子には、タイプタイプ(インターフェース、抽象クラス、具象クラス、COMラッパー、プロキシ)、実装されたインターフェースの数、メソッドを配布するためのインターフェースマップ、メソッドテーブルのスロット数、および実装を示すスロットのテーブルに関する情報が含まれます。



1つの重要なデータ構造はEEClassを指します。 CLRクラスローダーは、メソッドテーブルが作成される前にメタデータからEEClassを作成します。 図4では、SmallClassメソッドテーブルはそのEEClassを指しています。 これらの構造は、モジュールとアセンブリを示します。 メソッドとEEClassのテーブルは通常、ドメイン固有のヒープローダーにあります。 バイト[]は特殊なケースです。 メソッドテーブルとEEClassは、共有ドメインローダーのヒープ内にあります。 ローダーヒープは特定のドメイン(ドメイン固有)に属し、前述のデータ構造は、ロードされると、ドメインがアンロードされるまでどこにも移動しません。 また、既定のドメインはアンロードできないため、CLRが停止するまでコードが存在します。



オブジェクトインスタンス



気づいたように、値型のすべてのインスタンスは、ストリームスタックに埋め込まれるか、ガベージコレクタヒープに埋め込まれます。 すべての参照タイプは、ガベージコレクターのヒープまたはラージオブジェクトのヒープ(LOH)で作成されます。 図6は、オブジェクトのインスタンスの典型的なレイアウトを示しています。 オブジェクトは、外部相互作用およびP / Invokeスクリプトの状況で、レジスター(これはメソッドの実行中にメソッド引数を指定できます)またはオブジェクトのファイナライザーキューから、記述子テーブルのスタックで作成されたローカル変数によって参照できますファイナライザメソッド OBJECTREFは、オブジェクトのインスタンスの開始を示すのではなく、開始から4バイト(DWORD)のオフセットを示します。 DWORDはオブジェクトヘッダーと呼ばれ、SyncTableEntryテーブルにインデックス(1から始まるsynblk同期ブロックの番号)が含まれます。 インデックスを介して分散が行われるため、CLRはサイズの増加が必要なときにメモリ内のテーブルを移動できます。 SyncTableEntryは、オブジェクトへのソフトリンクを提供するため、同期ブロックの所有権をCLRにトレースできます。 ソフトリンクを使用すると、他のハードリンクが存在しなくなったときにガベージコレクタをクリーンアップできます。 SyncTableEntryは、有用な情報を含むSyncBlockへのポインターも格納しますが、オブジェクトのすべてのインスタンスには必要ありません。 この情報には、オブジェクトロック、そのハッシュコード、変換データ、およびドメインインデックス(AppDomainIndex)が含まれます。 オブジェクトのほとんどのインスタンスでは、同期ブロック(SyncBlock)に割り当てられるスペースはなく、同期ブロック番号はゼロになります。 以下に示すように、実行中のスレッドが式ロック(obj)またはobj.GetHashCodeでつまずくと、これは変わります。



 SmallClass obj = new SmallClass() // Do some work here lock(obj) { /* Do some synchronized work here */ } obj.GetHashCode();
      
      







図6.オブジェクトのインスタンスの表現



このコードでは、smallObjはSyncblkエントリテーブルの番号としてゼロ(syncblkなし)を使用します。 lockステートメントは、CLRに強制的にsyncblockレコードを作成させ、対応する番号をヘッダーに書き込みます。 C#のlockキーワードはMonitorクラスを使用してtry-catchブロックに展開されるため、同期のためにSyncBlockにMonitorオブジェクトが作成されます。 GetHashCode()メソッドを呼び出すと、HashcodeフィールドにSyncBlockのオブジェクトのハッシュコードが入力されます。



SyncBlockには、COMと組み合わせて使用​​される他のフィールドが含まれ、デリゲートをアンマネージコードにマーシャリングしますが、オブジェクトの一般的な使用には関係ありません。



タイプ(TypeHandle)のハンドラーは、オブジェクトのインスタンスのsyncblk番号に従います。 推論の連続性を維持するために、変数のインスタンスを明確にした後、タイプハンドラについて説明します。 インスタンスフィールドの変数リストは、タイプハンドラに従います。 デフォルトでは、インスタンスフィールドは、メモリ使用量が効率的で、アライメントのギャップが最小になるように配置されます。 図7のコードには、さまざまなサイズの一連のインスタンス変数を含む単純なSimpleClassクラスが含まれています。



図7インスタンス変数を使用したSimpleClass
 class SimpleClass { private byte b1 = 1; // 1 byte private byte b2 = 2; // 1 byte private byte b3 = 3; // 1 byte private byte b4 = 4; // 1 byte private char c1 = 'A'; // 2 bytes private char c2 = 'B'; // 2 bytes private short s1 = 11; // 2 bytes private short s2 = 12; // 2 bytes private int i1 = 21; // 4 bytes private long l1 = 31; // 8 bytes private string str = "MyString"; // 4 bytes (only OBJECTREF) //Total instance variable size = 28 bytes static void Main() { SimpleClass simpleObj = new SimpleClass(); return; } }
      
      







図8に、Visual Studioデバッガーのメモリウィンドウに表示されるSimpleClassオブジェクトのインスタンスの例を示します。 図7のreturnステートメントにブレークポイントを設定し、ECXレジスタに含まれるsimpleObjアドレスを使用して、オブジェクトのインスタンスをメモリビューに表示しました。 最初の4バイトブロックはsyncblk番号です。 同期を必要とするコードでインスタンスを使用しない(およびHashCodeメソッドを使用しない)ため、このフィールドは0に設定されます。オブジェクトへの参照はスタック変数に格納され、オフセット4にある4バイトを示します。バイト変数b1、b2、b3とb4は互いに並んでいます。 バイト変数b1、b2、b3、およびb4はすべて、横に並んで配置されています。 タイプs1とs2の両方の変数も並んで配置されます。 文字列変数strは、ガベージコレクタヒープにある文字列の現在のインスタンスを指す4バイトのODJECTREFです。 文字列は特別な型であり、同じテキストを含むすべてのインスタンスは、文字列のグローバルテーブル内の同じインスタンスを指します。これは、アセンブリの読み込みプロセス中に行われます。 このプロセスは文字列インターンと呼ばれ、メモリ使用量を最適化するように設計されています。 .NET Framework 1.1で前述したように、アセンブリはインターンプロセスをオフにすることはできません;おそらくCLRランタイムの将来のバージョンでは、これが提供される予定です。





図8.メモリ内のオブジェクトのインスタンスを表示するデバッグウィンドウ



したがって、ソースコード内の変数メンバーの字句シーケンスは、デフォルトではメモリでサポートされていません。 字句シーケンスがメモリに転送される外部の相互作用のシナリオでは、StructLayoutAttribute属性を使用できます。これは、LayoutKind列挙型の値を引数として受け取ります。 LayoutKind.Sequentialは、マーシャリングされたデータの字句一貫性を提供します。 .NET Frameworkでは、これは管理レイアウトに影響しません(.NET Framework 2.0では、属性を使用すると効果があります)。 追加のバイアスとフィールドのシーケンスを明示的に制御する必要がある外部相互作用のシナリオでは、フィールドレベルでLayoutKind.ExplicitをFieldOffset属性と組み合わせて使用​​できます。 メモリの直接の内容を確認したら、SOSデバッガーを使用してオブジェクトインスタンスの内容を確認します。 便利なコマンドの1つにDumpHeapがあります。これにより、ヒープの内容全体と特定のタイプのすべてのインスタンスを表示できます。 レジスタを使用する代わりに、DumpHeapは作成したオブジェクトのアドレスを表示できます。



 !DumpHeap -type SimpleClass Loaded Son of Strike data table version 5 from "C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/mscorwks.dll" Address MT Size 00a8197c 00955124 36 Last good object: 00a819a0 total 1 objects Statistics: MT Count TotalSize Class Name 955124 1 36 SimpleClass
      
      





オブジェクトの合計サイズは36バイトです。 文字列の大きさは関係ありません。SimpleClassのインスタンスにはDWORD OBJECTREFのみが含まれます。 SimpleClassインスタンス変数は、28バイトしか占有しません。 残りの8バイトには、TypeHandleタイプハンドラー(4バイト)とsyncblk同期ブロック番号(4バイト)が含まれます。 simpleObjインスタンスのアドレスを受け取ったら、次に示すように、DumpObjコマンドを使用してこのインスタンスの内容をダンプしましょう。



 !DumpObj 0x00a8197c Name: SimpleClass MethodTable 0x00955124 EEClass 0x02ca33b0 Size 36(0x24) bytes FieldDesc*: 00955064 MT Field Offset Type Attr Value Name 00955124 400000a 4 System.Int64 instance 31 l1 00955124 400000b c CLASS instance 00a819a0 str << some fields omitted from the display for brevity >> 00955124 4000003 1e System.Byte instance 3 b3 00955124 4000004 1f System.Byte instance 4 b4
      
      





前述のように、C#コンパイラによってクラス用に生成されるデフォルトのレイアウトレイアウトはLayoutType.Autoです(構造にはLayoutType.Sequentialが使用されます)。 このようにして、クラスローダーはオフセットを最小化するためにインスタンスフィールドを並べ替えます。 ObjSizeを使用して、インスタンスstrが占めるスペースを含むグラフを取得できます。 出力は次のとおりです。



!ObjSize 0x00a8197c

sizeof(00a8197c)= 72(0x48)バイト(SimpleClass)



ストライクの息子

この記事のCLRデータ構造の内容を表示するために使用されるSOSデバッグ拡張機能。 これは.NET Frameworkインストールパッケージの一部であり、%windir%\ Microsoft.NET \ Framework \ v1.1.4322パスにあります。 SOSをプロセスに読み込む前に、Visual Studio .NETのプロジェクトプロパティで管理デバッグを有効にします。 SOS.dllが置かれているディレクトリをPATH環境変数に追加します。 ブレークポイントで停止するときにSOSをロードするには、[デバッグ] | [開く] Windows | すぐに イミディエイトウィンドウで、.load sos.dllを実行します。 Use!Helpを使用して、デバッガーコマンドのリストを取得します。 SOSの詳細については、msdn Bugslayer列のドキュメントを参照してください。



オブジェクトグラフの全体サイズ(72バイト)からSimpleClassインスタンスのサイズ(36バイト)を引くと、strサイズ(36バイト)が得られます。 strインスタンスをダンプしてこれを確認しましょう。 コマンドの出力は次のとおりです。



 !DumpObj 0x00a819a0 Name: System.String MethodTable 0x009742d8 EEClass 0x02c4c6c4 Size 36(0x24) bytes
      
      





str文字列インスタンスのサイズ(36バイト)をSimpleClassインスタンスのサイズ(36バイト)に追加すると、ObjSizeコマンドの出力に対応する合計サイズが72バイトになります。 ObjSizeには、syncblkインフラストラクチャが占有するメモリは含まれないことに注意してください。 また、.NET Framework 1.1では、CLRはGDIオブジェクト、COMオブジェクト、ファイルハンドラーなどのアンマネージリソースによって占有されているメモリを認識しません。 したがって、このコマンドには反映されません。

タイプ(TypeHandle)のハンドラー、メソッドテーブル(MethodTable)へのポインターは、syncblk番号の直後にあります。 オブジェクトのインスタンスを作成する前に、CLRは読み込まれた型を調べ、型が見つからない場合は型情報を読み込み、メソッドテーブルのアドレスを取得し、オブジェクトのインスタンスを作成し、オブジェクトインスタンスのTypeHandleに値を書き込みます。 JITコンパイラーによってコンパイルされたコードは、TypeHandle型ハンドラーを使用して、メソッドを配布するためのメソッドのMethodTableテーブルを見つけます。 JITコンパイラーによってコンパイルされたコードは、タイプハンドラー(TypeHandle)を使用してメソッドテーブル(MethodTable)を配置し、メソッド呼び出しを分散します。 CLRは、MethodTableメソッドテーブルから読み込まれた型を見つける必要がある場合、型ハンドラー(TypeHandle)を使用します。



MethodTableテーブル



各クラスとインターフェイスは、アプリケーションドメインに読み込まれると、MethodTableデータ構造によってメモリ内に表されます。 これは、オブジェクトの最初のインスタンスを作成する前にクラスをロードした結果です。 ObjectInstanceオブジェクトのインスタンスは状態を保存しますが、MethodTableは動作情報を保存します。 MethodTableは、EEClassを使用して言語コンパイラによって生成されたメモリ内メタデータ構造にオブジェクトインスタンスを関連付けます。 MethodTableメソッドテーブルの情報とそれに関連付けられたデータ構造には、マネージコードからSystem.Typeを介してアクセスできます。 ObjectInstanceに含まれるTypeHandle型のハンドラーは、メソッドテーブルの先頭からのオフセットを示します。 このオフセットはデフォルトでは12バイトで、ガベージコレクターの情報が含まれていますが、ここでは説明しません。



図9は、メソッドテーブルの典型的な表現を示しています。 型ハンドラのいくつかの重要なフィールドを示しますが、より完全なリストについては図を使用してください。 ランタイムメモリプロファイルと直接的な相関関係があるため、ベースインスタンスサイズから始めましょう。





図9メソッドテーブルの表示



ベースインスタンスサイズ



基本インスタンスサイズは、コード内のフィールドの宣言に基づいてクラスローダーによって計算されたオブジェクトのサイズです。 前に説明したように、現在のガベージコレクターの実装には、少なくとも12バイトのオブジェクトインスタンスサイズが必要です。 クラスに宣言されたインスタンスフィールドがない場合、4バイトの冗長性が生じます。



残りの8バイトは、ヘッダー(オブジェクトヘッダー)(syncblk同期ブロック番号を含む場合があります)およびタイプハンドラー(TypeHandle)によって占有されます。 繰り返しますが、オブジェクトのサイズはStructLayoutAttributeの影響を受ける場合があります。



( Visual Studio .NET 2003 ) MyClass 3 (MyClass ) SOS . 9, 4- 12 (0x0000000C) . DumpHeap SOS:



 !DumpHeap -type MyClass Address MT Size 00a819ac 009552a0 12 total 1 objects Statistics: MT Count TotalSize Class Name 9552a0 1 12 MyClass
      
      







(MethodDesc), . : , , , . , . , , . vtable. , .MyClass , (.cctor) (.ctor). C# . . 10 MyClass. 10 Method2 IVMap, . 11 SOS MyClass.





10 MyClass



11 SOS MyClass
 !DumpMT -MD 0x9552a0 Entry MethodDesc Return Type Name 0097203b 00972040 String System.Object.ToString() 009720fb 00972100 Boolean System.Object.Equals(Object) 00972113 00972118 I4 System.Object.GetHashCode() 0097207b 00972080 Void System.Object.Finalize() 00955253 00955258 Void MyClass.Method1() 00955263 00955268 Void MyClass.Method2() 00955263 00955268 Void MyClass.Method2() 00955273 00955278 Void MyClass.Method3() 00955283 00955288 Void MyClass..cctor() 00955293 00955298 Void MyClass..ctor()
      
      







4 ToString, Equals, GetHashCode Finalize. System.Object. Method2 , . .cctor .ctor .





(MethodDesc) CLR. , , . , MethodDesc 3. MethodDesc (IL). MethodDesc PreJitStub, JIT . 12 . MethodDesc. 5- MethodDesc 8- , . 5 PreJitStub. 5- DumpMT ( MyClass 11) of SOS, MethodDesc 5 . JIT . 5 JIT x86.





12



12 PreJitStub. JIT Method2:



 !u 0x00955263 Unmanaged code 00955263 call 003C3538 ;call to the jitted Method2() 00955268 add eax,68040000h ;ignore this and the rest ;as !u thinks it as code
      
      





:



 !u 0x00955263 Unmanaged code 00955263 jmp 02C633E8 ;call to the jitted Method2() 00955268 add eax,0E8040000h ;ignore this and the rest ;as !u thinks it as code
      
      





5 ; Method2 . »!u" , 5- .



CodeOrIL JIT (RVA) (IL). . CLR JIT – . MethodDesc DumpMT JIT :



 !DumpMD 0x00955268 Method Name : [DEFAULT] [hasThis] Void MyClass.Method2() MethodTable 9552a0 Module: 164008 mdToken: 06000006 Flags : 400 IL RVA : 00002068
      
      





, MethodDesc :



 !DumpMD 0x00955268 Method Name : [DEFAULT] [hasThis] Void MyClass.Method2() MethodTable 9552a0 Module: 164008 mdToken: 06000006 Flags : 400 Method VA : 02c633e8
      
      





, , , COM .



: . , . , .



IVMap



12 , IVMap. 9, IVMap , . IVMap. MyInterface1 , IVMap. (MethodTable) MyClass, 9. . IVMap . . , IVMap . 28 (Interface Map ) InterfaceInfo . , MyClass. 4 InterfaceInfo (TypeHandle) MyInterface1 ( 9 10). (2 ) ( 0 1 ). , . MyInterface1 4, 5 6 . MyInterface2, 6, 7 8 . , , . MyClass MyInterface1.Method2 MyInterface2.Method2 .



IVMap, MethodDesc . , .NET Framework fastcall. ECX EDX, . «this» , ECX, «mov ecx, esi»:



 mi1.Method1(); mov ecx,edi ;move "this" pointer into ecx mov eax,dword ptr [ecx] ;move "TypeHandle" into eax mov eax,dword ptr [eax+0Ch] ;move IVMap address into eax at offset 12 mov eax,dword ptr [eax+30h] ;move the ifc impl start slot into eax call dword ptr [eax] ;call Method1 mc.Method1(); mov ecx,esi ;move "this" pointer into ecx cmp dword ptr [ecx],ecx ;compare and set flags call dword ptr ds:[009552D8h];directly call Method1
      
      





MyClass . JIT . IVMap , . IVMap, . , . 2, «mi1 = mc;» OBJECTREF mc mi1.





. MyClass.Method3 3:



 mc.Method3(); Mov ecx,esi ;move "this" pointer into ecx Mov eax,dword ptr [ecx] ;acquire the MethodTable address Call dword ptr [eax+44h] ;dispatch to the method at offset 0x44
      
      





, (). , . , . 8 ( 10) DumpMT.





. . , , OBJECTREF . OBJECTREF . , OBJECTREF , . 9, str, OBJECTREF , MyString .



EEClass



EEClass CLR . , EEClass ( ) . , EEClass. ( , ) JIT EEClass, ( vtable ) .



EEClass. , , , . EEClass . CLR EEClass , , . EEClass , . EEClass , . EEClass . EEClass : ParentClass, SiblingChain ChildrenChain. 13 EEClass MyClass 4.



13 . , . EEClass . EEClass , . . EEClass , , .





13 EEClass



, 13 MyClass ( 3). EEClass SOS. 3 mc.Method1. EEClass MyClass Name2EE:



 !Name2EE C:/Working/test/ClrInternals/Sample1.exe MyClass MethodTable: 009552a0 EEClass: 02ca3508 Name: MyClass
      
      





Name2EE , DumpDomain. EEClass, EEClass:



 !DumpClass 02ca3508 Class Name : MyClass, mdToken : 02000004, Parent Class : 02c4c3e4 ClassLoader : 00163ad8, Method Table : 009552a0, Vtable Slots : 8 Total Method Slots : a, NumInstanceFields: 0, NumStaticFields: 2,FieldDesc*: 00955224 MT Field Offset Type Attr Value Name 009552a0 4000001 2c CLASS static 00a8198c str 009552a0 4000002 30 System.UInt32 static aaaaaaaa ui
      
      





13 DumpClass . (mdToken) MyClass PE , System.Object. ( 13) , Program.



MyClass vtable ( ). , Method1 Method2 , . .cctor .ctor , 10(0xA) . . MyClass . .



おわりに



CLR. , , , . CLR .NET Framework. , , .



All Articles