ステップ実行

直接 ILを書き下して作った実行ファイルをVisualStudioでステップ実行できるよう試みる。

***

以下のような疑似コードにブレークが当たるようにする。

```text:code.text
Main
{
	smp = new Sample(12300, 45)
	sum = smp.GetSum()
	Console.WriteLine(sum.ToString())
	return
}

Define Sample
{
	field a : int
	field b : int

	Ctor(a: int, b: int)
	{
		this.a = a
		this.b = b
	}
	
	GetSum() : int
	{
		sum = this.a + this.b
		return sum
	}
}
```

実行ファイルを生成するプログラム。

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics.SymbolStore;
using System.IO;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            string exeName = "test.exe";
            AssemblyName asmName = new AssemblyName(exeName);
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Save);
            ModuleBuilder mb = ab.DefineDynamicModule(exeName, exeName, true);

            ISymbolWriter sw = mb.GetSymWriter();
            string codepath = Path.Combine(Directory.GetCurrentDirectory(), "code.txt");
            ISymbolDocumentWriter sdw = sw.DefineDocument(codepath, Guid.Empty, Guid.Empty, Guid.Empty);

            MethodBuilder main = BuildMain(sdw, mb);

            mb.CreateGlobalFunctions();
            ab.SetEntryPoint(main);
            ab.Save(exeName);

            return;
        }

        private static MethodBuilder BuildMain(ISymbolDocumentWriter sdw, ModuleBuilder mb)
        {
            SampleClassInfo cls = BuildSampleClass(sdw, mb);
            MethodBuilder main = mb.DefineGlobalMethod("Main", MethodAttributes.Static, typeof(void), Type.EmptyTypes);
            {
                ILGenerator il = main.GetILGenerator();
                
                // Sample smp;
                LocalBuilder locsmp = il.DeclareLocal(cls.Type);
                locsmp.SetLocalSymInfo("smp");

                // int sum;
                LocalBuilder locsum = il.DeclareLocal(typeof(int));
                locsum.SetLocalSymInfo("sum");
                il.Emit(OpCodes.Nop);

                // smp = new Sample(12300, 45);
                il.MarkSequencePoint(sdw, 4, 2, 4, 29); 
                il.Emit(OpCodes.Ldc_I4, 12300);
                il.Emit(OpCodes.Ldc_I4_S, (byte)45);
                il.Emit(OpCodes.Newobj, cls.Ctor);
                il.Emit(OpCodes.Stloc, locsmp);
                
                // sum = smp.GetSum();
                il.MarkSequencePoint(sdw, 5, 2, 5, 20);
                il.Emit(OpCodes.Ldloc, locsmp);
                il.EmitCall(OpCodes.Callvirt, cls.MethodGetSum, null);
                il.Emit(OpCodes.Stloc, locsum);
                
                // Console.WriteLine(sum.ToString());
                il.MarkSequencePoint(sdw, 6, 2, 6, 34);
                il.Emit(OpCodes.Ldloca_S, locsum);
                il.EmitCall(OpCodes.Call, typeof(int).GetMethod("ToString", Type.EmptyTypes), null);
                il.EmitCall(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), null);
                
                // return;
                il.MarkSequencePoint(sdw, 7, 2, 7, 8);
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ret);
            }

            return main;
        }

        private class SampleClassInfo
        {
            public TypeBuilder Type { get; private set; }
            public ConstructorBuilder Ctor{ get; private set; }
            public MethodBuilder MethodGetSum { get; private set; }

            public SampleClassInfo(TypeBuilder type, ConstructorBuilder ctor, MethodBuilder methodGetSum)
            {
                this.Type = type;
                this.Ctor = ctor;
                this.MethodGetSum = methodGetSum;
            }
        }

        private static SampleClassInfo BuildSampleClass(ISymbolDocumentWriter sdw, ModuleBuilder mb)
        {
            // class Sample
            TypeBuilder tb = mb.DefineType(
                "Sample",
                TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.NotPublic | TypeAttributes.BeforeFieldInit,
                typeof(object)
                );

            // private int a;
            FieldBuilder fa = tb.DefineField("a", typeof(int), FieldAttributes.Private);
            // private int b;
            FieldBuilder fb = tb.DefineField("b", typeof(int), FieldAttributes.Private);

            // constructor
            ConstructorBuilder ctor = tb.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.HasThis | CallingConventions.Standard,
                new Type[] { typeof(int), typeof(int), }, null, null
                );
            {
                // argument a, b
                ParameterBuilder pa = ctor.DefineParameter(1, ParameterAttributes.In, "a");
                ParameterBuilder pb = ctor.DefineParameter(2, ParameterAttributes.In, "b");

                ILGenerator il = ctor.GetILGenerator();

                il.MarkSequencePoint(sdw, 16, 2, 16, 3);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
                il.Emit(OpCodes.Nop);

                // this.a = a;
                il.MarkSequencePoint(sdw, 17, 3, 17, 13);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Stfld, fa);
                
                // this.b = b;
                il.MarkSequencePoint(sdw, 18, 3, 18, 13);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_2);
                il.Emit(OpCodes.Stfld, fb);
                
                // return;
                il.MarkSequencePoint(sdw, 19, 2, 19, 3);
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ret);
            }

            // int GetSum() {}
            MethodBuilder methodGetSum = tb.DefineMethod(
                "GetSum",
                MethodAttributes.Public | MethodAttributes.HideBySig,
                typeof(int), Type.EmptyTypes
                );
            {
                ILGenerator il = methodGetSum.GetILGenerator();
                
                // int sum;
                LocalBuilder locsum = il.DeclareLocal(typeof(int));
                locsum.SetLocalSymInfo("sum");

                il.MarkSequencePoint(sdw, 22, 2, 22, 3);
                il.Emit(OpCodes.Nop);

                // sum = this.a + this.b;
                il.MarkSequencePoint(sdw, 23, 3, 23, 24);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, fa);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, fb);
                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Stloc, locsum);
                
                // return sum;
                il.MarkSequencePoint(sdw, 24, 3, 24, 13);
                il.Emit(OpCodes.Ldloc, locsum);
                il.Emit(OpCodes.Ret);
            }

            tb.CreateType();
            return new SampleClassInfo(tb, ctor, methodGetSum);
        }
    }
}
```

`ModuleBuilder.GetSymWriter` で `ISymbolWriter` を取得。

`ISymbolWriter.DefineDocument` でソースコードファイルを指定して対応する `ISymbolDocumentWriter` を取得。

`ILGenerator.MarkSequencePoint` で、ILのコード位置がソースコードファイル上のどこに対応しているかを設定。
`LocalBuilder.SetLocalSymInfo` でローカル変数のシンボル名を定義。
`ConstructorBuilder` や `MethodBuilder` の `DefineParameter` でメソッド引数のシンボル名を定義。

***

このプログラムを実行すると test.exe と test.pdb が生成される。
同じディレクトリに code.txt を配置しておく。

VisualStudio を起動し、「ファイル」→「開く」→「プロジェクト・ソリューション」で test.exe を選択して開く。
code.txt をドロップして開く。
![cap_a.png](https://qiita-image-store.s3.amazonaws.com/0/151464/2c63740c-5b16-5804-e4b0-26925310ef27.png)

code.txt にブレークポイントを設定（smp = new Sample() の行で）
F5 で実行すると、設定したブレークポイントで止まる。
![cap_B.png](https://qiita-image-store.s3.amazonaws.com/0/151464/39b0e8ee-7906-837f-984f-91fa5bc816db.png)

後は比較的普通にステップ実行できる。
![cap_C.png](https://qiita-image-store.s3.amazonaws.com/0/151464/dc82a2f6-c2a3-6178-ee7d-1e4f67ff8274.png)