直接 ILを書き下して作った実行ファイルをVisualStudioでステップ実行できるよう試みる。
以下のような疑似コードにブレークが当たるようにする。
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
}
}
実行ファイルを生成するプログラム。
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 をドロップして開く。
code.txt にブレークポイントを設定(smp = new Sample() の行で)
F5 で実行すると、設定したブレークポイントで止まる。