makeするとconfigureが実行された後、tools/scripts内のビルドスクリプトが走る。libcxx,newlib,libbfcのコードを取ってきてbareflank_gcc_wrapper.shという独自のgcc/clangスクリプトでビルドする。 まず最初にsetup_(deist).shでディストロ別にdockerとかgcc/clangを落としてくる。 次にsetup_build_environmentで./configureが走って終わり。 bareflank_gcc_warpper.shはローカルじゃないとdocker runでホストと同じパスでコンパイル。docker imageはbareflank/compilerという名前でtools/dockerfilesにdockerfilesがある。 各ライブラリのビルドはソースコードを持っている物(bfcrt,bfunwindなど)はcommon/common_(subdir|target).mkを埋め込んでmakeする。これらは要はテンプレートになっていてsubidrはbin,test以外のディレクトリのmakeをする。srcのなかのMakefileはtarget.mkがあってそいつが実質的なmakeの処理。 MakefileはMakefile.bfからBUILD_ABS/makefilesにテンプレート置換などしてからコピーされる。ルートのMakefileからmake -c BUILD_ABS/makefilesすることでビルド開始。
Bit Vectorを命題論理に変換する作業。例えばl-bitのaとbがあって、a or bという項は それぞれの1bitごとに、a[i] or b[i]を取ってandという事になる。 足し算x + yは同様に1 bitごとに考えれば良いが桁上がりなどがあるので、半加算器を考える必要がある。 a=x[i],b=y[i]とすると、 s ≡ (a + b + i ) mod 2 ≡ a ⊕ b ⊕ i o ≡ (a + b + i ) div 2 ≡ a · b + a · i + b · i となって、これをTseitin encodingでCNFにすると、 (a ∨ b ∨ ¬o) ∧ (a ∨ ¬b ∨ i ∨ ¬o) ∧ (a ∨ ¬b ∨ ¬i ∨ o)∧
s2e_qemu_tb_execが中心。tryでパスを実行して、例えばsymbolicなデータへのアクセスがあったりパスのマージ要求があったり、割り込みが入って制御が変わったり、Annotationによって明示的に枝刈り指定(m_doSkip)されるなどの場合、CpuExitExceptionが投げられcpu_execまで戻ってs2e_qemu_finalize_tb_execでTBクリアしてやりなおし(別のパスの実行を始める) これがconcrete->symbolicへの切り替え。 またSymbolic Execution中のパス選択はsearcherがnextStateで気める。stateはrestoreYieldedState->resumeState関数内でsearcher->addStateで追加する。SymDriveはこのsearcherのアルゴリズムを特殊なオペコード挿入などで賢く枝刈りするようにした物。
S2Eはゲストのどの変数シンボル化するなどの情報を受け取る必要がある。これには2つの方法があり、一つはターゲットのプログラムのソースを変更してs2e_make_symbolicを呼び出す事。2つ目はLD_PRELOADでバイナリ修正無しでフックして通知する。 init_envをLD_PRELOADに指定することでゲスト内でターゲットプログラム実行時の__libc_start_mainをフックしてS2Eに知らせる。
QEMUのなかみ(QEMU internals) 5
QEMU Guest Agent
QEMUはゲストOS内にqemu-gaをインストールする事で、ホストからゲストの情報を取得できる。 qemu-gaはvirtio-serialを通して、ホストとUNIXソケットで双方向通信を行う。ゲスト側は/dev/virtio-ports/org.qemu.guest_agen.0などのデバイスファイルを通し、またホスト側は/tmp/qga.agentなどを通して通信する。(これらはqemu起動時にオプションで指定する)
|sh| ./qemu-ga -m virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent.0 ||<
QEMUのなかみ(QEMU internals) 4 10.ブロックデバイス qcow2などの各フォーマットは見たので、次はブロックデバイスのもう少し抽象的な機能を見る。 ブロックレイヤーはブロックジョブという機能があり、これは時間がかかるブロック処理をバックグラウンドで(i.e. VMを稼働させながら)実行する機能。主にLive snapshot, Image Streaming, Live commit, Image mirroringを実行する際使用される。 またゲストエージェント(WIP)を使ってゲストのファイルシステムをフリーズ(つまり状態を固定する)させたり、ゲストのアプリケーションにスナップショット取得の通知をしたりする事も考えられている。 ではまずLive commitを見てみよう。これはQMPでblock-commitを発行すると良い。(block-commitのHMPバージョンは何かしらの理由で無いらしいが、メンテナー自身も理由を忘れている https://lists.gnu.org/archive/html/qemu-block/2015-04/msg00015.html)
block-commitはQMPコマンドなため前回も少し触れたQAPIを使って定義されている。qapi-schema.jsonでqapi/block.jsonがincludeされ、さらにその中でqapi/block-core.jsonがincludeされている。このblock-core.jsonでblock-commitが定義されている。
(qmp-marshal.c)
前回の続きです。 part3ではライブマイグレーション 6.ライブマイグレーション docs/migration.txt ゲストが動いているデバイスの各状態を保存&リストアする機能がある。 これらはQEMUFileを利用して書き出される。これに対するヘルパー関数の役目としてregister_savevmなどが利用される。 がこれはレガシーな方法で今はVMStateが利用されている。
スナップショットの形式: 恐らくセクションに分かれている。register_savevmは、指定したsave/loadメソッドを持つSaveStateEntryを持った新しいセクションを追加する(?)
#pysymemu internal #system 1パスを表すStateクラス。StateクラスごとにSolverがあってそのパスの制約を解く。 Executorが最初のstateを作る。 state = State(args.program, args.argv, env ) executor = Executor(workspace=args.workspace) executor.putState(state)
Stateは__init__でargumentやenvを見て、そのデータの中にWILDCARD('+')が入っていたらmakeSymbolicでその部分をシンボルとして扱う。os.exeで初期レジスタ諸々の設定。 シンボルはsolver.mkArrayで生まれたSymbolオブジェクトをそのまま使い続ける。つまりargvなども実はSymbolオブジェクトを指している事になる。ちなみにsolver.mk***系は裏でz3にコマンドを投げてz3内にも適時対応する変数などを作っている。
LLVMのなかみ (LLVM internals)
基本的にLLVMContextがthread context情報を持っていてModuleが各モジュールを表す。 IR生成時は例えばグローバル変数の生成のような各モジュールに関連付ける必要のある操作ではModuleを渡して操作。またIR生成は最初にllvm::BasicBlockを作ってbuilder.SetInsertPointでBasicBlockを設定する事で、指定されたBasicBlockから命令挿入が始まる。 このようなBasicBlockはコンテキストLLVMContextを指定して生成、関連付け。
#0 ディレクトリ構成 lib/
- CodeGen: IR->マシン非依存データ郡(MachineInstr...)への変換
- Target/X86: ネイティブへの変換(アーキテクチャ依存部)
Pandaのなかみ
tainted_instrプラグイン: 指定されたテイントデータにアクセスした命令をダンプするプラグイン。 テイント解析用のtaint2,コールスタック復元用のcallstack_isntrを使っている。
(tainted_instr.cpp)
|c| bool init_plugin(void self) { panda_require("taint2"); []
TL;DR BasicBlockを実行する度にゲストOS判定を行う。 init_taskのアドレスなどからshared/kernelinfo/procinfo_generic/procinfo.iniデータベースを使ってOSの種類、バージョンを特定しProcInfoに格納。init_taskのアドレスによって特定している(これでディストリビューション,バージョン分けきれるのか?) また.ini内のts_xxxなどはtask_struct構造体のxxxメンバのオフセットを表している。 また、OSのバージョン名.initをkernelinfo/old_stuff/lib_confから検索し、glibcの関数,RVAの組み合わせを読み込む。 ゲストOSの判定が完了したらTLBが実行される度に
APIフック_bynameでは指定された関数名を、上記の方法で取得したfunc,RVAの組み合わせよりアドレスを得てフックをセットする。
APIフック情報はload/saveによる一時的な保存が可能。(いわゆるrecord and replayだと思われる)