diff options
Diffstat (limited to 'vm_dump.c')
| -rw-r--r-- | vm_dump.c | 781 |
1 files changed, 588 insertions, 193 deletions
@@ -9,6 +9,7 @@ **********************************************************************/ #include "ruby/internal/config.h" +#include "ruby/fiber/scheduler.h" #ifdef HAVE_UCONTEXT_H # include <ucontext.h> @@ -29,8 +30,8 @@ #endif #include "addr2line.h" -#include "gc.h" #include "internal.h" +#include "internal/gc.h" #include "internal/variable.h" #include "internal/vm.h" #include "iseq.h" @@ -46,8 +47,11 @@ const char *rb_method_type_name(rb_method_type_t type); int ruby_on_ci; -static void -control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) +#define kprintf(...) if (fprintf(errout, __VA_ARGS__) < 0) goto error +#define kputs(s) if (fputs(s, errout) < 0) goto error + +static bool +control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, FILE *errout) { ptrdiff_t pc = -1; ptrdiff_t ep = cfp->ep - ec->vm_stack; @@ -57,22 +61,28 @@ 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"; @@ -124,7 +134,9 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c iseq = cfp->iseq; pc = cfp->pc - 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); } @@ -134,36 +146,43 @@ 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; } - fprintf(stderr, "c:%04"PRIdPTRDIFF" ", + kprintf("c:%04"PRIdPTRDIFF" ", ((rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size) - cfp)); if (pc == -1) { - fprintf(stderr, "p:---- "); + kprintf("p:---- "); } else { - fprintf(stderr, "p:%04"PRIdPTRDIFF" ", pc); + kprintf("p:%04"PRIdPTRDIFF" ", pc); + } + 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); } - fprintf(stderr, "s:%04"PRIdPTRDIFF" ", cfp->sp - ec->vm_stack); - fprintf(stderr, ep_in_heap == ' ' ? "e:%06"PRIdPTRDIFF" " : "E:%06"PRIxPTRDIFF" ", ep % 10000); - fprintf(stderr, "%-6s", magic); + else { + kprintf("b:---- "); + } + kprintf("%-6s", magic); if (line) { - fprintf(stderr, " %s", posbuf); + kprintf(" %s", posbuf); } - if (VM_FRAME_FINISHED_P(cfp)) { - fprintf(stderr, " [FINISH]"); + if (VM_FRAME_FINISHED_P_UNCHECKED(cfp)) { + kprintf(" [FINISH]"); } if (0) { - fprintf(stderr, " \t"); - fprintf(stderr, "iseq: %-24s ", iseq_name); - fprintf(stderr, "self: %-24s ", selfstr); - fprintf(stderr, "%-1s ", biseq_name); + kprintf(" \t"); + kprintf("iseq: %-24s ", iseq_name); + kprintf("self: %-24s ", selfstr); + kprintf("%-1s ", biseq_name); } - fprintf(stderr, "\n"); + kprintf("\n"); // additional information for CI machines if (ruby_on_ci) { @@ -171,131 +190,390 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c if (me) { if (IMEMO_TYPE_P(me, imemo_ment)) { - fprintf(stderr, " me:\n"); - fprintf(stderr, " called_id: %s, type: %s\n", rb_id2name(me->called_id), rb_method_type_name(me->def->type)); - fprintf(stderr, " owner class: %s\n", rb_raw_obj_info(buff, 0x100, me->owner)); + kprintf(" me:\n"); + kprintf(" called_id: %s, type: %s\n", rb_id2name(me->called_id), rb_method_type_name(me->def->type)); + kprintf(" owner class: %s\n", rb_raw_obj_info(buff, 0x100, me->owner)); if (me->owner != me->defined_class) { - fprintf(stderr, " defined_class: %s\n", rb_raw_obj_info(buff, 0x100, me->defined_class)); + kprintf(" defined_class: %s\n", rb_raw_obj_info(buff, 0x100, me->defined_class)); } } else { - fprintf(stderr, " me is corrupted (%s)\n", rb_raw_obj_info(buff, 0x100, (VALUE)me)); + kprintf(" me is corrupted (%s)\n", rb_raw_obj_info(buff, 0x100, (VALUE)me)); } } - fprintf(stderr, " self: %s\n", rb_raw_obj_info(buff, 0x100, cfp->self)); + kprintf(" self: %s\n", rb_raw_obj_info(buff, 0x100, cfp->self)); if (iseq) { if (ISEQ_BODY(iseq)->local_table_size > 0) { - fprintf(stderr, " lvars:\n"); + 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; - fprintf(stderr, " %s: %s\n", + kprintf(" %s: %s\n", rb_id2name(ISEQ_BODY(iseq)->local_table[i]), rb_raw_obj_info(buff, 0x100, argv[i])); } } } } + return true; + error: + return false; } -void -rb_vmdebug_stack_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) +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 != 0) { +#define RUBY_VM_IFUNC_P(ptr) IMEMO_TYPE_P(ptr, imemo_ifunc) + if (RUBY_VM_IFUNC_P(cfp->iseq)) { + iseq_name = "<ifunc>"; + } + else if (SYMBOL_P((VALUE)cfp->iseq)) { + tmp = rb_sym2str((VALUE)cfp->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; + 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) { #if 0 VALUE *sp = cfp->sp; const VALUE *ep = cfp->ep; VALUE *p, *st, *t; - fprintf(stderr, "-- stack frame ------------\n"); + kprintf("-- stack frame ------------\n"); for (p = st = ec->vm_stack; p < sp; p++) { - fprintf(stderr, "%04ld (%p): %08"PRIxVALUE, (long)(p - st), p, *p); + kprintf("%04ld (%p): %08"PRIxVALUE, (long)(p - st), p, *p); t = (VALUE *)*p; if (ec->vm_stack <= t && t < sp) { - fprintf(stderr, " (= %ld)", (long)((VALUE *)GC_GUARDED_PTR_REF((VALUE)t) - ec->vm_stack)); + kprintf(" (= %ld)", (long)((VALUE *)GC_GUARDED_PTR_REF((VALUE)t) - ec->vm_stack)); } if (p == ep) - fprintf(stderr, " <- ep"); + kprintf(" <- ep"); - fprintf(stderr, "\n"); + kprintf("\n"); } #endif - fprintf(stderr, "-- Control frame information " + kprintf("-- Control frame information " "-----------------------------------------------\n"); while ((void *)cfp < (void *)(ec->vm_stack + ec->vm_stack_size)) { - control_frame_dump(ec, cfp); + control_frame_dump(ec, cfp, errout); cfp++; } - fprintf(stderr, "\n"); + kprintf("\n"); + return true; + + error: + return false; } -void +bool rb_vmdebug_stack_dump_raw_current(void) { const rb_execution_context_t *ec = GET_EC(); - rb_vmdebug_stack_dump_raw(ec, ec->cfp); + return rb_vmdebug_stack_dump_raw(ec, ec->cfp, stderr); } -void -rb_vmdebug_env_dump_raw(const rb_env_t *env, const VALUE *ep) +bool +rb_vmdebug_env_dump_raw(const rb_env_t *env, const VALUE *ep, FILE *errout) { unsigned int i; - fprintf(stderr, "-- env --------------------\n"); + kprintf("-- env --------------------\n"); while (env) { - fprintf(stderr, "--\n"); + kprintf("--\n"); for (i = 0; i < env->env_size; i++) { - fprintf(stderr, "%04d: %08"PRIxVALUE" (%p)", i, env->env[i], (void *)&env->env[i]); - if (&env->env[i] == ep) fprintf(stderr, " <- ep"); - fprintf(stderr, "\n"); + kprintf("%04d: %08"PRIxVALUE" (%p)", i, env->env[i], (void *)&env->env[i]); + if (&env->env[i] == ep) kprintf(" <- ep"); + kprintf("\n"); } env = rb_vm_env_prev_env(env); } - fprintf(stderr, "---------------------------\n"); + kprintf("---------------------------\n"); + return true; + + error: + return false; } -void -rb_vmdebug_proc_dump_raw(rb_proc_t *proc) +bool +rb_vmdebug_proc_dump_raw(rb_proc_t *proc, FILE *errout) { const rb_env_t *env; char *selfstr; VALUE val = rb_inspect(vm_block_self(&proc->block)); selfstr = StringValueCStr(val); - fprintf(stderr, "-- proc -------------------\n"); - fprintf(stderr, "self: %s\n", selfstr); + kprintf("-- proc -------------------\n"); + kprintf("self: %s\n", selfstr); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&proc->block)); - rb_vmdebug_env_dump_raw(env, vm_block_ep(&proc->block)); + rb_vmdebug_env_dump_raw(env, vm_block_ep(&proc->block), errout); + return true; + + error: + return false; } -void -rb_vmdebug_stack_dump_th(VALUE thval) +bool +rb_vmdebug_stack_dump_th(VALUE thval, FILE *errout) { rb_thread_t *target_th = rb_thread_ptr(thval); - rb_vmdebug_stack_dump_raw(target_th->ec, target_th->ec->cfp); + return rb_vmdebug_stack_dump_raw(target_th->ec, target_th->ec->cfp, errout); } #if VMDEBUG > 2 -/* copy from vm.c */ +/* copy from vm_insnhelper.c */ 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; - if (ISEQ_BODY(cfp->iseq)->type == ISEQ_TYPE_METHOD) { + if (ISEQ_BODY(cfp->iseq)->type == ISEQ_TYPE_METHOD || VM_FRAME_BMETHOD_P(cfp)) { bp += 1; } return bp; } static void -vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *cfp) +vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, FILE *errout) { int i, argc = 0, local_table_size = 0; VALUE rstr; @@ -321,17 +599,17 @@ vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *c { const VALUE *ptr = ep - local_table_size; - control_frame_dump(ec, cfp); + control_frame_dump(ec, cfp, errout); for (i = 0; i < argc; i++) { rstr = rb_inspect(*ptr); - fprintf(stderr, " arg %2d: %8s (%p)\n", i, StringValueCStr(rstr), - (void *)ptr++); + kprintf(" arg %2d: %8s (%p)\n", i, StringValueCStr(rstr), + (void *)ptr++); } for (; i < local_table_size - 1; i++) { rstr = rb_inspect(*ptr); - fprintf(stderr, " local %2d: %8s (%p)\n", i, StringValueCStr(rstr), - (void *)ptr++); + kprintf(" local %2d: %8s (%p)\n", i, StringValueCStr(rstr), + (void *)ptr++); } ptr = vm_base_ptr(cfp); @@ -347,13 +625,13 @@ vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *c rstr = rb_inspect(*ptr); break; } - fprintf(stderr, " stack %2d: %8s (%"PRIdPTRDIFF")\n", i, StringValueCStr(rstr), + kprintf(" stack %2d: %8s (%"PRIdPTRDIFF")\n", i, StringValueCStr(rstr), (ptr - ec->vm_stack)); } } else if (VM_FRAME_FINISHED_P(cfp)) { if (ec->vm_stack + ec->vm_stack_size > (VALUE *)(cfp + 1)) { - vm_stack_dump_each(ec, cfp + 1); + vm_stack_dump_each(ec, cfp + 1, errout); } else { /* SDR(); */ @@ -365,8 +643,8 @@ vm_stack_dump_each(const rb_execution_context_t *ec, const rb_control_frame_t *c } #endif -void -rb_vmdebug_debug_print_register(const rb_execution_context_t *ec) +bool +rb_vmdebug_debug_print_register(const rb_execution_context_t *ec, FILE *errout) { rb_control_frame_t *cfp = ec->cfp; ptrdiff_t pc = -1; @@ -382,18 +660,22 @@ rb_vmdebug_debug_print_register(const rb_execution_context_t *ec) } cfpi = ((rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size)) - cfp; - fprintf(stderr, " [PC] %04"PRIdPTRDIFF", [SP] %04"PRIdPTRDIFF", [EP] %04"PRIdPTRDIFF", [CFP] %04"PRIdPTRDIFF"\n", + kprintf(" [PC] %04"PRIdPTRDIFF", [SP] %04"PRIdPTRDIFF", [EP] %04"PRIdPTRDIFF", [CFP] %04"PRIdPTRDIFF"\n", pc, (cfp->sp - ec->vm_stack), ep, cfpi); + return true; + + error: + return false; } -void -rb_vmdebug_thread_dump_regs(VALUE thval) +bool +rb_vmdebug_thread_dump_regs(VALUE thval, FILE *errout) { - rb_vmdebug_debug_print_register(rb_thread_ptr(thval)->ec); + return rb_vmdebug_debug_print_register(rb_thread_ptr(thval)->ec, errout); } -void -rb_vmdebug_debug_print_pre(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, const VALUE *_pc) +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; @@ -402,10 +684,10 @@ rb_vmdebug_debug_print_pre(const rb_execution_context_t *ec, const rb_control_fr int i; for (i=0; i<(int)VM_CFP_CNT(ec, cfp); i++) { - printf(" "); + kprintf(" "); } - printf("| "); - if(0)printf("[%03ld] ", (long)(cfp->sp - ec->vm_stack)); + kprintf("| "); + if(0) kprintf("[%03ld] ", (long)(cfp->sp - ec->vm_stack)); /* printf("%3"PRIdPTRDIFF" ", VM_CFP_CNT(ec, cfp)); */ if (pc >= 0) { @@ -416,61 +698,60 @@ rb_vmdebug_debug_print_pre(const rb_execution_context_t *ec, const rb_control_fr } #if VMDEBUG > 3 - fprintf(stderr, " (1)"); - rb_vmdebug_debug_print_register(ec); + kprintf(" (1)"); + rb_vmdebug_debug_print_register(errout, ec); #endif + return true; + + error: + return false; } -void -rb_vmdebug_debug_print_post(const rb_execution_context_t *ec, const rb_control_frame_t *cfp -#if OPT_STACK_CACHING - , VALUE reg_a, VALUE reg_b -#endif - ) +bool +rb_vmdebug_debug_print_post(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, FILE *errout) { #if VMDEBUG > 9 - SDR2(cfp); + if (!rb_vmdebug_stack_dump_raw(ec, cfp, errout)) goto errout; #endif #if VMDEBUG > 3 - fprintf(stderr, " (2)"); - rb_vmdebug_debug_print_register(ec); + kprintf(" (2)"); + rb_vmdebug_debug_print_register(errout, ec); #endif /* stack_dump_raw(ec, cfp); */ #if VMDEBUG > 2 /* stack_dump_thobj(ec); */ - vm_stack_dump_each(ec, ec->cfp); + vm_stack_dump_each(ec, ec->cfp, errout); -#if OPT_STACK_CACHING - { - VALUE rstr; - rstr = rb_inspect(reg_a); - fprintf(stderr, " sc reg A: %s\n", StringValueCStr(rstr)); - rstr = rb_inspect(reg_b); - fprintf(stderr, " sc reg B: %s\n", StringValueCStr(rstr)); - } -#endif - printf + kprintf ("--------------------------------------------------------------\n"); #endif + return true; + +#if VMDEBUG > 2 + error: + return false; +#endif } VALUE -rb_vmdebug_thread_dump_state(VALUE self) +rb_vmdebug_thread_dump_state(FILE *errout, VALUE self) { rb_thread_t *th = rb_thread_ptr(self); rb_control_frame_t *cfp = th->ec->cfp; - fprintf(stderr, "Thread state dump:\n"); - fprintf(stderr, "pc : %p, sp : %p\n", (void *)cfp->pc, (void *)cfp->sp); - fprintf(stderr, "cfp: %p, ep : %p\n", (void *)cfp, (void *)cfp->ep); + kprintf("Thread state dump:\n"); + kprintf("pc : %p, sp : %p\n", (void *)cfp->pc, (void *)cfp->sp); + kprintf("cfp: %p, ep : %p\n", (void *)cfp, (void *)cfp->ep); + error: return Qnil; } #if defined __APPLE__ -# if __DARWIN_UNIX03 +# include <AvailabilityMacros.h> +# if defined(MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 # define MCTX_SS_REG(reg) __ss.__##reg # else # define MCTX_SS_REG(reg) ss.reg @@ -482,11 +763,27 @@ rb_vmdebug_thread_dump_state(VALUE self) # ifdef HAVE_LIBUNWIND # undef backtrace # define backtrace unw_backtrace -# elif defined(__APPLE__) && defined(HAVE_LIBUNWIND_H) +# elif defined(__APPLE__) && defined(HAVE_LIBUNWIND_H) \ + && defined(MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 # define UNW_LOCAL_ONLY # include <libunwind.h> # include <sys/mman.h> # undef backtrace + +# 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 + int backtrace(void **trace, int size) { @@ -582,20 +879,28 @@ darwin_sigtramp: } return n; -# else /* 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; } return n; +# else +# error unsupported architecture # endif } # elif defined(BROKEN_BACKTRACE) @@ -677,6 +982,11 @@ typedef void *PGET_MODULE_BASE_ROUTINE64; typedef void *PTRANSLATE_ADDRESS_ROUTINE64; # endif +struct dump_thead_arg { + DWORD tid; + FILE *errout; +}; + static void dump_thread(void *arg) { @@ -688,7 +998,8 @@ dump_thread(void *arg) BOOL (WINAPI *pSymFromAddr)(HANDLE, DWORD64, DWORD64 *, SYMBOL_INFO *); BOOL (WINAPI *pSymGetLineFromAddr64)(HANDLE, DWORD64, DWORD *, IMAGEHLP_LINE64 *); HANDLE (WINAPI *pOpenThread)(DWORD, BOOL, DWORD); - DWORD tid = *(DWORD *)arg; + DWORD tid = ((struct dump_thead_arg *)arg)->tid; + FILE *errout = ((struct dump_thead_arg *)arg)->errout; HANDLE ph; HANDLE th; @@ -727,6 +1038,14 @@ dump_thread(void *arg) frame.AddrFrame.Offset = context.Rbp; frame.AddrStack.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Rsp; +#elif defined(_M_ARM64) || defined(__aarch64__) + mac = IMAGE_FILE_MACHINE_ARM64; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrPC.Offset = context.Pc; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Fp; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Sp; #else /* i386 */ mac = IMAGE_FILE_MACHINE_I386; frame.AddrPC.Mode = AddrModeFlat; @@ -753,19 +1072,20 @@ dump_thread(void *arg) info->MaxNameLen = MAX_SYM_NAME; if (pSymFromAddr(ph, addr, &displacement, info)) { if (GetModuleFileName((HANDLE)(uintptr_t)pSymGetModuleBase64(ph, addr), libpath, sizeof(libpath))) - fprintf(stderr, "%s", libpath); - fprintf(stderr, "(%s+0x%"PRI_64_PREFIX"x)", + kprintf("%s", libpath); + kprintf("(%s+0x%"PRI_64_PREFIX"x)", info->Name, displacement); } - fprintf(stderr, " [0x%p]", (void *)(VALUE)addr); + kprintf(" [0x%p]", (void *)(VALUE)addr); memset(&line, 0, sizeof(line)); line.SizeOfStruct = sizeof(line); if (pSymGetLineFromAddr64(ph, addr, &tmp, &line)) - fprintf(stderr, " %s:%lu", line.FileName, line.LineNumber); - fprintf(stderr, "\n"); + kprintf(" %s:%lu", line.FileName, line.LineNumber); + kprintf("\n"); } } + error: ResumeThread(th); } CloseHandle(th); @@ -777,49 +1097,61 @@ dump_thread(void *arg) #endif void -rb_print_backtrace(void) +rb_print_backtrace(FILE *errout) { #if USE_BACKTRACE #define MAX_NATIVE_TRACE 1024 static void *trace[MAX_NATIVE_TRACE]; int n = (int)backtrace(trace, MAX_NATIVE_TRACE); #if (defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H)) && defined(HAVE_DLADDR) && !defined(__sparc) - rb_dump_backtrace_with_lines(n, trace); + rb_dump_backtrace_with_lines(n, trace, errout); #else char **syms = backtrace_symbols(trace, n); if (syms) { int i; for (i=0; i<n; i++) { - fprintf(stderr, "%s\n", syms[i]); + kprintf("%s\n", syms[i]); } free(syms); } + error: + /* ignore errors at writing */; #endif #elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); - HANDLE th = (HANDLE)_beginthread(dump_thread, 0, &tid); + struct dump_thead_arg arg = { + .tid = GetCurrentThreadId(), + .errout = errout, + }; + HANDLE th = (HANDLE)_beginthread(dump_thread, 0, &arg); if (th != (HANDLE)-1) WaitForSingleObject(th, INFINITE); #endif } #ifdef HAVE_LIBPROCSTAT +struct procstat; +struct kinfo_proc; +static void procstat_vm(struct procstat *, struct kinfo_proc *, FILE *); #include "missing/procstat_vm.c" #endif #if defined __linux__ -# if defined __x86_64__ || defined __i386__ || defined __aarch64__ || defined __arm__ || defined __riscv -# define HAVE_PRINT_MACHINE_REGISTERS 1 +# if defined(__x86_64__) || defined(__i386__) +# define dump_machine_register(reg) (col_count = print_machine_register(errout, mctx->gregs[REG_##reg], #reg, col_count, 80)) +# elif defined(__aarch64__) || defined(__arm__) || defined(__riscv) || defined(__loongarch64) +# define dump_machine_register(reg, regstr) (col_count = print_machine_register(errout, reg, regstr, col_count, 80)) # endif #elif defined __APPLE__ -# if defined __x86_64__ || defined __i386__ || defined __aarch64__ -# define HAVE_PRINT_MACHINE_REGISTERS 1 +# if defined(__aarch64__) +# define dump_machine_register(reg, regstr) (col_count = print_machine_register(errout, mctx->MCTX_SS_REG(reg), regstr, col_count, 80)) +# else +# define dump_machine_register(reg) (col_count = print_machine_register(errout, mctx->MCTX_SS_REG(reg), #reg, col_count, 80)) # endif #endif -#ifdef HAVE_PRINT_MACHINE_REGISTERS +#ifdef dump_machine_register static int -print_machine_register(size_t reg, const char *reg_name, int col_count, int max_col) +print_machine_register(FILE *errout, size_t reg, const char *reg_name, int col_count, int max_col) { int ret; char buf[64]; @@ -827,34 +1159,24 @@ print_machine_register(size_t reg, const char *reg_name, int col_count, int max_ ret = snprintf(buf, sizeof(buf), " %3.3s: 0x%.*" PRIxSIZE, reg_name, size_width, reg); if (col_count + ret > max_col) { - fputs("\n", stderr); + kputs("\n"); col_count = 0; } col_count += ret; - fputs(buf, stderr); + kputs(buf); return col_count; + + error: + return -1; } -# ifdef __linux__ -# if defined(__x86_64__) || defined(__i386__) -# define dump_machine_register(reg) (col_count = print_machine_register(mctx->gregs[REG_##reg], #reg, col_count, 80)) -# elif defined(__aarch64__) || defined(__arm__) || defined(__riscv) -# define dump_machine_register(reg, regstr) (col_count = print_machine_register(reg, regstr, col_count, 80)) -# endif -# elif defined __APPLE__ -# if defined(__aarch64__) -# define dump_machine_register(reg, regstr) (col_count = print_machine_register(mctx->MCTX_SS_REG(reg), regstr, col_count, 80)) -# else -# define dump_machine_register(reg) (col_count = print_machine_register(mctx->MCTX_SS_REG(reg), #reg, col_count, 80)) -# endif -# endif -static void -rb_dump_machine_register(const ucontext_t *ctx) +static bool +rb_dump_machine_register(FILE *errout, const ucontext_t *ctx) { int col_count = 0; - if (!ctx) return; + if (!ctx) return true; - fprintf(stderr, "-- Machine register context " + kprintf("-- Machine register context " "------------------------------------------------\n"); # if defined __linux__ @@ -958,6 +1280,26 @@ rb_dump_machine_register(const ucontext_t *ctx) dump_machine_register(mctx->__gregs[REG_S2+7], "s9"); dump_machine_register(mctx->__gregs[REG_S2+8], "s10"); dump_machine_register(mctx->__gregs[REG_S2+9], "s11"); +# elif defined __loongarch64 + dump_machine_register(mctx->__gregs[LARCH_REG_SP], "sp"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0], "a0"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+1], "a1"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+2], "a2"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+3], "a3"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+4], "a4"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+5], "a5"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+6], "a6"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+7], "a7"); + dump_machine_register(mctx->__gregs[LARCH_REG_A0+8], "fp"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0], "s0"); + dump_machine_register(mctx->__gregs[LARCH_REG_S1], "s1"); + dump_machine_register(mctx->__gregs[LARCH_REG_S2], "s2"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+3], "s3"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+4], "s4"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+5], "s5"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+6], "s6"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+7], "s7"); + dump_machine_register(mctx->__gregs[LARCH_REG_S0+8], "s8"); # endif } # elif defined __APPLE__ @@ -1025,15 +1367,20 @@ rb_dump_machine_register(const ucontext_t *ctx) # endif } # endif - fprintf(stderr, "\n\n"); + kprintf("\n\n"); + return true; + + error: + return false; } #else -# define rb_dump_machine_register(ctx) ((void)0) -#endif /* HAVE_PRINT_MACHINE_REGISTERS */ +# define rb_dump_machine_register(errout, ctx) ((void)0) +#endif /* dump_machine_register */ -void -rb_vm_bugreport(const void *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]; @@ -1049,8 +1396,8 @@ rb_vm_bugreport(const void *ctx) { static bool crashing = false; if (crashing) { - fprintf(stderr, "Crashed while printing bug report\n"); - return; + kprintf("Crashed while printing bug report\n"); + return true; } crashing = true; } @@ -1064,27 +1411,50 @@ rb_vm_bugreport(const void *ctx) 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) { - SDR(); - rb_backtrace_print_as_bugreport(); - fputs("\n", stderr); + 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 + // we can read these two numbers. It is an estimate because + // we are reading without synchronization. + 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 (ec->thread_ptr->scheduler != Qnil) { + kprintf("Note that the Fiber scheduler is enabled\n"); + } + kputs("\n"); } - rb_dump_machine_register(ctx); + rb_dump_machine_register(errout, ctx); #if USE_BACKTRACE || defined(_WIN32) - fprintf(stderr, "-- C level backtrace information " + kprintf("-- C level backtrace information " "-------------------------------------------\n"); - rb_print_backtrace(); + rb_print_backtrace(errout); - fprintf(stderr, "\n"); + kprintf("\n"); #endif /* USE_BACKTRACE */ if (other_runtime_info || vm) { - fprintf(stderr, "-- Other runtime information " + kprintf("-- Other runtime information " "-----------------------------------------------\n\n"); } if (vm && !rb_during_gc()) { @@ -1097,16 +1467,30 @@ rb_vm_bugreport(const void *ctx) name = vm->progname; if (name) { - fprintf(stderr, "* Loaded script: %.*s\n", + kprintf("* Loaded script: %.*s\n", LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); - fprintf(stderr, "\n"); + kprintf("\n"); } - if (vm->loaded_features) { - fprintf(stderr, "* Loaded features:\n\n"); - for (i=0; i<RARRAY_LEN(vm->loaded_features); i++) { - name = RARRAY_AREF(vm->loaded_features, i); + 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(loaded_features); i++) { + name = RARRAY_AREF(loaded_features, i); if (RB_TYPE_P(name, T_STRING)) { - fprintf(stderr, " %4d %.*s\n", i, + kprintf(" %4d %.*s\n", i, LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); } else if (RB_TYPE_P(name, T_CLASS) || RB_TYPE_P(name, T_MODULE)) { @@ -1114,49 +1498,50 @@ rb_vm_bugreport(const void *ctx) "class" : "module"; name = rb_search_class_path(rb_class_real(name)); if (!RB_TYPE_P(name, T_STRING)) { - fprintf(stderr, " %4d %s:<unnamed>\n", i, type); + kprintf(" %4d %s:<unnamed>\n", i, type); continue; } - fprintf(stderr, " %4d %s:%.*s\n", i, type, + kprintf(" %4d %s:%.*s\n", i, type, LIMITED_NAME_LENGTH(name), RSTRING_PTR(name)); } else { VALUE klass = rb_search_class_path(rb_obj_class(name)); if (!RB_TYPE_P(klass, T_STRING)) { - fprintf(stderr, " %4d #<%p:%p>\n", i, + kprintf(" %4d #<%p:%p>\n", i, (void *)CLASS_OF(name), (void *)name); continue; } - fprintf(stderr, " %4d #<%.*s:%p>\n", i, + kprintf(" %4d #<%.*s:%p>\n", i, LIMITED_NAME_LENGTH(klass), RSTRING_PTR(klass), (void *)name); } } } - fprintf(stderr, "\n"); + kprintf("\n"); } { -#ifdef PROC_MAPS_NAME +#ifndef RUBY_ASAN_ENABLED +# ifdef PROC_MAPS_NAME { FILE *fp = fopen(PROC_MAPS_NAME, "r"); if (fp) { - fprintf(stderr, "* Process memory map:\n\n"); + kprintf("* Process memory map:\n\n"); while (!feof(fp)) { char buff[0x100]; size_t rn = fread(buff, 1, 0x100, fp); - if (fwrite(buff, 1, rn, stderr) != rn) + if (fwrite(buff, 1, rn, errout) != rn) break; } fclose(fp); - fprintf(stderr, "\n\n"); + kprintf("\n\n"); } } -#endif /* __linux__ */ -#ifdef HAVE_LIBPROCSTAT -# define MIB_KERN_PROC_PID_LEN 4 +# endif /* __linux__ */ +# ifdef HAVE_LIBPROCSTAT +# define MIB_KERN_PROC_PID_LEN 4 int mib[MIB_KERN_PROC_PID_LEN]; struct kinfo_proc kp; size_t len = sizeof(struct kinfo_proc); @@ -1165,24 +1550,24 @@ rb_vm_bugreport(const void *ctx) mib[2] = KERN_PROC_PID; mib[3] = getpid(); if (sysctl(mib, MIB_KERN_PROC_PID_LEN, &kp, &len, NULL, 0) == -1) { - perror("sysctl"); + kprintf("sysctl: %s\n", strerror(errno)); } else { struct procstat *prstat = procstat_open_sysctl(); - fprintf(stderr, "* Process memory map:\n\n"); - procstat_vm(prstat, &kp); + kprintf("* Process memory map:\n\n"); + procstat_vm(prstat, &kp, errout); procstat_close(prstat); - fprintf(stderr, "\n"); + kprintf("\n"); } -#endif /* __FreeBSD__ */ -#ifdef __APPLE__ +# endif /* __FreeBSD__ */ +# ifdef __APPLE__ vm_address_t addr = 0; vm_size_t size = 0; struct vm_region_submap_info map; mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT; natural_t depth = 0; - fprintf(stderr, "* Process memory map:\n\n"); + kprintf("* Process memory map:\n\n"); while (1) { if (vm_region_recurse(mach_task_self(), &addr, &size, &depth, (vm_region_recurse_info_t)&map, &count) != KERN_SUCCESS) { @@ -1194,39 +1579,49 @@ rb_vm_bugreport(const void *ctx) depth++; } else { - fprintf(stderr, "%lx-%lx %s%s%s", addr, (addr+size), + kprintf("%lx-%lx %s%s%s", addr, (addr+size), ((map.protection & VM_PROT_READ) != 0 ? "r" : "-"), ((map.protection & VM_PROT_WRITE) != 0 ? "w" : "-"), ((map.protection & VM_PROT_EXECUTE) != 0 ? "x" : "-")); -#ifdef HAVE_LIBPROC_H +# ifdef HAVE_LIBPROC_H char buff[PATH_MAX]; if (proc_regionfilename(getpid(), addr, buff, sizeof(buff)) > 0) { - fprintf(stderr, " %s", buff); + kprintf(" %s", buff); } -#endif - fprintf(stderr, "\n"); +# endif + kprintf("\n"); } addr += size; size = 0; } +# endif #endif } + return true; + + error: + return false; } -void +bool rb_vmdebug_stack_dump_all_threads(void) { rb_thread_t *th = NULL; rb_ractor_t *r = GET_RACTOR(); + FILE *errout = stderr; // TODO: now it only shows current ractor ccan_list_for_each(&r->threads.set, th, lt_node) { #ifdef NON_SCALAR_THREAD_ID - fprintf(stderr, "th: %p, native_id: N/A\n", th); + kprintf("th: %p, native_id: N/A\n", th); #else - fprintf(stderr, "th: %p, native_id: %p\n", (void *)th, (void *)(uintptr_t)th->nt->thread_id); + kprintf("th: %p, native_id: %p\n", (void *)th, (void *)(uintptr_t)th->nt->thread_id); #endif - rb_vmdebug_stack_dump_raw(th->ec, th->ec->cfp); + if (!rb_vmdebug_stack_dump_raw(th->ec, th->ec->cfp, errout)) goto error; } + return true; + + error: + return false; } |
