#IL SpyでもDASMでも出来なかったこと
ExpresionTreeを利用した動的なメソッド構築の結果をデコンパイルしようとした場合、IL Spyを使ってもDASMを使っても一筋縄ではいかない。
なぜなら、プログラムをコンパイルしてアセンブリを生成した時点じゃ、当該アセンブリの中にはExpressioTreeそのものか、ExpressionTreeを構築するためのコードがコンパイルされているだけで生成結果は当然実行時まで存在すらしてない。
なので、そのソースコードやらExoressionTreeやらを表示することは可能でもそのコンパイル結果をデコンパイルすることはかなり面倒なことだった。
そんな中、我々取材班はdnSpyを使うことでこのExpressionTreeの動的生成結果をデコンパイルはおろか、デバッグすら可能だという情報を掴み一路南米アマゾンへと飛んだ。
#dnSpy使ってみた
dnSpy単体で昨日書いたとおりデバッグが可能となってる。デバッグできると言うことは対象アセンブリをdnSpyの中で実行可能と言うことだから、何も考えずにCompileメソッド通して実行すればステップイン出来るんじゃね?と甘い考えを持って試してみた
using System;
using System.Linq.Expressions;
namespace GoAmazon
{
class Program
{
static void Main(string[] args)
{
Expression<Action> expr = () => Console.WriteLine("我々取材班は南米アマゾンへと飛んだ!");
Action act = expr.Compile();
act();
}
}
}
これをdnSpyに食わせてデバッグしてみたら動的にコンパイルされたact
の中にステップイン出来るんじゃないかなって思ったけどactの中にステップイン出来なく、そう甘い話じゃないねーってオチだった。
てなコトで、結局のステップイン出来ないってことは、中身をこじ開けることができないんじゃ無いかなって予測を立てた。
#何とかしてみる
これらの結果から、実行時にDynamicAssemblyでっち上げてしまえば何とかなるかなと考え出したとき、dnSpyのreadmeに
Set breakpoints in any assembly, including framework assemblies, assemblies in the GAC and assemblies existing only in memory
と有ったので、Assemblyはメモリに存在してるだけで行けるんじゃねーか?と言うことで、ヘルパ作って試してみた。
using System;
using System.Linq.Expressions;
namespace GoAmazon
{
class Program
{
static void Main(string[] args)
{
Expression<Func<int, int, int>> addExpr = (x, y) => x + y;
ExpressionHelper.CreateDebugHelper(addExpr).Run(10, 20);
Expression<Action> goAmazon = () => Console.WriteLine("我々取材班は南米アマゾンへと飛んだ!");
ExpressionHelper.CreateDebugHelper(goAmazon).Run();
}
}
}
こんな感じに書いて、コンパイルしたモノをdnSpyに食わせる。食わせた直後が以下
Expressionが展開されてるけど、まぁその辺は無視して、とりあえずデバッグしてみる
デフォルトだと、エントリポイントでブレークするので、ContinueをとりあえずClick!
だと、先ほどまで出来なかった、ExpressionTreeの生成結果をデコンパイルしてデバッグまで出来ちゃう
#タネと仕掛け
タネと仕掛けは以下
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
namespace GoAmazon
{
public abstract class ExpressionHelper
{
protected static readonly MethodInfo BreakInfo = typeof(Debugger).GetMethod("Break");
public static ExpressionHelper<TDelegate> CreateDebugHelper<TDelegate>(Expression<TDelegate> expression) where TDelegate:class=>
new ExpressionHelper<TDelegate>(expression);
}
public class ExpressionHelper<TDelegate> : ExpressionHelper where TDelegate : class
{
internal ExpressionHelper(Expression<TDelegate> expression)
{
(Type[] parameterTypes, Type returnType) getInfo()
{
var tmp = typeof(TDelegate).GetMethod("Invoke");
return (tmp.GetParameters().Select(p => p.ParameterType).ToArray(), tmp.ReturnType);
}
Expression<TDelegate> addBreakPoint()
{
var breakExpr = Expression.Call(BreakInfo);
var blockExpr = Expression.Block(breakExpr, Expression.Invoke(expression, expression.Parameters));
return Expression.Lambda<TDelegate>(blockExpr, expression.Parameters);
}
var asmBld =
AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.RunAndCollect);
var modBld = asmBld.DefineDynamicModule("DynamicallyModule");
var typeBld = modBld.DefineType("Dependence");
var delegateInfo = getInfo();
var methodBld = typeBld.DefineMethod("Target", MethodAttributes.Static | MethodAttributes.Public,
delegateInfo.returnType, delegateInfo.parameterTypes);
addBreakPoint().CompileToMethod(methodBld);
var type = typeBld.CreateType();
Run = type.GetMethod("Target").CreateDelegate(typeof(TDelegate)) as TDelegate;
}
public TDelegate Run { get; }
}
}
ざくっと言うと、動的にアセンブリ・モジュール・タイプ・メソッドをでっち上げてる。
そのときに貰ってきたExpressionを元に、Debugger.Break()
をCallして、デバッグ時にそこで止まるように仕込みを入れてる。
#まとめ
一手間かけて南米アマゾンまで飛べばとりあえずデバッグできた (`・ω・´)
ヘルパの中身はそのうち気が向いたら又書きます。