まず最初に、これは非常に深いテーマであり、実際に遭遇した友人でもあることを宣言しておきますが、DynamicMethod + ILGenerator を使用して多くの動的メソッドを生成しますが、この動的メソッドでは時々オーバーフロー例外が頻繁に発生します。動的メソッド本体をデバッグする方法ですが、Visual Studio を使用してデバッグすると、個人的には難しいと思います。現時点では、windbg しか使用できません。次に、具体的なデバッグ手順について説明します。
1. テストコード
説明の便宜上、前回のテストコードです。
class Program
{
private delegate int AddDelegate(int a, int b);
static void Main(string[] args)
{
var dynamicAdd = new DynamicMethod("Add", typeof(int), new[] { typeof(int), typeof(int) }, true);
var il = dynamicAdd.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate));
Console.WriteLine(addDelegate(10, 20));
}
}
これは動的に生成された Add(int a,int b) メソッドです。そのメソッド本体をデバッグするにはどうすればよいでしょうか? ここには 2 つのトリックがあります。
最初: Debugger.Break(); を使用します。このステートメントは、プロセスに接続されているデバッガー、つまり Windbg に割り込みを通知できます。
2 番目: Marshal.GetFunctionPointerForDelegate を使用して、デリゲート メソッドの関数ポインター アドレスを取得します。
上記の 2 点を踏まえて、コードを次のように変更します。
static void Main(string[] args)
{
var dynamicAdd = new DynamicMethod("Add", typeof(int), new[] { typeof(int), typeof(int) }, true);
var il = dynamicAdd.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate));
Console.WriteLine("Function Pointer: 0x{0:x16}", Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());
Debugger.Break();
Console.WriteLine(addDelegate(10, 20));
}
次に、windbg を使用して exe プログラムを起動すると、次のようにコンソールに出力が表示されます。
2. コードヒープでメソッド本体のバイトコードを見つける
次に、関数ポインター 0x00000000023d062e を逆コンパイルします。
0:000> !U 0x00000000023d062e
Unmanaged code
023d062e b818063d02 mov eax,23D0618h
023d0633 e9e4c934fe jmp 0071d01c
023d0638 ab stos dword ptr es:[edi]
023d0639 ab stos dword ptr es:[edi]
023d063a ab stos dword ptr es:[edi]
023d063b ab stos dword ptr es:[edi]
023d063c ab stos dword ptr es:[edi]
023d063d ab stos dword ptr es:[edi]
023d063e ab stos dword ptr es:[edi]
023d063f ab stos dword ptr es:[edi]
上記の 23D0618h は最終的な実際の動的メソッド ポインター アドレスであり、dp を使用してポインターの値を確認します。
0:000> dp 23D0618h L1
023d0618 00a90050
次に、00a90050 アドレスを逆コンパイルして、メソッド本体のアセンブリ コードを確認します。
0:000> !U 00a90050
Normal JIT generated code
DynamicClass.Add(Int32, Int32)
Begin 00a90050, size 5
>>> 00a90050 8bc1 mov eax,ecx
00a90052 03c2 add eax,edx
00a90054 c3 ret
次に、2 つのパスがあります。
- おなじみのモード
アンマネージ コマンド bp 00a90050 を使用して、デバッグ用のブレークポイントを直接設定します。
- ハードモード
マネージド コマンド !bpmd xxx を使用して、メソッド記述子の下のブレークポイント デバッグを見つけます。
ここでは難しいモードを選択して対処します。
3. bpmd を使用してブレークポイントを設定します
!bpmd を使用してブレークポイントを設定するには、メソッド記述子が必要です。codeaddr ができたので、逆に記述子を見つけるにはどうすればよいでしょうか? !mln はここから入手できます。
0:000> !mln 00a90050
Method instance: (BEGIN=00a90050)(MD=0071537c disassemble)[DynamicClass.Add(Int32, Int32)]
上記の MD=0071537c 出力はメソッド記述子のアドレスであり、!bpmd -md 0071537c を使用してブレークポイントを設定できます。
0:000> !bpmd -md 0071537c
MethodDesc = 0071537c
Setting breakpoint: bp 00A90050 [DynamicClass.Add(Int32, Int32)]
0:000> g
Breakpoint 0 hit
eax=02505fe8 ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fc
eip=00a90050 esp=0019f488 ebp=0019f508 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
00a90050 8bc1 mov eax,ecx
出力から、ブレークポイントが正常にヒットし、clr が自動的に bp 00A90050 に転送されました。次に、ヒットしたブレークポイントの図を見てください。
上記 2 つのアセンブリ命令は、a+b の結果、つまり、a が ecx に配置され、b が edx に配置されます。信じられない場合は、2 回ステップを実行することもできます。
0:000> t
eax=0000000a ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fc
eip=00a90052 esp=0019f488 ebp=0019f508 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
00a90052 03c2 add eax,edx
0:000> t
eax=0000001e ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fc
eip=00a90054 esp=0019f488 ebp=0019f508 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
00a90054 c3 ret
ここでは ecx=0000000a edx=00000014 です。