LoginSignup
11
9

More than 5 years have passed since last update.

式木の動的生成結果をデバッグするため我々取材班は南米アマゾンへと飛んだ

Last updated at Posted at 2017-03-18

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に食わせる。食わせた直後が以下
image.png

Expressionが展開されてるけど、まぁその辺は無視して、とりあえずデバッグしてみる
デフォルトだと、エントリポイントでブレークするので、ContinueをとりあえずClick!

だと、先ほどまで出来なかった、ExpressionTreeの生成結果をデコンパイルしてデバッグまで出来ちゃう

image.png

image.png

タネと仕掛け

タネと仕掛けは以下

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して、デバッグ時にそこで止まるように仕込みを入れてる。

まとめ

一手間かけて南米アマゾンまで飛べばとりあえずデバッグできた (`・ω・´)
ヘルパの中身はそのうち気が向いたら又書きます。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9