久々の更新です。ようやく .NET デバッグが実戦でも通用するレベルになってきたので、初歩を紹介します。もちろん .NET デバッグといっても Visual Studio を使うのではなく、ntsd やら windbg といった Windows デバッガーを使います。Windows デバッガーの利点としては、稼働環境へファイルをコピーするだけでいい、リモート デバッグが可能、Visual Studio より細かいことができる、などが挙げられます。コンピューターの動作を理解するのにも役立ちますし、慣れてくると Visual Studio より速くデバッグできるようになります。それと、デバッガーの黒い画面を開いて仕事をしていると、周りから見ても 「仕事をしている感」 が醸し出されて便利です。(なんだそれは

ただし、まだ .NET Framework の動きはほとんど理解しきれていないので、細かい説明は端折ります。そのうち覚えます。

.NET のデバッグを行うためには、SOS と呼ばれるデバッガー エクステンション DLL が必要になります。ただし、これは .NET Framework に含まれているので、別途ダウンロードする必要はありません。

%windir%\Microsoft.NET\Framework\<.NET バージョン>\SOS.dll
%windir%\Microsoft.NET\Framework64\<.NET バージョン>\SOS.dll

SOS とは、”Son Of Strike” の略です。じゃあ Strike って何ぞ、という話ですが、これは以下のブログにそのエピソードが詳細に書かれています。もともと CLR 開発チームが “Lightning” という名前で作っていたものを、デバッガー エクステンションにしたときに “Strike” という名前に変えて、そこから一部のコードを取り除いたものだから “Son Of Strike” だそうです。

この記事には、そんな小話だけでなく SOS に関する非常に詳細な説明が書かれています。

SOS Debugging of the CLR, Part 1 - Jason Zander’s blog - Site Home - MSDN Blogs
http://blogs.msdn.com/b/jasonz/archive/2003/10/21/53581.aspx

MSDN だとこんなページもあります。

SOS.dll (SOS Debugging Extension)
http://msdn.microsoft.com/en-us/library/bb190764.aspx

このブログでは、細かいことは抜きにして早速デバッグしてみましょう。シンボルの設定は必ず行って下さい。順番が逆ですが、デバッグ環境の作り方をそのうち記事にするかもしれません。

Use the Microsoft Symbol Server to obtain debug symbol files
http://support.microsoft.com/kb/311503/en

今回の検証環境はこんな感じです。
現時点で最新の環境を使っていますが、Windows 7 でも XP でも同じことができるはずです。

  • OS: Windows Server 2012
  • CLR: 4.0.30319.18010 (.NET Framework 4.5)
  • IDE: Visual Studio 2012
  • Debugger: 6.2.9200.16384 (Windows Kit 8.0)

まずは、適当なプログラムを書きます。C# です、お決まりですね。もちろん F# でもいいです。

using System; 
using System.IO;

namespace cssandbox { 
    class Program { 
        static void Main(string[] args) { 
            var Prog = new Program(); 
            Prog.Print(Console.Out); 
        }

        string mMessage1; 
        string mMessage2;

        Program() { 
            mMessage1 = "Hello!"; 
            Sub(); 
        }

        Program(string s) { 
            mMessage1 = s; 
            Sub(); 
        }

        void Sub() { 
            mMessage2 = DateTime.Now.ToString(); 
        }

        void Print(TextWriter Writer) { 
            Writer.WriteLine(mMessage1); 
            Writer.WriteLine(mMessage2); 
        }

    } 
} 

これを Debug 構成でビルドして、デバッガーから起動します。私は ntsd 派なのでこんな感じです。ntdll!LdrpDoDebuggerBreak で止まるはずで、これはネイティブ コードのデバッグと同じです。というか、まだこのタイミングでは CLR がロードされていないので、マネージド コードは存在しません。

Microsoft (R) Windows Debugger Version 6.2.9200.16384 X86 
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: cssandbox.exe 
Symbol search path is: srv*d:\websymbols*http://msdl.microsoft.com/download/symbols 
Executable search path is: 
ModLoad: 005e0000 005e8000   cssandbox.exe 
ModLoad: 77820000 77977000   ntdll.dll 
ModLoad: 6f2f0000 6f33a000   C:\Windows\SysWOW64\MSCOREE.DLL 
ModLoad: 77630000 77760000   C:\Windows\SysWOW64\KERNEL32.dll 
ModLoad: 76f20000 76fc6000   C:\Windows\SysWOW64\KERNELBASE.dll 
(9a4.b10): Break instruction exception - code 80000003 (first chance) 
eax=00000000 ebx=00000003 ecx=be050000 edx=00000000 esi=00000000 edi=00000000 
eip=778c054d esp=0076f864 ebp=0076f890 iopl=0         nv up ei pl zr na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
ntdll!LdrpDoDebuggerBreak+0x2b: 
778c054d cc              int     3 
0:000>

ここで重要なのが、CLR が動作するプラットフォームと、利用するデバッガーのプラットフォームを一致させておくことです。OS が 64 bit だったとしても、デバッグするアプリが 32bit で動作する場合は、32bit のデバッガーを使わないといけません。SOS.dll はデバッグ対象の CLR とバージョンとプラットフォームが一致していないと動かないのですが、当然 64bit のデバッガー プロセスから 32bit の SOS.dll をロードできないため、このような制限が生まれます。デバッガーを起動する前にデバッグ対象の動作プラットフォームを調べておきましょう。

SOS のロード

SOS のロードで一番単純な方法は、以下のように SOS.dll のパスを直接指定する方法です。

0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll

32bit デバッガーから 64bit の SOS を読もうとすると、以下のように怒られます。

0:000> .load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll 
The call to LoadLibrary(C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll) failed, Win32 error 0n193 
    "%1 is not a valid Win32 application." 
Please check your debugger configuration and/or network access.

以上のようにプラットフォームの違いは判別されますが、.NET Framework バージョンの違いは判別されないので、以下のように .NET 2.0 の SOS はロードできてしまいます。これだと後々の SOS のコマンドが正しく動きません。

0:000> .load C:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll

いちいち .NET Framework のバージョンを調べるのが面倒くさい、という人のために .loadby というコマンドが存在します。こんな感じに使います。

0:000> sxe ld:clr 
0:000> g 
ModLoad: 6d500000 6db92000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll 
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7e60d000 
eip=77860fe8 esp=0076f4e4 ebp=0076f53c iopl=0         nv up ei pl nz na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206 
ntdll!NtMapViewOfSection+0xc: 
77860fe8 c22800          ret     28h 
0:000> .loadby sos clr

.loadby を使うと、指定したモジュールと同じところにある拡張 DLL をロードさせることができます。したがって、CLR.dll がロードされた後のタイミングで、clr.dll と同じところの SOS をロードすることで、適切なバージョンの SOS をロードすることができます。

デバッグするプログラムによっては clr で .loadby できず、代わりに mscoreei や mscorwks を使うことがあります。実現したいことは、CLR と同じ SOS をロードするだけなので、困ったら CLR のバージョンを調べて絶対パス指定で .load すれば OK です。

ブレーク ポイント

デバッグは、ブレークポイントを設定するところから始まります。そんなわけで、前述のサンプル プログラムの Main 関数で止めてみましょう。ネイティブ コードと違って、x コマンドは使えません。

単純な方法は、SOS の !name2ee コマンドを使う方法です。一気にやるとこんな感じです。

0:000> sxe ld:clrjit 
0:000> g 
(9a4.b10): Unknown exception - code 04242420 (first chance) 
ModLoad: 6ee60000 6eece000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll 
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7e60d000 
eip=77860fe8 esp=0076e5dc ebp=0076e634 iopl=0         nv up ei pl nz na po nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202 
ntdll!NtMapViewOfSection+0xc: 
77860fe8 c22800          ret     28h 
0:000> !name2ee cssandbox!cssandbox.Program.Main 
Module:      008e2e94 
Assembly:    cssandbox.exe 
Token:       06000001 
MethodDesc:  008e37ac 
Name:        cssandbox.Program.Main(System.String[]) 
Not JITTED yet. Use !bpmd -md 008e37ac to break on run. 
0:000> !bpmd -md 008e37ac 
MethodDesc = 008e37ac 
Adding pending breakpoints... 
0:000> g 
(9a4.b10): CLR notification exception - code e0444143 (first chance) 
JITTED cssandbox!cssandbox.Program.Main(System.String[]) 
Setting breakpoint: bp 00AA0077 [cssandbox.Program.Main(System.String[])] 
Breakpoint 0 hit 
eax=00000000 ebx=0076f31c ecx=024a22cc edx=00000000 esi=00000000 edi=0076f290 
eip=00aa0077 esp=0076f264 ebp=0076f278 iopl=0         nv up ei pl zr na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
00aa0077 90              nop 
0:000>  

ブレークポイントを設定するのに使いたいコマンドは !bpmd です。bp コマンドは、メモリにロードされているコードのアドレスを直接指定しますが、上記の例では、!name2ee コマンドを使って取得した Method Descriptor という値を使ってブレーク ポイントを設定しています。

コマンドの出力にもありますが、.NET の大きな特徴として、実行時 (JIT) コンパイルが行われることが挙げられます。当然、まだコンパイルされていないメソッドに対して bp コマンドは使えません。そこで、Method Descriptor を間接的に使ってブレークポイントを設定するわけです。Method Descriptor や EEClass といった CLR の内部構造については、以下の記事などを参考にして下さい。まだよく知らないのですわ・・・スミマセン。

JIT and Run: .NET Framework の内部: CLR がランタイム オブジェクトを作成するしくみ – MSDN Magazine, 2005 年 5 月
http://msdn.microsoft.com/ja-jp/magazine/ee216336.aspx

アプリケーションを起動して clr.dll がロード直後のタイミングでは、CLR の内部構造がほとんど何もできていないので、!name2ee すら実行することができず、以下のようなエラーが出ます。

0:000> !name2ee cssandbox!cssandbox.Program.Main 
Failed to obtain AppDomain data. 
Failed to request module list.

そこで今回は、clrjit.dll がロードされるタイミングを sxe で止めて、そのときに !name2ee を実行しました。

!bpmd した後の出力結果を見ると、JIT されたタイミングで bp コマンドが実行されているのが分かります(紫字部分)。JIT されてしまえば、ネイティブ コードと同じように扱うことができます。ただし、コンパイルされたコードはネイティブのものとは微妙に印象が異なるのが面白いところです。

ブレークポイントで止まっている状態で、以下のコマンドを実行してみます。

0:000> bl 
0 e 00aa0077     0001 (0001)  0:**** 
0:000> !name2ee cssandbox!cssandbox.Program.Main 
Module:      008e2e94 
Assembly:    cssandbox.exe 
Token:       06000001 
MethodDesc:  008e37ac 
Name:        cssandbox.Program.Main(System.String[]) 
JITTED Code Address: 00aa0050 
0:000> r 
eax=00000000 ebx=0076f31c ecx=024a22cc edx=00000000 esi=00000000 edi=0076f290 
eip=00aa0077 esp=0076f264 ebp=0076f278 iopl=0         nv up ei pl zr na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
00aa0077 90              nop 
0:000> k 
ChildEBP RetAddr 
WARNING: Frame IP not in any known module. Following frames may be wrong. 
0076f278 6d502652 0xaa0077 
0076f284 6d51264f clr!CallDescrWorkerInternal+0x34 
0076f2d8 6d512e95 clr!CallDescrWorkerWithHandler+0x6b 
0076f350 6d5c74ec clr!MethodDescCallSite::CallTargetWorker+0x152 
0076f47c 6d5c7610 clr!RunMain+0x1aa 
0076f6f0 6d651dc4 clr!Assembly::ExecuteMainMethod+0x124 
0076fbf4 6d651e67 clr!SystemDomain::ExecuteMainMethod+0x614 
0076fc50 6d651f7a clr!ExecuteEXE+0x4c 
0076fc90 6d65416a clr!_CorExeMainInternal+0xdc 
0076fccc 6f27f5a3 clr!_CorExeMain+0x4d 
0076fd04 6f2f7efd mscoreei!_CorExeMain+0x10a 
0076fd1c 6f2f4de3 MSCOREE!ShellShim__CorExeMain+0x7d 
0076fd24 77658543 MSCOREE!_CorExeMain_Exported+0x8 
0076fd30 7787ac69 KERNEL32!BaseThreadInitThunk+0xe 
0076fd74 7787ac3c ntdll!__RtlUserThreadStart+0x72 
0076fd8c 00000000 ntdll!_RtlUserThreadStart+0x1b 

まず、!bpmd によって自動的に bp コマンドが実行されたので、bl で確認できます。!name2ee コマンドを実行すると、JIT されたコードが 00aa0050 にロードされていることが分かります。bp された場所と若干ずれていますね。本当は 00aa0077 ではなく 00aa0050 で止まってほしいところです。止めたいところを !name2ee で調べて、JIT されていれば、bp コマンドを手動で打つこともできます。

また、r や k といったいつものコマンドも使うことができます。ただし、0xaa0077 のアドレスなどはシンボル名で解決されていません。スタックを見ると、clr.dll から cssandbox.exe が呼ばれていることが分かります。ステップ実行もできます。

変数を見る

目的の所で止めたら、変数の値を見たくなります。頑張れば dd コマンドを使えないこともないですが、.NET オブジェクトについては、SOS のエクステンションに頼ることになります。

cssandbox.exe に適当なパラメーターを指定して起動し、args の値を見る場合の例を示します。

Microsoft (R) Windows Debugger Version 6.2.9200.16384 X86 
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: cssandbox.exe ABCD 漢字 
Symbol search path is: srv*d:\websymbols*http://msdl.microsoft.com/download/symbols 
Executable search path is: 
ModLoad: 00af0000 00af8000   cssandbox.exe 
ModLoad: 77820000 77977000   ntdll.dll 
ModLoad: 6f2f0000 6f33a000   C:\Windows\SysWOW64\MSCOREE.DLL 
ModLoad: 77630000 77760000   C:\Windows\SysWOW64\KERNEL32.dll 
ModLoad: 76f20000 76fc6000   C:\Windows\SysWOW64\KERNELBASE.dll 
(11a4.cf0): Break instruction exception - code 80000003 (first chance) 
eax=00000000 ebx=00000003 ecx=18f00000 edx=00000000 esi=00000000 edi=00000000 
eip=778c054d esp=00c7f76c ebp=00c7f798 iopl=0         nv up ei pl zr na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
ntdll!LdrpDoDebuggerBreak+0x2b: 
778c054d cc              int     3 
0:000> sxe ld:clrjit 
0:000> g 
(11a4.cf0): Unknown exception - code 04242420 (first chance) 
ModLoad: 6ee60000 6eece000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll 
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7ef7d000 
eip=77860fe8 esp=00c7e4ec ebp=00c7e544 iopl=0         nv up ei pl nz na po nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202 
ntdll!NtMapViewOfSection+0xc: 
77860fe8 c22800          ret     28h 
0:000> .loadby sos clr 
0:000> !bpmd cssandbox.exe cssandbox.Program.Main 
Found 1 methods in module 00d72e94... 
MethodDesc = 00d737ac 
Adding pending breakpoints... 
0:000> g 
(11a4.cf0): CLR notification exception - code e0444143 (first chance) 
JITTED cssandbox!cssandbox.Program.Main(System.String[]) 
Setting breakpoint: bp 01220077 [cssandbox.Program.Main(System.String[])] 
Breakpoint 0 hit 
eax=00000000 ebx=00c7f22c ecx=02d122cc edx=00000000 esi=00000000 edi=00c7f1a0 
eip=01220077 esp=00c7f174 ebp=00c7f188 iopl=0         nv up ei pl zr na pe nc 
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
01220077 90              nop 
0:000> !dso 
OS Thread Id: 0xcf0 (0) 
ESP/REG  Object   Name 
ecx      02d122cc System.Object[]    (System.String[]) 
00C7F184 02d122cc System.Object[]    (System.String[]) 
00C7F200 02d122cc System.Object[]    (System.String[]) 
00C7F35C 02d122cc System.Object[]    (System.String[]) 
00C7F394 02d122cc System.Object[]    (System.String[]) 
0:000> !da 02d122cc 
Name:        System.String[] 
MethodTable: 6c8eae88 
EEClass:     6c5abb70 
Size:        24(0x18) bytes 
Array:       Rank 1, Number of elements 2, Type CLASS 
Element Methodtable: 6c93afb0 
[0] 02d122e4 
[1] 02d122fc 
0:000> !do 02d122e4 
Name:        System.String 
MethodTable: 6c93afb0 
EEClass:     6c54486c 
Size:        22(0x16) bytes 
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorl 
ib.dll 
String:      ABCD 
Fields: 
      MT    Field   Offset                 Type VT     Attr    Value Name 
6c93c770  40000aa        4         System.Int32  1 instance        4 m_stringLength 
6c93b9a8  40000ab        8          System.Char  1 instance       41 m_firstChar 
6c93afb0  40000ac        c        System.String  0   shared   static Empty 
    >> Domain:Value  010234f0:NotInit  << 
0:000> !do -nofields 02d122fc 
Name:        System.String 
MethodTable: 6c93afb0 
EEClass:     6c54486c 
Size:        18(0x12) bytes 
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorl 
ib.dll 
String:      漢字 
0:000> 

Main 関数で止めた後、!dso と !do コマンドを呼び出しています。!dso は、スタックに積まれているオブジェクトの一覧を表示するもので、!do はオブジェクトを表示するコマンドです。.オブジェクトが型情報を持っているので、dt と違って型を明示する必要がなくて便利です。dynamic 型でやるとどうなるんですかね。配列の場合は !do ではなく !da を使います。

!dso の結果を見ると args の値が ecx レジスターに入っています。実際、.NET のメソッドは fastcall で呼ばれるようです。

アセンブラを見る

!bpmd を使えばメソッドの先頭で止めることができますが、メソッドの途中で止める場合には、JIT されたコードのアセンブラを見ないといけません。ネイティブと同じように u や uf コマンドを使うことができます。例えば Main メソッドのアセンブラを uf で見るとこんな感じです。

0:000> !name2ee cssandbox cssandbox.Program.Main 
Module:      00d72e94 
Assembly:    cssandbox.exe 
Token:       06000001 
MethodDesc:  00d737ac 
Name:        cssandbox.Program.Main(System.String[]) 
JITTED Code Address: 01220050 
0:000> uf 01220050 
01220050 55              push    ebp 
01220051 8bec            mov     ebp,esp 
01220053 83ec14          sub     esp,14h 
01220056 33c0            xor     eax,eax 
01220058 8945f4          mov     dword ptr [ebp-0Ch],eax 
0122005b 8945f0          mov     dword ptr [ebp-10h],eax 
0122005e 8945ec          mov     dword ptr [ebp-14h],eax 
01220061 894dfc          mov     dword ptr [ebp-4],ecx 
01220064 833d6031d70000  cmp     dword ptr ds:[0D73160h],0 
0122006b 7405            je      01220072

0122006d e84270576c      call    clr!JIT_DbgIsJustMyCode (6d7970b4)

01220072 33d2            xor     edx,edx 
01220074 8955f8          mov     dword ptr [ebp-8],edx 
01220077 90              nop 
01220078 b9f037d700      mov     ecx,0D737F0h 
0122007d e87e20b4ff      call    00d62100 
01220082 8945f4          mov     dword ptr [ebp-0Ch],eax 
01220085 8b4df4          mov     ecx,dword ptr [ebp-0Ch] 
01220088 ff151038d700    call    dword ptr ds:[0D73810h] 
0122008e 8b45f4          mov     eax,dword ptr [ebp-0Ch] 
01220091 8945f8          mov     dword ptr [ebp-8],eax 
01220094 8b45f8          mov     eax,dword ptr [ebp-8] 
01220097 8945f0          mov     dword ptr [ebp-10h],eax 
0122009a e875d3686b      call    mscorlib_ni+0x36d414 (6c8ad414) 
0122009f 8945ec          mov     dword ptr [ebp-14h],eax 
012200a2 8b4df0          mov     ecx,dword ptr [ebp-10h] 
012200a5 8b55ec          mov     edx,dword ptr [ebp-14h] 
012200a8 3909            cmp     dword ptr [ecx],ecx 
012200aa ff15e037d700    call    dword ptr ds:[0D737E0h] 
012200b0 90              nop 
012200b1 90              nop 
012200b2 8be5            mov     esp,ebp 
012200b4 5d              pop     ebp 
012200b5 c3              ret 
0:000> 

いやー、硬派ですね。というのも、ほとんどの call 命令のオペランドが生アドレスだからでしょうか。

実は、アセンブラを見る時も SOS に頼ることができます。それが !U です。また、プライベート シンボルがある場合には、.lines を使うことでネイティブ コードと同じように行番号を表示させることができます。こんな感じです。

0:000> .lines 
Line number information will be loaded 
0:000> !U . 
Normal JIT generated code 
cssandbox.Program.Main(System.String[]) 
Begin 01220050, size 66

d:\VSDev\Projects\cssandbox\Program.cs @ 11: 
01220050 55              push    ebp 
01220051 8bec            mov     ebp,esp 
01220053 83ec14          sub     esp,14h 
01220056 33c0            xor     eax,eax 
01220058 8945f4          mov     dword ptr [ebp-0Ch],eax 
0122005b 8945f0          mov     dword ptr [ebp-10h],eax 
0122005e 8945ec          mov     dword ptr [ebp-14h],eax 
01220061 894dfc          mov     dword ptr [ebp-4],ecx 
01220064 833d6031d70000  cmp     dword ptr ds:[0D73160h],0 
0122006b 7405            je      01220072 
0122006d e84270576c      call    clr!JIT_DbgIsJustMyCode (6d7970b4) 
01220072 33d2            xor     edx,edx 
01220074 8955f8          mov     dword ptr [ebp-8],edx 
>>> 01220077 90              nop

d:\VSDev\Projects\cssandbox\Program.cs @ 12: 
01220078 b9f037d700      mov     ecx,0D737F0h (MT: cssandbox.Program) 
0122007d e87e20b4ff      call    00d62100 (JitHelp: CORINFO_HELP_NEWSFAST) 
01220082 8945f4          mov     dword ptr [ebp-0Ch],eax 
01220085 8b4df4          mov     ecx,dword ptr [ebp-0Ch] 
01220088 ff151038d700    call    dword ptr ds:[0D73810h] (cssandbox.Program..ctor(), mdToken: 06000002) 
0122008e 8b45f4          mov     eax,dword ptr [ebp-0Ch] 
01220091 8945f8          mov     dword ptr [ebp-8],eax

d:\VSDev\Projects\cssandbox\Program.cs @ 13: 
01220094 8b45f8          mov     eax,dword ptr [ebp-8] 
01220097 8945f0          mov     dword ptr [ebp-10h],eax 
0122009a e875d3686b      call    mscorlib_ni+0x36d414 (6c8ad414) (System.Console.get_Out(), mdToken: 06000945) 
0122009f 8945ec          mov     dword ptr [ebp-14h],eax 
012200a2 8b4df0          mov     ecx,dword ptr [ebp-10h] 
012200a5 8b55ec          mov     edx,dword ptr [ebp-14h] 
012200a8 3909            cmp     dword ptr [ecx],ecx 
012200aa ff15e037d700    call    dword ptr ds:[0D737E0h] (cssandbox.Program.Print(System.IO.TextWriter), mdToken: 06000005) 
012200b0 90              nop

d:\VSDev\Projects\cssandbox\Program.cs @ 14: 
012200b1 90              nop 
012200b2 8be5            mov     esp,ebp 
012200b4 5d              pop     ebp 
012200b5 c3              ret 
0:000> 

かなり読みやすくなりました。

コンストラクターについて

上のアセンブラで call 命令の部分を見ると、cssandbox.Program..ctor() というメソッドを呼び出す箇所があることに気づきます。コードを見ればすぐに分かりますが、これはコンストラクターです。コンストラクターは、内部的に .ctor というメソッドとして扱われるようです。ctor の先頭のドットも含めてメソッド名なので、完全修飾名にするとドットが連続する不思議な名前になります。

当然、!name2ee で Method Descriptor を見つけることもできます。Program.Program や Program.new のような名前では検索できないことを確認して下さい。今回は 2 つのコンストラクターをオーバーロードしていますので、両方とも検出され、パラメーターの種類も出してくれます。

0:000> !name2ee cssandbox cssandbox.Program.Program 
Module:      00d72e94 
Assembly:    cssandbox.exe 
0:000> !name2ee cssandbox cssandbox.Program.new 
Module:      00d72e94 
Assembly:    cssandbox.exe 
0:000> !name2ee cssandbox cssandbox.Program..ctor 
Module:      00d72e94 
Assembly:    cssandbox.exe 
Token:       06000002 
MethodDesc:  00d737b8 
Name:        cssandbox.Program..ctor() 
Not JITTED yet. Use !bpmd -md 00d737b8 to break on run. 
----------------------- 
Token:       06000003 
MethodDesc:  00d737c0 
Name:        cssandbox.Program..ctor(System.String) 
Not JITTED yet. Use !bpmd -md 00d737c0 to break on run. 
0:000>