VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))
が0になってrb_reg_search0
経由でVM_EP_LEP
をした時にVM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))[0]
を読もうとしてSEGVしたり、ep[1]
(VM_ENV_DATA_INDEX_ENV
)が0になってvm_env_write_slowpath
経由でRVALUE_OLD_P_RAW
した時に(RBASIC(ep[1])->flags
を読もうとしてSEGVしたりしているので、epがどこで作られるのか調べてそのメモを置いておくことにする。めちゃくちゃ迷走してるので多分本人以外には参考にならない
自分も忘れかねないので書いておくとepはenvironment pointerの略
そもそもcfpはどこでどう積まれるか
死んだ状況でbtをやると、こういう感じになっている
#0 0x00005555556dbc48 in VM_ENV_FLAGS (ep=0x0, flag=2) at vm_core.h:1081
#1 0x00005555556dbd71 in VM_ENV_LOCAL_P (ep=0x0) at vm_core.h:1143
#2 0x00005555556dc576 in VM_EP_LEP (ep=0x0) at vm.c:28
#3 0x00005555556dc61b in VM_CF_LEP (cfp=0x7ffff7fd1ad0) at vm.c:64
#4 0x00005555556f8c1d in vm_cfp_svar_get (th=0x555555a8c470, cfp=0x7ffff7fd1ad0, key=1) at vm.c:1210
#5 0x00005555556f8cd1 in vm_svar_get (key=1) at vm.c:1224
#6 0x00005555556f8d1b in rb_backref_get () at vm.c:1237
#7 0x0000555555640a9c in rb_reg_search0 (set_backref_str=1, reverse=0, pos=0, str=93825000927280, re=93825000636760) at re.c:1508
#8 rb_reg_search (reverse=0, pos=0, str=<optimized out>, re=93825000636760) at re.c:1580
#9 reg_match_pos (pos=0, strp=<synthetic pointer>, re=93825000636760) at re.c:3014
#10 rb_reg_match (re=93825000636760, str=93825000927280) at re.c:3068
#11 0x00005555556e27db in call_cfunc_1 (func=0x5555556409d0 <rb_reg_match>, recv=93825000636760, argc=1, argv=0x7ffff7ed2430)
at vm_insnhelper.c:1769
#12 0x00005555556e3283 in vm_call_cfunc_with_frame (th=0x555555a8c470, reg_cfp=0x7ffff7fd1ad0, calling=0x7fffffff25c0, ci=0x555555cab5b0,
cc=0x555555cab680) at vm_insnhelper.c:1954
#13 0x00005555556e33cd in vm_call_cfunc (th=0x555555a8c470, reg_cfp=0x7ffff7fd1ad0, calling=0x7fffffff25c0, ci=0x555555cab5b0,
cc=0x555555cab680) at vm_insnhelper.c:1970
...
#0 0x000055555558af6a in RVALUE_OLD_P_RAW (obj=0) at gc.c:1167
#1 0x000055555558afa1 in RVALUE_OLD_P (obj=0) at gc.c:1174
#2 0x0000555555594b4b in rb_gc_writebarrier_remember (obj=0) at gc.c:6036
#3 0x00007ffff57cee60 in vm_env_write_slowpath (ep=0x7ffff7ed2138, index=-9, v=8) at ./vm_insnhelper.c:341
#4 0x00007ffff57ceef6 in vm_env_write (ep=0x7ffff7ed2138, index=-9, v=8) at ./vm_insnhelper.c:355
...
#7 0x00005555556ea4fb in vm_exec_core (th=0x555555a8c470, initial=0) at insns.def:886
#8 0x00005555556fa5d1 in vm_exec (th=0x555555a8c470) at vm.c:1800
#9 0x00005555556fb220 in rb_iseq_eval (iseq=0x555555c785d0) at vm.c:2037
#10 0x00005555555c7b62 in rb_load_internal0 (th=0x555555a8c470, fname=93825000925840, wrap=0) at load.c:617
#11 0x00005555555c8a08 in rb_require_internal (fname=93825000926040, safe=0) at load.c:998
#12 0x00005555555c8bdb in rb_require_safe (fname=93825000926360, safe=0) at load.c:1044
#13 0x00005555555c8146 in rb_f_require (obj=93824998105800, fname=93825000926360) at load.c:826
#14 0x00005555556e27db in call_cfunc_1 (func=0x5555555c8121 <rb_f_require>, recv=93824998105800, argc=1, argv=0x7ffff7ed20a8)
at vm_insnhelper.c:1769
#15 0x00005555556e3283 in vm_call_cfunc_with_frame (th=0x555555a8c470, reg_cfp=0x7ffff7fd1f80, calling=0x7fffffffd3a0, ci=0x555555e198d0,
cc=0x555555e19c40) at vm_insnhelper.c:1954
#16 0x00005555556e33cd in vm_call_cfunc (th=0x555555a8c470, reg_cfp=0x7ffff7fd1f80, calling=0x7fffffffd3a0, ci=0x555555e198d0,
cc=0x555555e19c40) at vm_insnhelper.c:1970
#17 0x00005555556ea4db in vm_exec_core (th=0x555555a8c470, initial=0) at insns.def:886
#18 0x00005555556fa5d1 in vm_exec (th=0x555555a8c470) at vm.c:1800
#19 0x00005555556fb260 in rb_iseq_eval_main (iseq=0x555555bebb08) at vm.c:2048
#20 0x000055555557a7fb in ruby_exec_internal (n=0x555555bebb08) at eval.c:246
#21 0x000055555557a921 in ruby_exec_node (n=0x555555bebb08) at eval.c:310
#22 0x000055555557a8f4 in ruby_run_node (n=0x555555bebb08) at eval.c:302
#23 0x0000555555577f5f in main (argc=6, argv=0x7fffffffdbc8) at ./main.c:42
vm_exec_coreの中でいくらでも積まれうるので何個積まれてるのかはこれだけではよくわからないが、とりあえずvm_push_frame
への到達方法が少なくとも以下の種類存在しているように見える
rb_iseq_eval_main
VALUE
rb_iseq_eval_main(const rb_iseq_t *iseq)
{
rb_thread_t *th = GET_THREAD();
VALUE val;
vm_set_main_stack(th, iseq);
val = vm_exec(th);
return val;
}
static void
vm_set_main_stack(rb_thread_t *th, const rb_iseq_t *iseq)
{
VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
rb_binding_t *bind;
GetBindingPtr(toplevel_binding, bind);
RUBY_ASSERT_MESG(bind, "TOPLEVEL_BINDING is not built");
vm_set_eval_stack(th, iseq, 0, &bind->block);
/* save binding */
if (iseq->body->local_table_size > 0) {
vm_bind_update_env(toplevel_binding, bind, vm_make_env_object(th, th->ec.cfp));
}
}
static void
vm_set_eval_stack(rb_thread_t * th, const rb_iseq_t *iseq, const rb_cref_t *cref, const struct rb_block *base_block)
{
vm_push_frame(th, iseq, VM_FRAME_MAGIC_EVAL | VM_FRAME_FLAG_FINISH,
vm_block_self(base_block), VM_GUARDED_PREV_EP(vm_block_ep(base_block)),
(VALUE)cref, /* cref or me */
iseq->body->iseq_encoded,
th->ec.cfp->sp, iseq->body->local_table_size,
iseq->body->stack_max);
}
rb_iseq_eval
VALUE
rb_iseq_eval(const rb_iseq_t *iseq)
{
rb_thread_t *th = GET_THREAD();
VALUE val;
vm_set_top_stack(th, iseq);
val = vm_exec(th);
return val;
}
static void
vm_set_top_stack(rb_thread_t *th, const rb_iseq_t *iseq)
{
if (iseq->body->type != ISEQ_TYPE_TOP) {
rb_raise(rb_eTypeError, "Not a toplevel InstructionSequence");
}
/* for return */
vm_push_frame(th, iseq, VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH, th->top_self,
VM_BLOCK_HANDLER_NONE,
(VALUE)vm_cref_new_toplevel(th), /* cref or me */
iseq->body->iseq_encoded, th->ec.cfp->sp,
iseq->body->local_table_size, iseq->body->stack_max);
}
CALL_FUNC in vm_exec_core
vm_call_iseq_setup_normal
static VALUE
vm_call_general(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
return vm_call_method(th, reg_cfp, calling, ci, cc);
}
/* 中略 */
static VALUE
vm_call_method_each_type(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
switch (cc->me->def->type) {
case VM_METHOD_TYPE_ISEQ:
CI_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);
return vm_call_iseq_setup(th, cfp, calling, ci, cc);
/* 略 */
}
}
static inline VALUE
vm_call_iseq_setup_normal(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
int opt_pc, int param_size, int local_size)
{
const rb_callable_method_entry_t *me = cc->me;
const rb_iseq_t *iseq = def_iseq_ptr(me->def);
VALUE *argv = cfp->sp - calling->argc;
VALUE *sp = argv + param_size;
cfp->sp = argv - 1 /* recv */;
vm_push_frame(th, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
calling->block_handler, (VALUE)me,
iseq->body->iseq_encoded + opt_pc, sp,
local_size - param_size,
iseq->body->stack_max);
return Qundef;
}
vm_call_cfunc_with_frame
/* 前略 */
static VALUE
vm_call_method_each_type(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
switch (cc->me->def->type) {
case VM_METHOD_TYPE_ISEQ:
CI_SET_FASTPATH(cc, vm_call_iseq_setup, TRUE);
return vm_call_iseq_setup(th, cfp, calling, ci, cc);
case VM_METHOD_TYPE_NOTIMPLEMENTED:
case VM_METHOD_TYPE_CFUNC:
CI_SET_FASTPATH(cc, vm_call_cfunc, TRUE);
return vm_call_cfunc(th, cfp, calling, ci, cc);
/* 略 */
}
}
static VALUE
vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
CALLER_SETUP_ARG(reg_cfp, calling, ci);
return vm_call_cfunc_with_frame(th, reg_cfp, calling, ci, cc);
}
static VALUE
vm_call_cfunc_with_frame(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
{
VALUE val;
const rb_callable_method_entry_t *me = cc->me;
const rb_method_cfunc_t *cfunc = vm_method_cfunc_entry(me);
int len = cfunc->argc;
VALUE recv = calling->recv;
VALUE block_handler = calling->block_handler;
int argc = calling->argc;
RUBY_DTRACE_CMETHOD_ENTRY_HOOK(th, me->owner, me->def->original_id);
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->def->original_id, ci->mid, me->owner, Qundef);
vm_push_frame(th, NULL, VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL, recv,
block_handler, (VALUE)me,
0, th->ec.cfp->sp, 0, 0);
if (len >= 0) rb_check_arity(argc, len, len);
reg_cfp->sp -= argc + 1;
VM_PROFILE_UP(R2C_CALL);
val = (*cfunc->invoker)(cfunc->func, recv, argc, reg_cfp->sp + 1);
CHECK_CFP_CONSISTENCY("vm_call_cfunc");
rb_vm_pop_frame(th);
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->def->original_id, ci->mid, me->owner, val);
RUBY_DTRACE_CMETHOD_RETURN_HOOK(th, me->owner, me->def->original_id);
return val;
}
vm_push_frameはどのようにepを用意するか
static rb_control_frame_t *
vm_push_frame(rb_thread_t *th,
const rb_iseq_t *iseq,
VALUE type,
VALUE self,
VALUE specval,
VALUE cref_or_me,
const VALUE *pc,
VALUE *sp,
int local_size,
int stack_max)
{
return vm_push_frame_(&th->ec, iseq, type, self, specval, cref_or_me, pc, sp, local_size, stack_max);
}
static inline rb_control_frame_t *
vm_push_frame_(rb_execution_context_t *ec,
const rb_iseq_t *iseq,
VALUE type,
VALUE self,
VALUE specval,
VALUE cref_or_me,
const VALUE *pc,
VALUE *sp,
int local_size,
int stack_max)
{
rb_control_frame_t *const cfp = ec->cfp - 1;
int i;
vm_check_frame(type, specval, cref_or_me, iseq);
VM_ASSERT(local_size >= 0);
/* check stack overflow */
CHECK_VM_STACK_OVERFLOW0(cfp, sp, local_size + stack_max);
ec->cfp = cfp;
/* setup new frame */
cfp->pc = (VALUE *)pc;
cfp->iseq = (rb_iseq_t *)iseq;
cfp->self = self;
cfp->block_code = NULL;
/* setup vm value stack */
/* initialize local variables */
for (i=0; i < local_size; i++) {
*sp++ = Qnil;
}
/* setup ep with managing data */
VM_ASSERT(VM_ENV_DATA_INDEX_ME_CREF == -2);
VM_ASSERT(VM_ENV_DATA_INDEX_SPECVAL == -1);
VM_ASSERT(VM_ENV_DATA_INDEX_FLAGS == -0);
*sp++ = cref_or_me; /* ep[-2] / Qnil or T_IMEMO(cref) or T_IMEMO(ment) */
*sp++ = specval /* ep[-1] / block handler or prev env ptr */;
*sp = type; /* ep[-0] / ENV_FLAGS */
cfp->ep = sp;
cfp->sp = sp + 1;
#if VM_DEBUG_BP_CHECK
cfp->bp_check = sp + 1;
#endif
if (VMDEBUG == 2) {
SDR();
}
return cfp;
}
そういえばruby under a microscopeにも書いてあったけどepは最初sp-1なのだった。
ep[-2]
(VM_ENV_DATA_INDEX_ME_CREF
) が作られる場所
*sp++ = cref_or_me; /* ep[-2] / Qnil or T_IMEMO(cref) or T_IMEMO(ment) */
vm_push_frame
の第6引数がそのまま入る。先ほどの4つのケースではそれぞれ、
- rb_iseq_eval_main
-
(VALUE)cref
→0
-
- rb_iseq_eval
(VALUE)vm_cref_new_toplevel(th)
- vm_call_iseq_setup_normal
-
(VALUE)me
→cc->me
-
- vm_call_cfunc_with_frame
-
(VALUE)me
→cc->me
-
うーん
vm_call_cfunc_with_frame
直後のec->cfp->ep[-2]
は明らかにcc->me
になる。
従って、vm_call_cfunc_with_frame
で呼び出されている関数がth->ec.cfp->ep[-2]
と参照してきて0になってるとしたら、まあvm_search_method
後にcc->me
が0だったという話になりそう。
…だがgdbでフレームを辿って値を見てみるとそうではない。うーん。わからない。
というか、ruby_current_thread->ec.cfp->ep[-2]
とかやると普通に値が入っている。
というところで、最初はVM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))
がcfp->ep[-2]
のことだと勝手に勘違いしていたがそうでないことに気付いた。
まあとりあえずep[-2]
の雑な理解としてはmethod entryかcref(class reference)のどちらかが入っている
VM_ENV_PREV_EPとは
static inline const VALUE *
VM_ENV_PREV_EP(const VALUE *ep)
{
VM_ASSERT(VM_ENV_LOCAL_P(ep) == 0);
return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
}
#define GC_GUARDED_PTR_REF(p) VM_TAGGED_PTR_REF((p), 0x03)
#define VM_TAGGED_PTR_REF(v, mask) ((void *)((v) & ~mask))
#define VM_ENV_DATA_INDEX_SPECVAL (-1) /* ep[-1] */
~
はNOTなので& ~0x03
をやると0100
のビットが落ちることになる。まあポインタだと立ってない場所なので、何かのフラグが入ってたら打ち消す処理であり、とりあえず無視してよい。
ので、VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))
は雑に見るとep[-1][-1]
ということだ。ep[-2]
ではないのは冷静に見たら当たり前だけど。
落ちてるケースのepの置き場を再確認
まあそれは置いておいて、落ちるケースにおいて、以下のような順序で参照された場合に0になることがわかっている。
(gdb) p ruby_current_thread->ec.cfp->ep
$8 = (const VALUE *) 0x7ffff7ed2448
(gdb) p vm_normal_frame(ruby_current_thread, ruby_current_thread->ec.cfp->ep)->ep
$9 = (const VALUE *) 0x555555d5a4f8
(gdb) p VM_ENV_PREV_EP(vm_normal_frame(ruby_current_thread, ruby_current_thread->ec.cfp->ep)->ep)
$10 = (const VALUE *) 0x0
(gdb) p VM_ENV_PREV_EP(ruby_current_thread->ec.cfp->ep)
$11 = (const VALUE *) 0x0
よく見るとこのケースでは VM_ENV_PREV_EP(VM_ENV_PREV_EP(cfp->ep))
ですらなくVM_ENV_PREV_EP(cfp->ep)
が0である。まあ既にbtを見た時からやり直してるので再現条件が複数あったのかもしれないが。というわけでep[-1]
の方を見ていく
ep[-1]
(VM_ENV_DATA_INDEX_SPECVAL
) が作られる場所
ruby under a microscopeもチラ見しながらもうなんか割とep[-1]
の中身がわかってきた。ep[-1]
には多分一つ上のスコープのep
のアドレスが入っていて、ep[-1][-1][-1]...
とやっていくと上の階層に辿れるのだろう。
というわけで、以下の行に何が入るか、とりあえず先ほどの4つのケースを見ていく。
*sp++ = specval /* ep[-1] / block handler or prev env ptr */;
vm_push_frameの第5引数。
- rb_iseq_eval_main
VM_GUARDED_PREV_EP(vm_block_ep(base_block))
- rb_iseq_eval
-
VM_BLOCK_HANDLER_NONE
→0
-
- vm_call_iseq_setup_normal
-
(VALUE)me
→calling->block_handler
→ ブロックがない場合VM_BLOCK_HANDLER_NONE
→0
-
- vm_call_cfunc_with_frame
-
(VALUE)me
→calling->block_handler
→ ブロックがない場合VM_BLOCK_HANDLER_NONE
→0
-
うーん
ていうかblock handlerの場合ってどう使われるんだろう…
… vm_invoke_block の時にVM_CF_BLOCK_HANDLER(reg_cfp)
でVM_EP_LEP(cfp->ep)[-1]
してblock handlerを参照し、VM_TAGGED_PTR_REF(block_handler, 0x03)
をすることによって以下のrb_captured_block
とかに変換されるっぽい。
struct rb_captured_block {
VALUE self;
const VALUE *ep;
union {
const rb_iseq_t *iseq;
const struct vm_ifunc *ifunc;
VALUE val;
} code;
};
vm_invoke_iseq_block
はこのiseqを取り出してvm_push_frameしているので、まあそう使われることがわかる。
ここにもepが入ってるから、前のepへの参照がなくとも、ブロックがepを辿りたい時はVM_TAGGED_PTR_REF(block_handler, 0x03)->ep[-1]
とかでいけそう。
どのような時にVM_EP_LEP
でep
が0になってしまうか
まず、VM_EP_LEP
の定義はこう
static inline const VALUE *
VM_EP_LEP(const VALUE *ep)
{
while (!VM_ENV_LOCAL_P(ep)) {
ep = VM_ENV_PREV_EP(ep);
}
return ep;
}
static inline const VALUE *
VM_ENV_PREV_EP(const VALUE *ep)
{
VM_ASSERT(VM_ENV_LOCAL_P(ep) == 0);
return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
}
static inline int
VM_ENV_LOCAL_P(const VALUE *ep)
{
return VM_ENV_FLAGS(ep, VM_ENV_FLAG_LOCAL) ? 1 : 0;
}
static inline unsigned long
VM_ENV_FLAGS(const VALUE *ep, long flag)
{
VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS];
VM_ASSERT(FIXNUM_P(flags));
return flags & flag;
}
#define VM_ENV_DATA_INDEX_SPECVAL (-1) /* ep[-1] */
#define VM_ENV_DATA_INDEX_FLAGS ( 0) /* ep[ 0] */
VM_ENV_FLAG_LOCAL = 0x0002,
まあそもそもopt_send_without_block
とかで普通にメソッドを呼び出したらそのフレームのep[-1]
はVM_BLOCK_HANDLER_NONE
で0になってしまう。
なので、普通にメソッドを呼び出した時、というのはep[0]
で0x0002のビットが立ってないとまずVM_EP_LEP
から値は返らない。
というわけなので、ep[0]
がどうセットされるかを見ることにする
ep[0]
(VM_ENV_DATA_INDEX_FLAGS
) が作られる場所
名前からして、epのこの場所に何らかのフラグが一杯入るっぽい。フラグに使われそうなenumのリストはこれ
enum {
/* Frame/Environment flag bits:
* MMMM MMMM MMMM MMMM ____ __FF FFFF EEEX (LSB)
*
* X : tag for GC marking (It seems as Fixnum)
* EEE : 3 bits Env flags
* FF..: 6 bits Frame flags
* MM..: 16 bits frame magic (to check frame corruption)
*/
/* frame types */
VM_FRAME_MAGIC_METHOD = 0x11110001,
VM_FRAME_MAGIC_BLOCK = 0x22220001,
VM_FRAME_MAGIC_CLASS = 0x33330001,
VM_FRAME_MAGIC_TOP = 0x44440001,
VM_FRAME_MAGIC_CFUNC = 0x55550001,
VM_FRAME_MAGIC_IFUNC = 0x66660001,
VM_FRAME_MAGIC_EVAL = 0x77770001,
VM_FRAME_MAGIC_RESCUE = 0x88880001,
VM_FRAME_MAGIC_DUMMY = 0x99990001,
VM_FRAME_MAGIC_MASK = 0xffff0001,
/* frame flag */
VM_FRAME_FLAG_PASSED = 0x0010,
VM_FRAME_FLAG_FINISH = 0x0020,
VM_FRAME_FLAG_BMETHOD = 0x0040,
VM_FRAME_FLAG_CFRAME = 0x0080,
VM_FRAME_FLAG_LAMBDA = 0x0100,
/* env flag */
VM_ENV_FLAG_LOCAL = 0x0002,
VM_ENV_FLAG_ESCAPED = 0x0004,
VM_ENV_FLAG_WB_REQUIRED = 0x0008
};
で、vm_push_frameでは、
*sp = type; /* ep[-0] / ENV_FLAGS */
第3引数のtypeがそのまま入る。vm_push_frame
で値がチェックされるっぽいので後で見る
実際にvm_push_frameをさきほどの4つのケースでおいかけていく。
- rb_iseq_eval_main
VM_FRAME_MAGIC_EVAL | VM_FRAME_FLAG_FINISH
- rb_iseq_eval
VM_FRAME_MAGIC_TOP | VM_ENV_FLAG_LOCAL | VM_FRAME_FLAG_FINISH
- vm_call_iseq_setup_normal
VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL
- vm_call_cfunc_with_frame
VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL
今回は値の完全な式が書いてあってわかりやすい。
CALL_METHOD
から呼ばれているケースではいずれもVM_ENV_FLAG_LOCAL
が入っている。あとLEPと呼ばれているのはep[0]
にVM_ENV_FLAG_LOCAL
が立ってるepのことだろう。
で、CALL_METHODで積まれるcfpのepには両方VM_ENV_FLAG_LOCAL
が入ってるので、メソッド呼び出し直後のth->ec.cfp->ep[0]
は0x0002が立ってないと何かおかしい。
結局何がおかしいのか
ruby_current_thread->ec.cfp->ep[0]
を見てみたら1010101010101010000000010000011
になっていた。なので0x0002は立っている。
一方でVM_CF_LEP
の中から呼ばれるVM_EP_LEP
の引数のepは0になってしまっていた。
で、そもそもVM_CF_LEP
の引数のcfpとruby_current_thread->ec.cfp
が違っている。
これはvm_cfp_svar_get
が(GET_THREAD()
してきたthとそのcfpから)vm_normal_frame(th, cfp)
したものをVM_CF_LEP
に渡しているからだ。
すると、メソッド呼び出しで積まれたcfpからvm_normal_frame(th, cfp)
を参照した時、その結果のcfpとそのepはどこで作られているのかを知る必要がある。
vm_normal_frameとは
割と疲れてきた
static rb_control_frame_t *
vm_normal_frame(rb_thread_t *th, rb_control_frame_t *cfp)
{
while (cfp->pc == 0) {
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
if (RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
return 0;
}
}
return cfp;
}
#define RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp) ((cfp)+1)
#define RUBY_VM_END_CONTROL_FRAME(th) \
((rb_control_frame_t *)((th)->ec.vm_stack + (th)->ec.vm_stack_size))
#define RUBY_VM_VALID_CONTROL_FRAME_P(cfp, ecfp) \
((void *)(ecfp) > (void *)(cfp))
#define RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp) \
(!RUBY_VM_VALID_CONTROL_FRAME_P((cfp), RUBY_VM_END_CONTROL_FRAME(th)))
えっ
pcが0のcfpはひたすらスキップされるようだ。c関数なメソッドの場合はpcもクソもなさそうなので、まあRubyで書かれてるフレームまで遡るのだろうか
一応確認しておくか…
static inline rb_control_frame_t *
vm_push_frame_(rb_execution_context_t *ec,
const rb_iseq_t *iseq,
VALUE type,
VALUE self,
VALUE specval,
VALUE cref_or_me,
const VALUE *pc,
VALUE *sp,
int local_size,
int stack_max)
{
rb_control_frame_t *const cfp = ec->cfp - 1;
int i;
vm_check_frame(type, specval, cref_or_me, iseq);
VM_ASSERT(local_size >= 0);
/* check stack overflow */
CHECK_VM_STACK_OVERFLOW0(cfp, sp, local_size + stack_max);
ec->cfp = cfp;
/* setup new frame */
cfp->pc = (VALUE *)pc;
/* ... */
}
static rb_control_frame_t *
vm_push_frame(rb_thread_t *th,
const rb_iseq_t *iseq,
VALUE type,
VALUE self,
VALUE specval,
VALUE cref_or_me,
const VALUE *pc,
VALUE *sp,
int local_size,
int stack_max)
{
return vm_push_frame_(&th->ec, iseq, type, self, specval, cref_or_me, pc, sp, local_size, stack_max);
}
第7引数がそのまま入る。
- rb_iseq_eval_main
iseq->body->iseq_encoded
- rb_iseq_eval
iseq->body->iseq_encoded
- vm_call_iseq_setup_normal
iseq->body->iseq_encoded + opt_pc
- vm_call_cfunc_with_frame
0
はい。まあそりゃそうですわ
なので、まあバックトレースを見て、一番近いRubyで実装されてるっぽいフレームのcfp->pc
が0でなかったりcfp->ep
で0x0002が立ってるかどうかを見るとよさそう。
でまあrb_backtrace
を見ると一個上はRubyのメソッドだったので、*((ruby_current_thread->ec.cfp)+1)
とかを見てみるとpcはゼロではない。rb_p ((ruby_current_thread->ec.cfp)+1)->iseq->body->location.label
とかやるとメソッド名も確認できる。(rb_p rb_iseq_path((ruby_current_thread->ec.cfp)+1)->iseq)
でパスもわかる)
で、((ruby_current_thread->ec.cfp)+1)->ep[0]
を見てみると10101010101010101010101101011010111000001010000
だった。CALL_METHOD
で積まれたのであればこれがおかしい。
フム
frame magicのビットパターン
一応フレームが何なのか調べる時に使えそうなのでメモっておく
定数名 | 16進数 | 2進数 |
---|---|---|
VM_FRAME_MAGIC_METHOD | 0x11110001 | 00010001000100010000000000000001 |
VM_FRAME_MAGIC_BLOCK | 0x22220001 | 00100010001000100000000000000001 |
VM_FRAME_MAGIC_CLASS | 0x33330001 | 00110011001100110000000000000001 |
VM_FRAME_MAGIC_TOP | 0x44440001 | 01000100010001000000000000000001 |
VM_FRAME_MAGIC_CFUNC | 0x55550001 | 01010101010101010000000000000001 |
VM_FRAME_MAGIC_IFUNC | 0x66660001 | 01100110011001100000000000000001 |
VM_FRAME_MAGIC_EVAL | 0x77770001 | 01110111011101110000000000000001 |
VM_FRAME_MAGIC_RESCUE | 0x88880001 | 10001000100010000000000000000001 |
VM_FRAME_MAGIC_DUMMY | 0x99990001 | 10011001100110010000000000000001 |
VM_FRAME_MAGIC_MASK | 0xffff0001 | 11111111111111110000000000000001 |
さっきの奴をマスク取ってみるとこう: 1010101101011010000000000000000
なんか壊れてるっぽい感…
まあこのフレームが積まれる過程をデバッガで追いかけてみます。記事に残しておく意味がありそうなのはここまでだと思うのでこのへんで