kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

月別アーカイブ: 7月 2013

.NET 基礎勉強会で IL の話をしてきました


資料現物とかは以下

PowerPoint のファイル http://sdrv.ms/12Gh11k

ソースとかはこちら https://github.com/kazuk/SimpleILer/

 

比較的簡単(当社比)なコードで、 MSIL を逆アセンブル、制御フロー解析、データフロー解析をやっています。

コアロジックは Msil クラス に実装されていて、上から順に ILInstructions で命令の切り出し、GetRuns で基本ブロックに分解、PopulateControlFlowPath で制御フローパスの列挙、 SimulateEvalStack で評価スタックのシミュレーションをやり、結果として各 IL 命令のデータ依存グラフが得られます。

基本ブロック

簡単に言えば、制御構造単位での命令列です。基本ブロックでは末尾を除いて制御フロー命令が無いようになっています。また、基本ブロックの途中には飛び込まない(制御フローが飛び込む場所は基本ブロックの先頭になる)ように切り出しされます。

[制御フロー命令ではない一連の命令] optional [制御フロー命令]

制御フロー命令が無い基本ブロックの末尾には、次のブロックへの無条件分岐が暗黙に存在すると考えても一緒です。

基本ブロックを導入する事で、制御フローの列挙が非常に簡単になります。各基本ブロック内には制御フロー命令は存在しませんから基本ブロック内の命令を完全取り去って nop にしてしまっても、トポロジー的な意味でのメソッド内での制御構造は変化しません。逆にMSIL を吐き出したく、その中である程度複雑な制御トポロジーを扱いたい(=コンパイラを書きたい)なら複数の基本ブロックを作り上げてその基本ブロックを出力ILバイト列にどういう順序で出力するか、基本ブロックを接続する制御フロー命令は何を使えば良いかという順序で考える事になります。

動作フローのパス列挙

基本ブロックに分解された結果の基本ブロックの末尾命令が示すブロック間の接続を Queue を使った幅優先探索をやっています。

例外とかで飛び出す、飛び込む物を認識したい場合にはもうちょっと細工が要りますが要点は一緒です。

単純に無条件分岐なら分岐先基本ブロックが次の探索対象、条件分岐であれば条件成立時と非成立時の両方を探索するようにキューすることになります。当然に多分岐なら多分岐のすべての分岐先を探索します。

評価スタックのシミュレーションとデータフロー解析

今回の実装では評価スタックの要素型は意識していません。何個のデータを Push するかによって命令オフセットをスタックに積み、何個のデータを Pop するかにしたがって、スタックに積まれた命令オフセットを取り込んで、どの命令が積んだデータをどの命令が受け取るかという事だけを求めています。命令オフセットだけあれば再度 IL バイト列を見てあげれば実際の操作もわかるし、そのスタック要素の型も求まりますのでそこのコストが低ければ解析結果に入れる必要とか無いんじゃないのって事です。

デコンパイルやクロスプラットフォーム変換

この関係性を元に “データを受け取る IL 命令 ( データを出したIL命令 , … )” の形式で出力された構造がデモの最後に表示された変な式構造です。 IL 命令が add だったら + を二項演算子の中置記法でだすとかの判定と変換をしつこくやっていけば普通の言語での式になるでしょう。表示せずに式の木構造を何かに出せばもっとなんかできるかもしれません。(.NETの式木のノードを出力すれば式木になるでしょう) LLVM Instruction で出せばLLVMバックエンドでコンパイルできるはずでしょう。

この辺はやりたきゃどうぞの世界です。

まとめ

こんな基礎の上に .NET が乗っかってるんだって話で MSIL 読もうぜ、コードで!って話でした。

自分的には制約ソルバーとか証明って方に進んでいきたいんだけど、それをするには僕の人生ではまだ時間が足りていない感じで妄想段階を出ていませんです。

NuGet で MsBuild ターゲットが展開できるのが便利すぎて全俺が(ry


というわけで、 NuGet & MsBuild なネタ。

NuGet 2.5 から MsBuild の targets と props の展開ができるようになりました。

NuGet 2.5 のリリースノート

んで何が便利かというと、ビルド時処理のカスタマイズを部品化できて、 csproj を人がいじらないでも、その部品の付け外しが NuGet パッケージのインストール/アンインストールで簡単にできるという事で、これまでは一部のビルドヲタとかビルドをカスタムすることができる人に覆いかぶさっていた作業が NuGet パッケージ入れろよとか、外せよの一言で済んでしまうという事である。

取りあえず、便利すぎて濡れたので作った物

そういうカスタムビルド処理用の NuGet パッケージを書くためのプロジェクトテンプレートを作ってみたのが NuGetCustomBuilder になる。

んで、このプロジェクトテンプレートを元に、一般的によく使いそうなビルド拡張を NuGet パッケージとしてどんどんこさえちゃうよ!ってのが OnBuild だ。

これで何がどう変わるのか

NuGet を絡めてちょっと大きな規模の開発とかをどう変える事ができるのか書いてみたい。

その昔は Visual Studio がいっぱいいっぱいになって落ちるか落ちないかまで、1つのソリューションにライブラリプロジェクトとかどんどん放り込んでいったはずだ。なんでそんなことをしたのかというと、ビルドの依存性とかのつながりがソリューションの単位で閉じていて、ビルド順序の制御とか依存関係を追いかけてやる事ってのはソリューションで閉じた単位でしか、開発環境としては面倒が見れなかったからだ。

これを複数のソリューションに簡単に分割でき、そしてさらにパッケージ間で依存関係が定義できるようになったのが NuGet のいいところでおいしい所で、それ以前はビルドイベントを使ってビルド後にアセンブリをどっかにまとめて出して、それを参照している別のソリューションでビルド前にそれを取り込むような事をやってきたはずだ。

NuGet でそれが可能になったはずでも、旧来通りにソリューション間で依存アセンブリを受渡ししていたりすることは非常に多かった。

まず、最初の要因として底辺揃えが横行する現場では新機能なんてそうそう使えるもんじゃない。ましてや、外部からのモジュールを NuGet で勝手に取り込んでしまっては困るという都合があったりするかもしれない。

その第一の障壁を乗り越えたとしても、取り込めるけど、自分たちで NuGet パッケージを作るのはあまりやられてこなかった。

NuGet パッケージを作るうえでのパッケージングや、パッケージをリポジトリに出すところの問題だ、ビルドイベントを使うと Visual Studio にとっては依存関係のトラッキングの利かないタスクが増えた=何がどう変わるか予想ができないから常にそのタスクを動かさざるを得ない=ビルド時間が延びる。ビルド時間を延ばさずにできる方法といえば、MsBuild の知識を身に着けて csproj を直接編集せざるを得なかった。

結果として、そこまでやるのは必要知識の増大でしかなく(底辺揃えの横行している現場では必要知識の増大は最大の導入障壁である)、64bit版OSにメモリ乗っければ現行路線を進んでも扱えるソリューションもメモリに大きくなるし問題ないでしょうっていうムーアの法則頼りの解決法も 64bit への移行期であることも幸いして機能しているのでパッケージシステムに頼らないでも回る所は回っているからよっぽどでない限り問題無いのだ。

さて NuGet パッケージを作ってみよう、それをローカルリポジトリに配置してみよう

簡単にクラスライブラリを NuGet パッケージにしてみよう。時代にしたがってポータブルクラスライブラリをベースに作ろう。

imageimage

パッケージマネージャーコンソールで、 Install-Package OnBuild.PackageAssemblyForNuget とするとパッケージングの準備がされる。

imageimage

ビルドすると nuspec が作られるので、プロジェクトに追加する。

image

ビルドすると Properties\AssemblyInfo.cs の記述不足が指摘されるので直す。

imageimage

Warning だけになればパッケージは作成されている。(直す直さないは任せる)

image

ご覧のとおりで csproj には直接には手を入れてないので、 MsBuild の知識はなくともこれは可能になったと言える。

続いて、 OnBuild.PublishNugetPackage を入れる。

image

Readme が表示されるので、それに従って設定する。プロジェクト名.csproj.nupublish ファイルをサンプルからコピペで作る。

imageimage

リリースモードでだけリポジトリにコピーが走るようになっているので Release に切り替えてビルドすると、NuGet リポジトリへコピーが走る。

image

ローカル NuGet リポジトリへの発行も、csproj を直接変更する必要はなく実現されたという事になる。

これでローカルなNuGet リポジトリを使って、パッケージを管理する事で共通モジュールの実装等を行う上でのネックだったビルドカスタマイズに関する手間は追放できたし、csproj を直接編集するとか素で言ったら km 単位で引かれるような事をやらせないでOK になったわけだ。

ビルドサーバクラスターという夢

「C#はビルドが早い!」って言われるけどさ、C++等と比べれば確かにそうなんだけど、規模が大きくなればそこそこ遅い。

企業向けの一連のプログラム大き目なシステムで exe を数百個ビルドするとか、共通のアセンブリに手が入ると当然に数百個の exe をビルドしなおさなければならない。

話を戻して1ソリューションに exe を一杯おかれると、ビルドの並列化とかしたくても詰む、なんで詰むかってそりゃ依存関係を追いながらビルドする単位がデカいから詰む。頑張っても Debug 版はこっちのビルドサーバ、Release 版はあっちと分けれて二つとかになって、いまどきの安いマシンを大量投入とか頑張りたくても頑張れない。

んで、ソースはソース管理に、細かいパッケージはパッケージリポジトリにって恰好になると、パッケージの依存関係を追いながら、ビルドをすることができる。んで、小分けパッケージ間での依存関係でビルドしていくなら、ソリューションの単位が小さくなって、数が増えるので並行ビルドができるようになる。

いまどきのビルドサーバーさんは TFS にしても TeamCity にしても Jenkins にしても、ビルドエージェントは必要なら複数持てて並行ビルドが可能なら並行ビルドをやってのけるので、並行度を上げれば上げただけビルドが早くなる構成に持っていくには、小分けパッケージの依存関係で管理するのがおすすめって事。

規模のデカいシステムをメンテしていて、ビルドの遅さに泣いている人はこの辺取り組んでおくんなまし。ってか取り組むような相談受けて作ってみた感なので、頑張ってくださいませ(to 該当者)。

まとめ

OnBuild にあったほうがいいんじゃない?というネタとか、 pull request くれればどんどんマージするし、自分で必要だなーって思ったネタとか入れていくつもりだったりする。プロジェクトテンプレートを元に勝手に作って放流してくれても別に構わない。

んで、csproj のカスタムで悩んでるとかあれば Issues · kazuk/OnBuild に request とかラベル作ってあるんでラベル付けて入れてくれれば、「あぁ、それ俺も使う」とかならさくっと作るかもしれんし、そうでなければ放置するかもしれん。

ちゅうわけで、ソーシャルコーディングしたい。

(まとまっているのかはよくわからない。)