diff options
Diffstat (limited to 'vm_dump.c')
| -rw-r--r-- | vm_dump.c | 354 |
1 files changed, 328 insertions, 26 deletions
@@ -37,6 +37,7 @@ #include "iseq.h" #include "vm_core.h" #include "ractor_core.h" +#include "zjit.h" #define MAX_POSBUF 128 @@ -61,28 +62,37 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c const char *magic, *iseq_name = "-", *selfstr = "-", *biseq_name = "-"; VALUE tmp; const rb_iseq_t *iseq = NULL; - const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp); + const rb_callable_method_entry_t *me = rb_vm_frame_method_entry_unchecked(cfp); + const rb_box_t *box = NULL; if (ep < 0 || (size_t)ep > ec->vm_stack_size) { ep = (ptrdiff_t)cfp->ep; ep_in_heap = 'p'; } - switch (VM_FRAME_TYPE(cfp)) { + switch (VM_FRAME_TYPE_UNCHECKED(cfp)) { case VM_FRAME_MAGIC_TOP: magic = "TOP"; + box = VM_ENV_BOX_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_METHOD: magic = "METHOD"; + if (me) { + box = me->def->box; + } break; case VM_FRAME_MAGIC_CLASS: magic = "CLASS"; + box = VM_ENV_BOX_UNCHECKED(cfp->ep); break; case VM_FRAME_MAGIC_BLOCK: magic = "BLOCK"; break; case VM_FRAME_MAGIC_CFUNC: magic = "CFUNC"; + if (me) { + box = me->def->box; + } break; case VM_FRAME_MAGIC_IFUNC: magic = "IFUNC"; @@ -112,23 +122,25 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c selfstr = ""; } - if (cfp->iseq != 0) { + if (CFP_ISEQ(cfp)) { + iseq = CFP_ISEQ(cfp); #define RUBY_VM_IFUNC_P(ptr) IMEMO_TYPE_P(ptr, imemo_ifunc) - if (RUBY_VM_IFUNC_P(cfp->iseq)) { + if (RUBY_VM_IFUNC_P(iseq)) { iseq_name = "<ifunc>"; } - else if (SYMBOL_P((VALUE)cfp->iseq)) { - tmp = rb_sym2str((VALUE)cfp->iseq); + else if (SYMBOL_P((VALUE)iseq)) { + tmp = rb_sym2str((VALUE)iseq); iseq_name = RSTRING_PTR(tmp); snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); line = -1; } else { - if (cfp->pc) { - iseq = cfp->iseq; - pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; + if (CFP_PC(cfp)) { + pc = CFP_PC(cfp) - ISEQ_BODY(iseq)->iseq_encoded; iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); - line = rb_vm_get_sourceline(cfp); + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { + line = rb_vm_get_sourceline(cfp); + } if (line) { snprintf(posbuf, MAX_POSBUF, "%s:%d", RSTRING_PTR(rb_iseq_path(iseq)), line); } @@ -138,7 +150,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c } } } - else if (me != NULL) { + else if (me != NULL && IMEMO_TYPE_P(me, imemo_ment)) { iseq_name = rb_id2name(me->def->original_id); snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); line = -1; @@ -154,11 +166,19 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c } kprintf("s:%04"PRIdPTRDIFF" ", cfp->sp - ec->vm_stack); kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); + kprintf("l:%s ", VM_ENV_LOCAL_P(cfp->ep) ? "y" : "n"); + if (box) { + kprintf("b:%04ld ", box->box_id % 10000); + } + else { + kprintf("b:---- "); + } + kprintf("r:%p ", cfp->jit_return); kprintf("%-6s", magic); if (line) { kprintf(" %s", posbuf); } - if (VM_FRAME_FINISHED_P(cfp)) { + if (VM_FRAME_FINISHED_P_UNCHECKED(cfp)) { kprintf(" [FINISH]"); } if (0) { @@ -193,7 +213,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c if (ISEQ_BODY(iseq)->local_table_size > 0) { kprintf(" lvars:\n"); for (unsigned int i=0; i<ISEQ_BODY(iseq)->local_table_size; i++) { - const VALUE *argv = cfp->ep - ISEQ_BODY(cfp->iseq)->local_table_size - VM_ENV_DATA_SIZE + 1; + const VALUE *argv = cfp->ep - ISEQ_BODY(CFP_ISEQ(cfp))->local_table_size - VM_ENV_DATA_SIZE + 1; kprintf(" %s: %s\n", rb_id2name(ISEQ_BODY(iseq)->local_table[i]), rb_raw_obj_info(buff, 0x100, argv[i])); @@ -206,6 +226,251 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c return false; } +static inline const rb_control_frame_t * +vmdebug_search_cf_from_ep(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, const VALUE * const ep) +{ + if (!ep) { + return NULL; + } + else { + const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ + + while (cfp < eocfp) { + if (cfp->ep == ep) { + return cfp; + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + + return NULL; + } +} + +static rb_callable_method_entry_t * +vmdebug_env_method_entry_unchecked(VALUE obj, int can_be_svar) +{ + if (obj == Qfalse) return NULL; + + switch (imemo_type(obj)) { + case imemo_ment: + return (rb_callable_method_entry_t *)obj; + case imemo_cref: + return NULL; + case imemo_svar: + if (can_be_svar) { + return vmdebug_env_method_entry_unchecked(((struct vm_svar *)obj)->cref_or_me, FALSE); + } + default: + return NULL; + } +} + +static const rb_callable_method_entry_t * +vmdebug_frame_method_entry_unchecked(const VALUE *ep) +{ + rb_callable_method_entry_t *me; + + while (!VM_ENV_LOCAL_P_UNCHECKED(ep)) { + if ((me = vmdebug_env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) return me; + ep = VM_ENV_PREV_EP_UNCHECKED(ep); + } + + return vmdebug_env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); +} + +static bool +box_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +{ + ptrdiff_t pc = -1; + ptrdiff_t ep = env - ec->vm_stack; + char ep_in_heap = ' '; + char posbuf[MAX_POSBUF+1]; + int line = 0; + const char *magic, *iseq_name = "-"; + VALUE tmp; + const rb_iseq_t *iseq = NULL; + const rb_box_t *box = NULL; + const rb_control_frame_t *cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, env); + const rb_callable_method_entry_t *me = vmdebug_frame_method_entry_unchecked(env); + + if (ep < 0 || (size_t)ep > ec->vm_stack_size) { + if (cfp) { + ep = (ptrdiff_t)cfp->ep; + ep_in_heap = 'p'; + } + } + + switch (VM_ENV_FLAGS_UNCHECKED(env, VM_FRAME_MAGIC_MASK)) { + case VM_FRAME_MAGIC_TOP: + magic = "TOP"; + box = VM_ENV_BOX_UNCHECKED(env); + break; + case VM_FRAME_MAGIC_METHOD: + magic = "METHOD"; + if (me) { + box = me->def->box; + } + break; + case VM_FRAME_MAGIC_CLASS: + magic = "CLASS"; + box = VM_ENV_BOX_UNCHECKED(env); + break; + case VM_FRAME_MAGIC_BLOCK: + magic = "BLOCK"; + break; + case VM_FRAME_MAGIC_CFUNC: + magic = "CFUNC"; + if (me) { + box = me->def->box; + } + break; + case VM_FRAME_MAGIC_IFUNC: + magic = "IFUNC"; + break; + case VM_FRAME_MAGIC_EVAL: + magic = "EVAL"; + break; + case VM_FRAME_MAGIC_RESCUE: + magic = "RESCUE"; + break; + case VM_FRAME_MAGIC_DUMMY: + magic = "DUMMY"; + break; + case 0: + magic = "------"; + break; + default: + magic = "(none)"; + break; + } + + if (cfp && CFP_ISEQ(cfp)) { +#define RUBY_VM_IFUNC_P(ptr) IMEMO_TYPE_P(ptr, imemo_ifunc) + const rb_iseq_t *resolved_iseq = CFP_ISEQ(cfp); + if (RUBY_VM_IFUNC_P(resolved_iseq)) { + iseq_name = "<ifunc>"; + } + else if (SYMBOL_P((VALUE)resolved_iseq)) { + tmp = rb_sym2str((VALUE)resolved_iseq); + iseq_name = RSTRING_PTR(tmp); + snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); + line = -1; + } + else { + if (CFP_PC(cfp)) { + iseq = resolved_iseq; + pc = CFP_PC(cfp) - ISEQ_BODY(iseq)->iseq_encoded; + iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { + line = rb_vm_get_sourceline(cfp); + } + if (line) { + snprintf(posbuf, MAX_POSBUF, "%s:%d", RSTRING_PTR(rb_iseq_path(iseq)), line); + } + } + else { + iseq_name = "<dummy_frame>"; + } + } + } + else if (me != NULL && IMEMO_TYPE_P(me, imemo_ment)) { + iseq_name = rb_id2name(me->def->original_id); + snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name); + line = -1; + } + + if (cfp) { + kprintf("c:%04"PRIdPTRDIFF" ", + ((rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size) - cfp)); + } + else { + kprintf("c:---- "); + } + kprintf(ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); + kprintf("l:%s ", VM_ENV_LOCAL_P(env) ? "y" : "n"); + if (box) { + kprintf("b:%04ld ", box->box_id % 10000); + } + else { + kprintf("b:---- "); + } + kprintf("%-6s", magic); + if (line) { + kprintf(" %s", posbuf); + } + if (VM_ENV_FLAGS_UNCHECKED(env, VM_FRAME_FLAG_FINISH) != 0) { + kprintf(" [FINISH]"); + } + kprintf("\n"); + return true; + error: + return false; +} + +static bool +box_env_dump_unchecked(const rb_execution_context_t *ec, const VALUE *env, const rb_control_frame_t *checkpoint_cfp, FILE *errout) +{ + if (env == NULL) { + kprintf("c:---- e:000000 l:- b:---- (none)\n"); + return true; + } + else { + return box_env_dump(ec, env, checkpoint_cfp, errout); + } + error: + return false; +} + +bool +rb_vmdebug_box_env_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *current_cfp, FILE *errout) +{ + // See VM_EP_RUBY_LEP for the original logic + const VALUE *ep = current_cfp->ep; + const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ + const rb_control_frame_t *cfp = current_cfp, *checkpoint_cfp = current_cfp; + + kprintf("-- Ruby Box detection information " + "-----------------------------------------\n"); + + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + } + goto stop; + } + + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + if (!cfp) { + goto stop; + } + if (cfp >= eocfp) { + kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); + goto stop; + } + ep = cfp->ep; + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + if (!ep) { + goto stop; + } + } + + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + box_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + } + + stop: + kprintf("\n"); + return true; + + error: + return false; +} + bool rb_vmdebug_stack_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, FILE *errout) { @@ -305,9 +570,9 @@ static const VALUE * vm_base_ptr(const rb_control_frame_t *cfp) { const rb_control_frame_t *prev_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - const VALUE *bp = prev_cfp->sp + ISEQ_BODY(cfp->iseq)->local_table_size + VM_ENV_DATA_SIZE; + const VALUE *bp = prev_cfp->sp + ISEQ_BODY(CFP_ISEQ(cfp))->local_table_size + VM_ENV_DATA_SIZE; - if (ISEQ_BODY(cfp->iseq)->type == ISEQ_TYPE_METHOD || VM_FRAME_BMETHOD_P(cfp)) { + if (ISEQ_BODY(CFP_ISEQ(cfp))->type == ISEQ_TYPE_METHOD || VM_FRAME_BMETHOD_P(cfp)) { bp += 1; } return bp; @@ -322,7 +587,7 @@ vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *c const VALUE *ep = cfp->ep; if (VM_FRAME_RUBYFRAME_P(cfp)) { - const rb_iseq_t *iseq = cfp->iseq; + const rb_iseq_t *iseq = CFP_ISEQ(cfp); argc = ISEQ_BODY(iseq)->param.lead_num; local_table_size = ISEQ_BODY(iseq)->local_table_size; } @@ -393,7 +658,7 @@ rb_vmdebug_debug_print_register(const rb_execution_context_t *ec, FILE *errout) ptrdiff_t cfpi; if (VM_FRAME_RUBYFRAME_P(cfp)) { - pc = cfp->pc - ISEQ_BODY(cfp->iseq)->iseq_encoded; + pc = cfp->pc - ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded; } if (ep < 0 || (size_t)ep > ec->vm_stack_size) { @@ -418,7 +683,7 @@ rb_vmdebug_thread_dump_regs(VALUE thval, FILE *errout) bool rb_vmdebug_debug_print_pre(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, const VALUE *_pc, FILE *errout) { - const rb_iseq_t *iseq = cfp->iseq; + const rb_iseq_t *iseq = CFP_ISEQ(cfp); if (iseq != 0) { ptrdiff_t pc = _pc - ISEQ_BODY(iseq)->iseq_encoded; @@ -511,13 +776,17 @@ rb_vmdebug_thread_dump_state(FILE *errout, VALUE self) # include <sys/mman.h> # undef backtrace -# if defined(__arm64__) +# if defined(__arm64__) || defined(__POWERPC__) static bool is_coroutine_start(unw_word_t ip) { +#if defined(USE_MN_THREADS) && USE_MN_THREADS struct coroutine_context; extern void ruby_coroutine_start(struct coroutine_context *, struct coroutine_context *); return ((void *)(ip) == (void *)ruby_coroutine_start); +#else + return false; +#endif } # endif @@ -616,19 +885,22 @@ darwin_sigtramp: } return n; -# elif defined(__arm64__) +# elif defined(__arm64__) || defined(__POWERPC__) /* Since Darwin arm64's _sigtramp is implemented as normal function, * unwind can unwind frames without special code. * https://github.com/apple/darwin-libplatform/blob/215b09856ab5765b7462a91be7076183076600df/src/setjmp/generic/sigtramp.c */ while (unw_step(&cursor) > 0) { unw_get_reg(&cursor, UNW_REG_IP, &ip); +# if defined(__arm64__) // Strip Arm64's pointer authentication. // https://developer.apple.com/documentation/security/preparing_your_app_to_work_with_pointer_authentication // I wish I could use "ptrauth_strip()" but I get an error: // "this target does not support pointer authentication" trace[n++] = (void *)(ip & 0x7fffffffffffull); - +# else + trace[n++] = (void *)ip; +# endif // Apple's libunwind can't handle our coroutine switching code if (is_coroutine_start(ip)) break; } @@ -1114,6 +1386,7 @@ rb_dump_machine_register(FILE *errout, const ucontext_t *ctx) bool rb_vm_bugreport(const void *ctx, FILE *errout) { + const char *box_env = getenv("RUBY_BUGREPORT_BOX_ENV"); const char *cmd = getenv("RUBY_ON_BUG"); if (cmd) { char buf[0x100]; @@ -1144,10 +1417,22 @@ rb_vm_bugreport(const void *ctx, FILE *errout) enum {other_runtime_info = 0}; #endif const rb_vm_t *const vm = GET_VM(); + const rb_box_t *current_box = rb_current_box_in_crash_report(); const rb_execution_context_t *ec = rb_current_execution_context(false); + VALUE loaded_features; + + if (current_box) { + loaded_features = current_box->loaded_features; + } + else { + loaded_features = rb_root_box()->loaded_features; + } if (vm && ec) { rb_vmdebug_stack_dump_raw(ec, ec->cfp, errout); + if (box_env) { + rb_vmdebug_box_env_dump_raw(ec, ec->cfp, errout); + } rb_backtrace_print_as_bugreport(errout); kputs("\n"); // If we get here, hopefully things are intact enough that @@ -1156,8 +1441,11 @@ rb_vm_bugreport(const void *ctx, FILE *errout) kprintf("-- Threading information " "---------------------------------------------------\n"); kprintf("Total ractor count: %u\n", vm->ractor.cnt); - kprintf("Ruby thread count for this ractor: %u\n", rb_ec_ractor_ptr(ec)->threads.cnt); - if (rb_fiber_scheduler_get() != Qnil) { + const rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + if (cr) { + kprintf("Ruby thread count for this ractor: %u\n", cr->threads.cnt); + } + if (ec->thread_ptr && ec->thread_ptr->scheduler != Qnil) { kprintf("Note that the Fiber scheduler is enabled\n"); } kputs("\n"); @@ -1192,10 +1480,24 @@ rb_vm_bugreport(const void *ctx, FILE *errout) LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); kprintf("\n"); } - if (vm->loaded_features) { + if (rb_box_available()) { + kprintf("* Ruby Box: enabled\n"); + if (current_box) { + kprintf("* Current box id: %ld, type: %s\n", + current_box->box_id, + BOX_USER_P(current_box) ? (BOX_MAIN_P(current_box) ? "main" : "user") : "root"); + } + else { + kprintf("* Current box: NULL (crashed)\n"); + } + } + else { + kprintf("* Ruby Box: disabled\n"); + } + if (loaded_features) { kprintf("* Loaded features:\n\n"); - for (i=0; i<RARRAY_LEN(vm->loaded_features); i++) { - name = RARRAY_AREF(vm->loaded_features, i); + for (i=0; i<RARRAY_LEN(loaded_features); i++) { + name = RARRAY_AREF(loaded_features, i); if (RB_TYPE_P(name, T_STRING)) { kprintf(" %4d %.*s\n", i, LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); |
