Alcantarea - A Visual Studio Extension for Runtime C++ Code Editing

Introduction

Alcantarea is an add-in for Visual Studio that allow you to apply C++ code changes to running programs. This functionality makes development iteration cycle drastically faster, and makes C++ programming more fun.

Visual Studio has similar functionality called Edit and Continue. Alcantarea has some advantages and disadvantages.
Notable advantages:
  • Alcantarea supports x64 native code
  • Alcantarea works with optimized code (/ZI option is not required)
Notable disadvantages:
  • Source code debugging will not work after applying changes
  • Alcantarea does not support managed code
Although these disadvantages are serious, these advantages are very helpful especially for game developers.
Alcantarea は C++ ソースの変更を実行中のプログラムに反映させる Visual Studio 用アドインです。 これにより、ソース編集 -> ビルド -> テスト のサイクルを劇的に速めることができ、また、C++ プログラミングがより楽しくなります。

Visual Studio には Edit and Continue という類似機能がありますが、Alcantarea にはいくつかの利点と欠点があります。
主な利点:
  • x64 のネイティブコードでも機能する
  • 最適化が有効なコードに対しても機能する (/ZI オプションを必要としない)
主な欠点:
  • 編集後はソースコードデバッグができなくなる
  • マネージドコードでは機能しない
やや厳しい欠点があるものの、この 2 つの利点は特にゲーム開発者には重宝するはずです。

demo ( watch in full size )

How To Use

  • Alcantarea supports Visual Studio 2010 - 2015 preview (Tested on 64-bit Windows 7 and 8.1).
  • Alcantarea requires /Zi (generate debug information), /MAP (generate map files) options (detailed in next section).
  • After the installation complete, the menu (right image) will appear.menu
  • The basic usage is: start program by "Alcantarea"->"Start Debugging", edit source, and "Alcantarea"->"Apply Changes".
  • By default, Alcantarea updates the cursor's current position function, and the functions updated in the past. But in some special case, Visual Studio cannot get cursor's current position function (the function is wrapped by macro, the .sdf file could not load, etc). In this case, you need to use Symbol Filter to choose functions to update.
  • By using Symbol Filter ("Alcantarea"->"Symbol Filter"), you can choose functions to update, and set on load/unload handler (functions called on applying changes). You can set ignore pattern in "Symbol Filter" tab on "Alcantarea"->"Optins". Symbols that match these patterns are not shown in Symbol Filter.
  • "Alcantarea"->"Attach to Debugged Process" command makes programs that are started by normel "Debug"->"Start Debugging (F5)" able to apply changes. But it sometimes fails. Using "Alcantarea"->"Start Debugging" is recommended.
  • Unlike Edit and Continue, Alcantarea cannot apply changes in break mode. If you want to stop the program before applying changes, you can use "Alcantarea"->"Suspend/Resume Process". This command suspends/resumes all threads except Alcantarea's communicator thread in target process.
  • You can load any .obj files and apply functions in it by using "Alcantarea"->"Load Obj Files". This means, you can apply any languages' code changes if the language's compiler generates .obj files. (e.g. Intel ISPC. This is good for high performance programming)
  • "Alcantarea"->"Load Symbols" loads symbol information from .pdb and .map files. By default, symbols are loaded automatically and you do not need to use this command. You need this command only when "Hook LoadLibrary()" option is disabled and runtime-linker requires symbols in DLLs loaded by LoadLibrary().
  • Alcantarea は Visual Studio 2010 - 2015 preview に対応しており、64 bit の Widnows 7 と 8.1 で動作を確認しています。
  • Alcantarea を使う際はプロジェクト側の /Zi ( デバッグ情報の生成) と /MAP (.map ファイル生成) オプションを有効にする必要があります (詳細は後述)。
  • インストール後、右図のようなメニューが追加されます。menu
  • 基本的な使用方法は "Alcantarea"->"Start Debugging" でプログラムを起動し、ソースを編集し、"Alcantarea"->"Apply Changes" で変更を適用するだけです。
  • 変更が適用されるのは、デフォルトでは VisualStudio のエディタの現在のカーソル位置の関数、および過去に変更を適用した関数です。 しかし、稀にエディタの現在のカーソル位置の関数をうまく取得できないことがあります (マクロで包まれていてうまくパースできない、何らかの要因で .sdf ファイルを読めていない など)。このような場合シンボルフィルタによる手動設定が必要になります。
  • シンボルフィルタ "Alcantarea"->"Symbol Filter" を使うと、関数の 更新する/しない などを個別に設定することができます。また、ロード/アンロード時 (変更を反映する前後) に自動的に呼ばれるハンドラ関数の設定もできます。無関係なシンボルが多数表示されて煩わしい場合、オプション ("Alcantarea"->"Options") の "Symbol Filter" の項目でパターンを定義することで、それにマッチするシンボルを表示しないようにできます。デフォルトでコンパイラが暗黙に生成するシンボル群と std:: のシンボルを非表示にするパターンが設定されています。
  • 通常の "デバッグ開始 (F5)" でプログラムを起動した場合でも、"Alcantarea"->"Attach to Debugged Process" で変更を適用できるようになります。しかしこちらは失敗することがあります。"Alcantarea"->"Start Debugging" で起動する方を推奨します。
  • Edit and Continue と違い、Alcantarea はデバッガでプログラムを止めてる間は変更を適用できません。止めてから変更を適用したい場合、"Alcantarea"->"Suspend/Resume Process" コマンドを使います。このコマンドは対象プロセスの Alcantarea の通信スレッド以外のスレッドを全て suspend/resume させます。
  • "Alcantarea"->"Load Obj Files" を使うと、任意の .obj ファイルを読み込んでそれに含まれる関数を適用することができます。これにより、コンパイラが .obj ファイルを生成する言語であれば C/C++ 以外でも実行時に変更を適用することができます。(例: Intel ISPC。スピード狂にお勧めの言語です)
  • "Alcantarea"->"Load Symbols" は .pdb, .map ファイルからシンボル情報を読み込みます。通常は自動的に読み込むのでこれを使う必要はありません。 これが必要になるのは、オプション "Hook LoadLibrary()" を切っている状態で、変更適用に LoadLibrary() でロードしたモジュールのシンボルが必要になる時です。

How It Works

This section describes how Alcantarea applying code changes.
Unfortunately, understanding it is important to avoid troubles.

What Alcantarea do is:
  1. Start program and inject a dll that communicate with the add-in.
  2. Compile C++ sources and request the program to load .obj files when "Apply Changes" is executed.
  3. Load & link .obj files, and replace existing functions with new ones in .obj files. (Emit jmp instructions on the head of old functions to redirect to new ones)
以下に Alcantarea が内部でどのようなことをやっているかを解説します。
遺憾ながら、このアドインを使う上で内部処理を知っておくことはトラブルを避けるために重要です。

Alcantarea は以下のような手順で実行時に変更を適用しています:
  1. プログラムを起動し、アドインと通信するための dll を注入する
  2. "Apply Changes" が実行されると、アドイン側で C++ ソースをコンパイルし、プログラム側に .obj ファイルをロードするリクエストを送る
  3. プログラム側は .obj ファイルを自力でロード&リンクし、.obj ファイルの中の関数で元の関数を更新する (元の関数の先頭を新しい関数への jmp に書き換える)

This approach works quite well. But there are some limitations and pitfalls.
  • Symbol information is required to runtime linking. So one of /Zi (generate debug information) and /MAP (generate map files) options is required. Practically, both of them are required. /Zi is for debug. /MAP is for speed. Searching symbols from debug information is painfully slow when the program is large. By using .map files, it will be much faster.
  • Source code debugging will not work after applying changes. Adding debug information from .obj files at runtime is difficult to implement and currently in investigation.
  • Updating functions that have function-static objects can be dangerous. Function-static objects in before-updated functions and after-updated functions are different instances. Mixing them will cause problems.
  • Runtime-edited code cannot catch exceptions. Catching exception is difficult to implement and there is no plan.
  • Alcantarea will not work if /LTCG (link time code generation) option is enabled. /LTCG makes .obj files unloadable.
  • Changing data structure is difficult to handle and should be avoided. Applying code changes does not change existing data's structure. So the program needs to can use both before-changed data structures and after-changed ones properly.
  • Runtime-edited code cannot use functions in external libraries that are not contained in host program. It will cause link error because runtime linker cannot find these symbols. (Link errors simply cancel applying code changes. The program can continue running)
  • Adding or removing virtual functions cannot be applied properly because vftable cannot be modified.
  • Inlined functions cannot be updated. Nothing can be done about it.
In conclusion, Alcantarea is fit for small program changes such like changing algorithms in functions. And is not fit for large program changes such like changing data structure or adding new classes.

Runtime loader & linker is DynamicPatcher with some improvements. DynamicPatcher is my previous work. And it is distributed under CC BY license. If you want to use this functionality in your program, you can use it freely. DynamicPatcher was used by Riot Games.
このアプローチは概ねうまく機能しますが、いくつかの制限や注意すべき点があります。
  • 実行時に自力でリンクするのにシンボル情報が必要なため、 /Zi (デバッグ情報生成) か /MAP (map ファイル生成) のどちらかが必須になります。/Zi は通常有効になっていると思われますが、デバッグ情報からシンボルを探すのは大規模なプログラムでは非常に遅くなります。それと比べ、.map ファイルを使った場合は高速です。このため事実上両オプション必須に近い状態です。
  • 実行時編集後、ソースコードデバッグができなくなります。 厳しい制限なので対処したいところですが、.obj ファイルからデバッグ情報を取り出して実行中にデバッグ情報を追加するのは難しく、実装の見通しは立っていません。
  • 関数内 static オブジェクトを含む関数を更新するのは危険です。 更新前の関数の static オブジェクトと更新後のそれは別のインスタンスとして扱われます。2 つのインスタンスが混ざると高確率でよくないことが起きます。
  • 実行時編集されたコードの中では例外の catch ができません。例外の catch は実装が難しく、実装は未定です。
  • リンク時コード生成 (/LTCG) 最適化オプションが有効になっていると編集を反映できなくなります。 このオプションは .obj ファイルのフォーマットを変え、ロードできなくしてしまいます。
  • データ構造を変える変更は扱いが難しく、できるだけ避けるべきです。 プログラムを変更しても既存のデータの構造は変わらないため、プログラムは変更前と変更後両方のデータ構造を適切に扱える作りになっている必要があります。
  • 実行時編集されたコードは、ホストプログラムが含んでいない外部ライブラリの関数を使えません。 ホストプログラムにないシンボルは見つけられないため、リンクエラーになります。この場合、エラーメッセージが出て変更が反映されないだけで、クラッシュしたりはしません。
  • virtual 関数の追加や削除は正しく反映できません。vftable の構造が変えられないためです。
  • インライン展開された関数は更新できません。これは対処のしようがありません。
総じて、Alcantarea は関数の中のアルゴリズムを変えるような小さな変更には向きますが、データ構造を変えたり class を追加したりなどの大きな変更には向きません。

余談ながら、.obj ファイルのローダやリンカは DynamicPatcher に改良を加えたものになっています。 DynamicPatcher は私の過去の制作物であり、CC BY ライセンスでオープンソースで公開しています。この機能をご自身のプログラムに組み込みたい場合、自由にご利用いただけます。
DynamicPatcher は Riot Games に採用された実績があります。

また、実装に関する踏み入った話は、CEDEC 2014 のセッション "Live Coding in C++" の資料 が参考になると思われます。

Download

Alcantarea-1.0.2.vsix | source

  • 1.0.2 (2015/01/08)
    • now Alcantarea is free & open-sourced
    • added support for Visual Studio 2015 (preview)
  • 1.0.1 (2014/02/10)
    • bug fix: Alcantarea -> Start Debugging failed in some case if the project is in solution folders
    • bug fix: Alcantarea -> Start Debugging failed in some case if Configuration Properties -> Debugging -> Command is not $(TargetPath)
    • bug fix: Supported .obj files compiled with /bigobj
  • 1.0.0 (2013/12/24)
    • first public release

Forum

Jump to google group