Contenu connexe
Similaire à Runtime c++editing (20)
Runtime c++editing
- 2. Who am I ?
i-saint / Seiya Ishibashi ( @i_saint )
●
●
●
●
ゲーム屋さん (コンソール系)
仕事では主にシステム&ローレベルプログラミングを担当
趣味でグラフィックスとかも
CPU を全コア無駄なく全力でぶん回すのが生き甲斐
- 8. Edit & Continue
●
●
●
●
VisualC++ の強力な機能
実行中にデバッガで止めて C++ ソースを編集すると、それを反映しつつ実行を
継続できる
特定のコンパイルオプション (/ZI) をつけてビルドするだけで対応可能
最適化が有効だと使えない、なぜか x64 非対応、などの制限あり
○ 最適化有効だと使えないのはゲーム屋的に痛い
○ x64 非対応も次世代機を考えるといずれ問題になるはず
強力だが、使える状況が限定されている
- 20. DynamicPatcher
1. C++ ソースを更新したらコンパイル
● 更新の監視
○
○
専用スレッドで FindFirstChangeNotification()
ここは RuntimeCompiledC++ と同じ
● コンパイル
○
○
○
msbuild を呼ぶだけ。GNU 系ツールで言うところの make
.vcxproj ファイルは makefile の機能も果たしている
コンパイルだけしたい場合 “/target:ClCompile” を指定
- 22. Load & Link .obj Files
.obj はフォーマットが公開されており、比較的わかりやすい構造なため、自力でロー
ド&リンクして中にある関数を実行できるようにするのはそこまで難しくはない。
(資料 http://www.skyfree.org/linux/references/coff.pdf )
大雑把には以下の手順
1. ファイルの内容を section を再配置しつつメモリ上にマップ
2. relocation 情報を元にシンボルをリンク
- 23. Load & Link .obj Files
1. section を再配置しつつメモリ上にマップ
●
●
●
.obj ファイルは section と呼ばれるブロックで構成される
section 毎に色んな属性と情報が付随する
○ 読み取り専用データ、実行コード、デバッグ情報、etc
align 指定がある section があり、.obj ファイルの状態では align を考慮した配
置になっていない。自力で再配置する必要がある
○ これを怠ると __m128 の literal を参照とかで謎のクラッシュが起きる
○ VirtualAlloc() で確保した、実行可能属性付きの領域に section の内容を
移していけば ok
- 24. Load & Link .obj Files
2. relocation 情報を元にシンボルをリンク
●
●
●
●
●
relocation 情報: リンク時にここにあのシンボルのアドレスを書き込んでね、とい
う情報
この情報に従ってアドレスを書き込んでいけばリンクが完了する
.obj 内にあるシンボルは .obj のシンボルテーブルから見つけられる
ホストプログラムのシンボルは dbghelp の SymFromName() で取得
○ .pdb が必要になる
○ この API は猛烈に遅く、最初のリンクはやや時間がかかってしまう
特定のシンボルは常にホストプログラムのシンボルでリンクする機構も用意
○ singleton の getInstance() などで必要になる
- 25. Load & Link .obj Files
制限事項
●
●
●
●
/LTCG (リンク時コード生成) オプションでコンパイルされた .obj は対応不可
○ 通常と異なるファイルフォーマットになるため
○ VisualC++ のツールチェイン (dumpbin) ですら情報を出せない
/GR (RTTI 有効) でコンパイルされた .obj は危険
○ vftable の構造が変わる
○ .obj の時点では最初の要素が RTTI 情報へのポインタになっている
○ 通常実行時は 1 個前にずれて -1 番目が RTTI 情報になっている
○ 再現する方法がわからず未対応
○ 幸いゲーム屋は RTTI 使わないことが多い
global オブジェクトのコンストラクタ問題
○ atexit() でデストラクタを呼ぶ処理を登録するため危険。非対応。
デバッガでソースを追えない
○ かなしい
- 27. Patching Functions
古い関数を新しい関数への jmp に書き換えて更新
●
●
関数の先頭 5 byte を新しい関数への jmp に書き換える
○ x86 には命令自身に飛び先アドレスを含められる jmp 命令がある
○ これを使うとレジスタの内容を変えずに制御を飛ばせるため、同じ型の引
数の別の関数に簡単にリダイレクトさせることができる
関数のアドレスは変わらないので vftable の更新が必要なくなり、シリアライズ
なしで class の挙動を更新しつつ実行継続できる
○ データ構造が変わる変更はさすがに無理。ユーザー側の対応が必要
○ シリアライズとか事前に余剰スペースを設けてなんとかするとか
- 28. Patching Functions
古い関数も呼べるようにする
●
●
●
hook 的に使いたいことがたまにあるため対応
更新前の 5 byte を含む命令を別の場所に退避させ、末尾に元の場所への jmp
を書き加えておく
○ このコード片を call すれば更新前の関数が実行される
○ これを実装するのは結構しんどい。x86 は命令が可変長なので、命令を正
しく解釈して 5 byte を含む命令がどこまでか調べる必要がある
○ 相対アドレスを含む命令を含む場合つなぎ変えも必要
○ 幸い命令の解釈はライブラリ (tDisasm) があったのでそれを使用した
MHook の実装が参考になる
○ http://codefromthe70s.org/mhook23.aspx
○ tDisasm は MHook に含まれる
- 29. Patching Functions
その他注意点
●
●
x64 の場合、相対アドレスが 32bit を超えるケースへの対処が必要
○ 64bit absolute indirect jump なる命令があるので、これをトランポリンコー
ドとして挟む
■ 古い関数->トランポリン->新しい関数
○ 古い関数に直接 64bit absolute indirect jump を書き込んでもいいが、14
byte 必要なためやや危険
.obj をロードした時どの関数を更新するか?
○ 問答無用で全部更新するのは危険だし無駄が多い
○ dllexport は .obj に情報が残るのでそれを利用
○ dllexport (を包んだマクロ) をつけたシンボルを自動更新するという仕様に
○ プログラム開始時点でついてる必要はないため、多少ユーザーに手間をか
けることになるが致命的な制限にはならないと判断
- 31. Dealing with .dll
dll との併用
●
●
●
.obj の制限は .dll なら解決できる
○ デバッガでソース追えない -> dll なら可能
○ リンク遅い -> dll ならビルド時に完了してる
○ 奇妙なコンパイルオプション制限 -> dll なら必要なし
.obj も .dll も透過的に扱えれば状況に応じて使い分けができる
プロジェクト分離する手間はかかるが、どちらにせよ規模が大きくなってきたら
dll への分離が必要に迫られることが多い
○ ビルド & リンク時間削減、リンク時メモリ使用量削減などのため
- 32. Dealing with .dll
dll 対応
●
●
●
●
ExportAddressTable を巡回すれば dllexport なシンボルを巡回できる
あとは .obj 同様、古い関数の先頭を新しい関数への jmp に書き換えるだけ
○ dll->dll の関数書き換えだと .pdb すら不要
RuntimeCompiledC++ と違い、シリアライズ不要の恩恵は残る
○ 関数のアドレスは変わらない、という違いから
ここまでは簡単だが…
- 33. Dealing with .dll
ファイルロック問題
●
●
ロードされている dll は対応する pdb と共にロックがかかる
○ ビルドしてできた dll をそのままロードすると、pdb へ書き込めなくなって以
降のビルドが失敗する
○ この問題を回避するため、ロードする前に dll & pdb を適当にリネーム&コ
ピーする必要がある
○ このとき、dll の中にある pdb へのフルパスと GUID、および pdb の中にあ
る GUID を更新する必要がある
■ GUID についてはやや複雑な事情があるが、こうしないとデバッガが
正しいシンボル情報を読めないことがある
この対応が大変
- 34. Dealing with .lib
ついでに .lib にも対応
●
●
.obj が数珠つなぎになっただけの構造なので簡単
○ http://hp.vector.co.jp/authors/VA050396/tech_04.html
インポートライブラリは未対応だが、dll 直接読めるので無問題