動的なilの生成と編集

36
動的なILの生成/編集 terurou 2013/07/20

Upload: terurou

Post on 24-May-2015

2.909 views

Category:

Technology


6 download

DESCRIPTION

.NET基礎勉強会(http://connpass.com/event/2441/)での発表資料のバグ修正版です

TRANSCRIPT

動的なILの生成/編集

terurou 2013/07/20

目次

• 自己紹介

• IL生成の予備知識

• ILの生成方法

• Windows Store AppsでのIL生成

• まとめ

自己紹介

7月から無職してます。

技術領域

• Haxe/JavaScriptとかWindows Store Appsとか WPFとかSilverlightとかできます。

• Haxe/JavaScriptは「なごやまつり」で話す予定です。

• ギョーム系DataGridの実装に定評があります。

• たぶんシステムアーキテクチャ設計が本業です。

• 最近はF#でWebSocketサーバを試作してました。

コミュニティ活動 - DSTokaiとNGK

• DSTokai : 東海地方のメタコミュニティ

• IT系イベントカレンダーとコミュニティ連絡用ML

• http://go.dstokai.info/

• NGK : なごや ごうどう こんしんかい

• 毎年忘年会を開催しています

• 一昨年は100人、去年は120人

• ちなみに今年は12/7に開催予定

• 花見はここ数年サボってます

ILと私

• IronPythonとSilverlight2でR&D(3年前)

• Silverlight上でScalaアプリを実行(2年前)

• Silverlightアプリを動的生成(1年半前)

• Store AppsからDesktop用EXE生成(1年前)

• その他、.NETのお仕事で仕方なく…

IL生成の予備知識

photo: http://www.flickr.com/photos/seditiouscanary/1279041211/, CC BY-NC-ND 2.0

ILとは

• .NETのアセンブリ言語

• IL = Intermediate Language = 中間言語

.assembly extern mscorlib { .publickeytoken = (b7 7a 5c 56 19 34 e0 89) .ver 4:0:0:0 } .assembly Test { .hash algorithm 0x00008004 .ver 1:0:0:0 } .module Test.exe .class auto ansi Test.Program extends [mscorlib]System.Object { .method static void Main (string[] args) cil managed { .maxstack 8 .entrypoint IL_0000: ldstr "Hello World" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } }

.NETとILの関係図

Compiler

ilasm

ildasm

CLR

icons: http://www.gentleface.com/, CC BY-NC 3.0

C#, VB, F#, ...

IL

Assembly Native 実行

Assembly(PE:Portable Executable) .NETの実行バイナリ(EXE, DLL) ilasm ILアセンブラ .NET Frameworkに標準付属 ildasm IL逆アセンブラ .NET Frameworkに標準付属

動的なILの生成

.NETにはILを動的(アプリ実行時)に生成するAPIが標準で備わっています。

• 静的な型やメソッドの作成

• 生成したILの即時実行

• Assembly(EXE, DLL)の生成・保存

• 既存APIの編集(Monkey Patching)は不可能

• 別の手段を使えば編集することも可能

IL生成の利用例

• 処理の高速化

• 動的メソッドコール(Reflection)

• シリアライザ(動的に静的型を生成)

• 透過プロキシやMockの内部実装

• アスペクト指向/Weaving

• 自作コンパイラのバックエンド

本当にIL生成が必要か?

• IL生成は黒魔術

• 利用例で紹介した通り、必要な個所は限定される

• 問題点も多い

• 低レベルAPI

• 可読性やメンテナンス性の低下

• プログラミング言語の型制約の無視できる

• 型安全ではなくなり、品質保証にコストがかかる

IL生成の前に検討すべきもの

• デザインパターンやIoC Container

• コードの書き方で逃げられないか?

• T4テンプレート(自動コード生成)

• コンパイル前にどうにかならないか?

• System.Dynamic(C# 4.0のdynamic型)

• 動的プロパティや動的メソッドコール等で利用

• 内部的には実行時に自動でILを生成している

• 下手に手書きでILを生成するよりも高速

• F#(高度な型システム, TypeProvider)

• C#やVBの型システムが貧弱だからなのでは??

ILの生成方法

IL生成の基本的な手順

1. C#でひな形コードを書いてビルド

2. Assemblyを逆アセンブリ

• ildasmやILSpy等を利用する

3. 逆アセンブリした結果を見ながらコードを書く

逆アセンブリツール

• ILSpy http://ilspy.net/

• 無償で使えておすすめ

• IL以外にもC#やVBへ逆アセンブリ可能

• ildasm

• .NET SDK標準ツール

• C:¥Program Files¥Microsoft SDKs¥Windows¥... に入っている

• .NET Reflector

• 有償($95)

API

• 標準API

• System.Reflectoin.Emit(.NET 2.0/1.1~)

• Sysytem.CodeDom(.NET 2.0/1.1~)

• System.Linq.Expressions(実質.NET 4.0~)

• 外部ライブラリ

• Mono.Cecil

• IKVM.Reflection

• CLR Profiling API

System.Reflection.Emit(.NET 2.0/1.1~)

• IL生成の基本的なAPI

• 動的に生成したILコードは即座に実行可能

• 低レベルAPIなのでプログラムが煩雑になる

• OpCodeを1つずつ埋め込むことが必要

• .NET 3.5までの環境ではこれを使うことになる

System.Reflection.Emitのコード例

Hello Worldを表示するだけのEXEを生成 var appDomain = AppDomain.CurrentDomain; var assemblyBuilder = appDomain.DefineDynamicAssembly( new AssemblyName("Test"), AssemblyBuilderAccess.RunAndSave); var moduleBuilder = assemblyBuilder.DefineDynamicModule("Test.exe"); var typeBuilder = moduleBuilder.DefineType("Program", TypeAttributes.Class); var methodBuilder = typeBuilder.DefineMethod("Main", MethodAttributes.Static); methodBuilder.SetParameters(typeof(string[])); var il = methodBuilder.GetILGenerator(); var write = typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }); il.Emit(OpCodes.Ldstr, "Hello World"); il.Emit(OpCodes.Call, write); il.Emit(OpCodes.Ret); typeBuilder.CreateType(); assemblyBuilder.SetEntryPoint(methodBuilder); assemblyBuilder.Save("Test.exe");

System.CodeDom(.NET 2.0/1.1~)

• C#やVBのコンパイラAPI

• 即時実行可能(スクリプト的に利用できる)

• Assemblyの出力可能

• System.Reflection.Emitの置き換えにはらなない

• コンパイル時にオーバーヘッドが発生する

• 生成するILを動的に変更したい場合、文字列として ソースコードを組み立てる必要がある

System.Linq.Expressions(実質.NET 4.0~)

• Expression Tree(式木)

• 式木とはいうものの、構文木(if文, for文など)もカバー

• System.Reflection.Emitよりも簡単

• ラムダ式からExpression Treeを構築できる

• Expression Treeを走査したり改変もできる

• .NET 4.0以降でIL生成といえばこれ

Expression<Action> exp = () => { Console.WriteLine("Hello World"); }

System.Linq.Expressionsのコード例

Hello Worldを表示するだけのEXEを生成 var appDomain = AppDomain.CurrentDomain; var assemblyBuilder = appDomain.DefineDynamicAssembly( new AssemblyName("Test"), AssemblyBuilderAccess.RunAndSave); var moduleBuilder = assemblyBuilder.DefineDynamicModule("Test.exe"); var typeBuilder = moduleBuilder.DefineType("Program", TypeAttributes.Class); var methodBuilder = typeBuilder.DefineMethod("Main", MethodAttributes.Static); methodBuilder.SetParameters(typeof(string[])); Expression<Action> exp = () => Console.WriteLine("Hello World"); exp.CompileToMethod(methodBuilder); typeBuilder.CreateType(); assemblyBuilder.SetEntryPoint(methodBuilder); assemblyBuilder.Save("Test.exe");

System.Linq.Expressionsのコード例

ラムダの書き換え(メッセージ出力を追加)

class SampleVisitor : ExpressionVisitor { protected override Expression VisitLambda<T>(Expression<T> node) { Expression<Action> post = () => Console.WriteLine("post"); return Expression.Lambda<Action>(Expression.Block(new Expression[] { Expression.Invoke(node), Expression.Invoke(post) })); } }

var visitor = new SampleVisitor(); var result = visitor.Visit(exp); var func = ((Expression<Action>)result).Compile(); func.Invoke();

Mono.Cecil

• Assembly(EXE, DLL)のRead/Write

• 生成時にデバッグシンボルの出力も可能

• Assemblyの解析、処理の挿入や改変

• リソースの挿入、差し替え

• 生成したILの即時実行は不可能

• Assembly保存→DLLとして読み込みなら可能

• コンパイラ, AOP, IL to JavaScript, ...

Mono.Cecilのコード例

Hello Worldを表示するだけのEXEを生成

var assembly = AssemblyDefinition.CreateAssembly( new AssemblyNameDefinition("Test", new Version()), "Test.exe", ModuleKind.Console); var module = assembly.MainModule; var type = module.Types.First(); var method = new MethodDefinition( "Main", MethodAttributes.Static, module.Import(typeof(void))); var il = method.Body.GetILProcessor(); var write = typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }); il.Emit(OpCodes.Ldstr, "HelloWorld"); il.Emit(OpCodes.Call, module.Import(write)); il.Emit(OpCodes.Ret); type.Methods.Add(method); assembly.EntryPoint = method; assembly.Write("Test.exe");

Mono.Cecilのコード例

全メソッドの末尾(retの前)にメッセージ出力を追加 var assembly = AssemblyDefinition.ReadAssembly("Test.exe"); var write = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); assembly.Modules .SelectMany(x => x.Types) .SelectMany(x => x.Methods) .ToList() .ForEach(method => { var il = method.Body.GetILProcessor(); var ldstr = il.Create(OpCodes.Ldstr, "Finish"); var call = il.Create(OpCodes.Call, method.Module.Import(write)); method.Body.Instructions .Where(x => x.OpCode == OpCodes.Ret) .ToList() .ForEach(x => { il.InsertBefore(x, ldstr); il.InsertBefore(x, call); }); }); assembly.Write("Test-Mod.exe");

Assembly内の全メソッドを取得

Console.WriteLine("Finish");を 挿入

IKVM.Reflection

• Assembly(EXE, DLL)のRead/Write

• 生成時にデバッグシンボルの出力も可能

• System.Reflectionに似たAPI

• 生成したILの即時実行は不可能

• Assembly保存→DLLとして読み込みなら可能

• Mono C#コンパイラのバックエンドで採用

CLR Profiling API

• .NET界でも最上級レベルの黒魔術

• 実行中に全APIをフックして処理の差し替えが可能

• 私は使ったことはありません

• 参考資料

• C# 動的メソッド入れ替え - Apply a monkey patch to any static languages on CLR - http://urasandesu.blogspot.jp/2011/10/c-apply-monkey-patch-to-any-static.html

Windows Store AppsでのIL生成

photo: http://www.flickr.com/photos/mr_o/8028197750/, CC BY-NC-SA 2.0

.NET for Windows Store Apps

同じ.NETでも、Windows Store AppsとDesktopの APIは大きく異なる

• 名前空間の整理、スリム化

• 非同期前提

• セキュリティ的に危険なAPIを徹底削除

IL関連のAPI

• System.Linq.Expressionsのみが存在

• System.Reflection.Emitは削除

• Assemblyを生成/ロードするAPIは削除

• ILを生成してもローカルキャッシュできない

• Assemblyは所詮バイト列なのでどうにかなる

• ロードできないので、生成しても自分で使えない

• Windows RTで自作.NETアプリが動作すれば…

まとめ

photo: http://www.flickr.com/photos/johncline/108863343/, CC BY-NC-ND 2.0

まとめ

極力、IL生成は避ける。

どうしてもIL生成したいのなら

• とりあえずSystem.Linq.Expressions

• 自由度が高く、APIもわかりやすい

• Mono.Cecilを使えばAssemblyの改変が捗る

• 採用しているOSSも多いので参考にしやすい

ご清聴ありがとうございました