LoginSignup
2
0

More than 5 years have passed since last update.

YARVのepはどのように作られるのか

Last updated at Posted at 2017-10-03

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)cref0
  • rb_iseq_eval
    • (VALUE)vm_cref_new_toplevel(th)
  • vm_call_iseq_setup_normal
    • (VALUE)mecc->me
  • vm_call_cfunc_with_frame
    • (VALUE)mecc->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_NONE0
  • vm_call_iseq_setup_normal
    • (VALUE)mecalling->block_handler → ブロックがない場合 VM_BLOCK_HANDLER_NONE0
  • vm_call_cfunc_with_frame
    • (VALUE)mecalling->block_handler → ブロックがない場合 VM_BLOCK_HANDLER_NONE0

うーん

ていうか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_LEPepが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
なんか壊れてるっぽい感…

まあこのフレームが積まれる過程をデバッガで追いかけてみます。記事に残しておく意味がありそうなのはここまでだと思うのでこのへんで

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0