Closed kenju closed 7 years ago
VMへの制御フレームのPop/Pushの実行はAPI化されている。このメソッド内部で必要な情報をログするメソッドを挟み込めば行けそう。
vm_insnshelper.c:vm_push_frame
vm_insnshelper.c:vm_pop_frame
Memo
vm_core.h
のVM Debug Level(VMDEBUG)
オプション、1, 2
以外は↓のエラーで動かない。余裕があったら修正する。
../ruby/vm_dump.c:292:12: error: no member named 'stack' in 'struct rb_thread_struct'
if ((th)->stack + (th)->stack_size > (VALUE *)(cfp + 1)) {
~~~~ ^
../ruby/vm_dump.c:292:26: error: no member named 'stack_size' in 'struct rb_thread_struct'
if ((th)->stack + (th)->stack_size > (VALUE *)(cfp + 1)) {
~~~~ ^
2 errors generated.
make: *** [vm_dump.o] Error 1
VMDEBUG
を変更する方法:
コンパイル時に見るオプションなので、変更するときはソースコードを書き換えた上で再コンパイル。
VMDEBUG=2
でコンパイルした時に有効になる、vm_dump:rb_vmdebug_stack_dump_raw()
(随所ではSDR()
で呼び出されている)がデバッグ用のコントロールフレームをいい感じで出力している。このメソッドの実装を参考にする。
-- Control frame information -----------------------------------------------
c:0005 p:---- s:0017 e:000016 CFUNC :inspect
c:0004 p:0005 s:0014 e:000012 BLOCK ../ruby/test.rb:2 [FINISH]
c:0003 p:---- s:0010 e:000009 CFUNC :times
c:0002 p:0008 s:0006 e:000005 EVAL ../ruby/test.rb:1 [FINISH]
c:0001 p:0000 s:0003 E:000390 (none) [FINISH]
| 0005 putstring "hello world"
| 0007 opt_send_without_block <callinfo!mid:p, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
2.times do
p 'hello world'
end
のコードに対して、シンプルにフレームスタックのみをログした出力結果が以下の通り↓
https://gist.github.com/kenju/8221df17ebafcef5be05eb7ef32e7d16
これを一次ソースとする。この次にRubyでデータ整形した上でoFに流し込めばよさそう。
Memo
2.times do
p 'hello world'
end
のようなシンプルなコードに対しても、https://gist.github.com/kenju/8221df17ebafcef5be05eb7ef32e7d16 を見るとわかる通り、↓のような行が大量に出力される。
:inherited
c:0002 p:---- s:0006 e:000005 CFUNC :inherited
c:0001 p:0000 s:0003 E:0012e0 (none) [FINISH]
これはrb_define_module
などでModuleのinheritedが呼ばれるため。そもそも表示させる必要はないので、スキップさせたい。これも余裕があれば or 後日対応したい。
:set_encoding
Push +
c:0002 p:---- s:0006 e:000005 CFUNC :set_encoding
c:0001 p:0000 s:0003 E:0012e0 (none) [FINISH]
若干だが、エンコードセットでも呼ばれている。これも本来のコードにとっては不要。
{
"code": "2.times do\np 'hello world'\nend",
"controlFrames": [
{
"type": "push",
"stacks": [
{ "c": "0006", "p": "----", "s": "0021", "e": "000020", "magicType": "CFUNC", "insns": ":inspect" },
{ "c": "0005", "p": "----", "s": "0018", "e": "000017", "magicType": "CFUNC", "insns": ":inspect" },
// ...
]
},
{
"type": "pop",
"stacks": [
{ "c": "0005", "p": "----", "s": "0018", "e": "000017", "magicType": "CFUNC", "insns": ":inspect" }
// ...
]
}
]
}
controlFrames
> stacks
:
{
"count": "0006",
"programCounter": "----",
"stackPointer": "0021",
"envPointer": "000020",
"magicType": "CFUNC",
"posbuf": ":inspect"
}
NOTE:
[Q]
BLOCK
typeになるが、Enumarator#sum
など、Cでイテレーターを書いてある箇所があるその他
ruby foo.rb
を実行した時の最初の制御フレームのtypeはTOP
ではない。
TOP
-> EVAL
だったが、Top level bindingを実装する時にEVAL
-> TOP
に変わった出力したログファイルをJSONに変換するロジックは以下のレポジトリで担当させた。
[作業報告]
SDR()
をベースに少し書き換えたVMスタックフレームの出力をログファイルに出力し、JSONに変換するところまでできた(コードはものすごくみづらいので、後日テスト書いてリファクタする)
最後のvisualize部分に移る。
時間が足りなかったので、スタックフレームを一旦oFではなくReact Appで実装。 あとはAnimationつければそれっぽくなる。
rubyhackchallenge期間でやりたかったことは達成したため、このIssuesはクローズします。
今後の開発は https://github.com/kenju/vm_stackexplorer で行います。
ありがとうございました!
概要
VMの実行状況を可視化することで、VMの内部実装への理解が深まる手段を提供できるとともに、デバッグ時の有用なツールとなりうる。
コアコンセプト
YARVは、フレームスタックを利用してRubyの制御の流れを管理している(具体的には
rb_control_frame_t
構造体のスタック)。rb_control_frame_t
は、SP(stack pointer)
やPC(program counter)
といったポインタを持っているので、この一連の制御フレームがあればRubyの実行プロセスを再現可能なはず、という点に着目。フレームスタックを何かしらの中間表現に出力し、それを元にインタラクティブに再生可能(例:アニメやDVDプレーヤー)なコンテンツを作成する。
タスク
今回は「中間表現の作成」まで。
printf
で標準出力log/framestacks.txt
のようなファイルに出力後日TODO