diff options
Diffstat (limited to 'vm_backtrace.c')
| -rw-r--r-- | vm_backtrace.c | 680 |
1 files changed, 438 insertions, 242 deletions
diff --git a/vm_backtrace.c b/vm_backtrace.c index e50611c7bb..07d2e33e32 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -11,6 +11,7 @@ #include "eval_intern.h" #include "internal.h" +#include "internal/class.h" #include "internal/error.h" #include "internal/vm.h" #include "iseq.h" @@ -30,9 +31,6 @@ id2str(ID id) } #define rb_id2str(id) id2str(id) -#define BACKTRACE_START 0 -#define ALL_BACKTRACE_LINES -1 - inline static int calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) { @@ -46,7 +44,7 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) } if (lineno) *lineno = ISEQ_BODY(iseq)->location.first_lineno; #ifdef USE_ISEQ_NODE_ID - if (node_id) *node_id = -1; + if (node_id) *node_id = ISEQ_BODY(iseq)->location.node_id; #endif return 1; } @@ -56,8 +54,11 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) VM_ASSERT(ISEQ_BODY(iseq)->iseq_size); ptrdiff_t n = pc - ISEQ_BODY(iseq)->iseq_encoded; - VM_ASSERT(n <= ISEQ_BODY(iseq)->iseq_size); VM_ASSERT(n >= 0); +#if SIZEOF_PTRDIFF_T > SIZEOF_INT + VM_ASSERT(n <= (ptrdiff_t)UINT_MAX); +#endif + VM_ASSERT((unsigned int)n <= ISEQ_BODY(iseq)->iseq_size); ASSUME(n >= 0); size_t pos = n; /* no overflow */ if (LIKELY(pos)) { @@ -67,7 +68,7 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) #if VMDEBUG && defined(HAVE_BUILTIN___BUILTIN_TRAP) else { /* SDR() is not possible; that causes infinite loop. */ - rb_print_backtrace(); + rb_print_backtrace(stderr); __builtin_trap(); } #endif @@ -116,14 +117,9 @@ rb_vm_get_sourceline(const rb_control_frame_t *cfp) } typedef struct rb_backtrace_location_struct { - enum LOCATION_TYPE { - LOCATION_TYPE_ISEQ = 1, - LOCATION_TYPE_CFUNC, - } type; - + const rb_callable_method_entry_t *cme; const rb_iseq_t *iseq; const VALUE *pc; - ID mid; } rb_backtrace_location_t; struct valued_frame_info { @@ -135,37 +131,32 @@ static void location_mark(void *ptr) { struct valued_frame_info *vfi = (struct valued_frame_info *)ptr; - rb_gc_mark(vfi->btobj); + rb_gc_mark_movable(vfi->btobj); } static void -location_mark_entry(rb_backtrace_location_t *fi) +location_ref_update(void *ptr) { - switch (fi->type) { - case LOCATION_TYPE_ISEQ: - rb_gc_mark_movable((VALUE)fi->iseq); - break; - case LOCATION_TYPE_CFUNC: - if (fi->iseq) { - rb_gc_mark_movable((VALUE)fi->iseq); - } - break; - default: - break; - } + struct valued_frame_info *vfi = ptr; + vfi->btobj = rb_gc_location(vfi->btobj); } -static size_t -location_memsize(const void *ptr) +static void +location_mark_entry(rb_backtrace_location_t *fi) { - /* rb_backtrace_location_t *fi = (rb_backtrace_location_t *)ptr; */ - return sizeof(rb_backtrace_location_t); + rb_gc_mark((VALUE)fi->cme); + if (fi->iseq) rb_gc_mark_movable((VALUE)fi->iseq); } static const rb_data_type_t location_data_type = { "frame_info", - {location_mark, RUBY_TYPED_DEFAULT_FREE, location_memsize,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + { + location_mark, + RUBY_TYPED_DEFAULT_FREE, + NULL, // No external memory to report, + location_ref_update, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; int @@ -178,25 +169,17 @@ static inline rb_backtrace_location_t * location_ptr(VALUE locobj) { struct valued_frame_info *vloc; - GetCoreDataFromValue(locobj, struct valued_frame_info, vloc); + TypedData_Get_Struct(locobj, struct valued_frame_info, &location_data_type, vloc); return vloc->loc; } static int location_lineno(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: + if (loc->iseq) { return calc_lineno(loc->iseq, loc->pc); - case LOCATION_TYPE_CFUNC: - if (loc->iseq && loc->pc) { - return calc_lineno(loc->iseq, loc->pc); - } - return 0; - default: - rb_bug("location_lineno: unreachable"); - UNREACHABLE; } + return 0; } /* @@ -213,20 +196,111 @@ location_lineno_m(VALUE self) return INT2FIX(location_lineno(location_ptr(self))); } +VALUE rb_mod_name0(VALUE klass, bool *permanent); + +VALUE +rb_gen_method_name(VALUE owner, VALUE name) +{ + bool permanent; + if (RB_TYPE_P(owner, T_CLASS) || RB_TYPE_P(owner, T_MODULE)) { + if (RCLASS_SINGLETON_P(owner)) { + VALUE v = RCLASS_ATTACHED_OBJECT(owner); + if (RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE)) { + v = rb_mod_name0(v, &permanent); + if (permanent && !NIL_P(v)) { + return rb_sprintf("%"PRIsVALUE".%"PRIsVALUE, v, name); + } + } + } + else { + owner = rb_mod_name0(owner, &permanent); + if (permanent && !NIL_P(owner)) { + return rb_sprintf("%"PRIsVALUE"#%"PRIsVALUE, owner, name); + } + } + } + return name; +} + static VALUE -location_label(rb_backtrace_location_t *loc) +calculate_iseq_label(VALUE owner, const rb_iseq_t *iseq) +{ +retry: + switch (ISEQ_BODY(iseq)->type) { + case ISEQ_TYPE_TOP: + case ISEQ_TYPE_CLASS: + case ISEQ_TYPE_MAIN: + return ISEQ_BODY(iseq)->location.label; + case ISEQ_TYPE_METHOD: + return rb_gen_method_name(owner, ISEQ_BODY(iseq)->location.label); + case ISEQ_TYPE_BLOCK: + case ISEQ_TYPE_PLAIN: { + int level = 0; + const rb_iseq_t *orig_iseq = iseq; + if (ISEQ_BODY(orig_iseq)->parent_iseq != 0) { + while (ISEQ_BODY(orig_iseq)->local_iseq != iseq) { + if (ISEQ_BODY(iseq)->type == ISEQ_TYPE_BLOCK) { + level++; + } + iseq = ISEQ_BODY(iseq)->parent_iseq; + } + } + if (level <= 1) { + return rb_sprintf("block in %"PRIsVALUE, calculate_iseq_label(owner, iseq)); + } + else { + return rb_sprintf("block (%d levels) in %"PRIsVALUE, level, calculate_iseq_label(owner, iseq)); + } + } + case ISEQ_TYPE_RESCUE: + case ISEQ_TYPE_ENSURE: + case ISEQ_TYPE_EVAL: + iseq = ISEQ_BODY(iseq)->parent_iseq; + goto retry; + default: + rb_bug("calculate_iseq_label: unreachable"); + } +} + +static bool +is_internal_location(const rb_iseq_t *iseq) +{ + static const char prefix[] = "<internal:"; + const size_t prefix_len = sizeof(prefix) - 1; + VALUE file = rb_iseq_path(iseq); + return strncmp(prefix, RSTRING_PTR(file), prefix_len) == 0; +} + +// Return true if a given location is a C method or supposed to behave like one. +static inline bool +location_cfunc_p(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: - return ISEQ_BODY(loc->iseq)->location.label; - case LOCATION_TYPE_CFUNC: - return rb_id2str(loc->mid); + if (!loc->cme) return false; + + switch (loc->cme->def->type) { + case VM_METHOD_TYPE_CFUNC: + return true; + case VM_METHOD_TYPE_ISEQ: + return is_internal_location(loc->cme->def->body.iseq.iseqptr); default: - rb_bug("location_label: unreachable"); - UNREACHABLE; + return false; } } +static VALUE +location_label(rb_backtrace_location_t *loc) +{ + if (location_cfunc_p(loc)) { + return rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + } + else { + VALUE owner = Qnil; + if (loc->cme) { + owner = loc->cme->owner; + } + return calculate_iseq_label(owner, loc->iseq); + } +} /* * Returns the label of this frame. * @@ -243,15 +317,14 @@ location_label(rb_backtrace_location_t *loc) * 1.times do * puts caller_locations(0).first.label * end - * * end * end * * The result of calling +foo+ is this: * - * label: foo - * label: block in foo - * label: block (2 levels) in foo + * foo + * block in foo + * block (2 levels) in foo * */ static VALUE @@ -263,21 +336,36 @@ location_label_m(VALUE self) static VALUE location_base_label(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: - return ISEQ_BODY(loc->iseq)->location.base_label; - case LOCATION_TYPE_CFUNC: - return rb_id2str(loc->mid); - default: - rb_bug("location_base_label: unreachable"); - UNREACHABLE; + if (location_cfunc_p(loc)) { + return rb_id2str(loc->cme->def->original_id); } + + return ISEQ_BODY(loc->iseq)->location.base_label; } /* - * Returns the base label of this frame. + * Returns the base label of this frame, which is usually equal to the label, + * without decoration. + * + * Consider the following example: + * + * def foo + * puts caller_locations(0).first.base_label + * + * 1.times do + * puts caller_locations(0).first.base_label + * + * 1.times do + * puts caller_locations(0).first.base_label + * end + * end + * end + * + * The result of calling +foo+ is this: * - * Usually same as #label, without decoration. + * foo + * foo + * foo */ static VALUE location_base_label_m(VALUE self) @@ -288,15 +376,7 @@ location_base_label_m(VALUE self) static const rb_iseq_t * location_iseq(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: - return loc->iseq; - case LOCATION_TYPE_CFUNC: - return loc->iseq; - default: - rb_bug("location_iseq: unreachable"); - UNREACHABLE; - } + return loc->iseq; } /* @@ -320,18 +400,10 @@ location_path_m(VALUE self) static int location_node_id(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: + if (loc->iseq) { return calc_node_id(loc->iseq, loc->pc); - case LOCATION_TYPE_CFUNC: - if (loc->iseq && loc->pc) { - return calc_node_id(loc->iseq, loc->pc); - } - return -1; - default: - rb_bug("location_node_id: unreachable"); - UNREACHABLE; } + return -1; } #endif @@ -357,18 +429,10 @@ rb_get_iseq_from_frame_info(VALUE obj) static VALUE location_realpath(rb_backtrace_location_t *loc) { - switch (loc->type) { - case LOCATION_TYPE_ISEQ: + if (loc->iseq) { return rb_iseq_realpath(loc->iseq); - case LOCATION_TYPE_CFUNC: - if (loc->iseq) { - return rb_iseq_realpath(loc->iseq); - } - return Qnil; - default: - rb_bug("location_realpath: unreachable"); - UNREACHABLE; } + return Qnil; } /* @@ -395,25 +459,19 @@ location_format(VALUE file, int lineno, VALUE name) rb_str_cat_cstr(s, "unknown method"); } else { - rb_str_catf(s, "`%s'", RSTRING_PTR(name)); + rb_str_catf(s, "'%s'", RSTRING_PTR(name)); } + RB_GC_GUARD(name); return s; } static VALUE location_to_str(rb_backtrace_location_t *loc) { - VALUE file, name; + VALUE file, owner = Qnil, name; int lineno; - switch (loc->type) { - case LOCATION_TYPE_ISEQ: - file = rb_iseq_path(loc->iseq); - name = ISEQ_BODY(loc->iseq)->location.label; - - lineno = calc_lineno(loc->iseq, loc->pc); - break; - case LOCATION_TYPE_CFUNC: + if (location_cfunc_p(loc)) { if (loc->iseq && loc->pc) { file = rb_iseq_path(loc->iseq); lineno = calc_lineno(loc->iseq, loc->pc); @@ -422,10 +480,15 @@ location_to_str(rb_backtrace_location_t *loc) file = GET_VM()->progname; lineno = 0; } - name = rb_id2str(loc->mid); - break; - default: - rb_bug("location_to_str: unreachable"); + name = rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + } + else { + file = rb_iseq_path(loc->iseq); + lineno = calc_lineno(loc->iseq, loc->pc); + if (loc->cme) { + owner = loc->cme->owner; + } + name = calculate_iseq_label(owner, loc->iseq); } return location_format(file, lineno, name); @@ -451,10 +514,10 @@ location_inspect_m(VALUE self) } typedef struct rb_backtrace_struct { - rb_backtrace_location_t *backtrace; int backtrace_size; VALUE strary; VALUE locary; + rb_backtrace_location_t backtrace[1]; } rb_backtrace_t; static void @@ -471,27 +534,11 @@ backtrace_mark(void *ptr) } static void -backtrace_free(void *ptr) -{ - rb_backtrace_t *bt = (rb_backtrace_t *)ptr; - if (bt->backtrace) ruby_xfree(bt->backtrace); - ruby_xfree(bt); -} - -static void location_update_entry(rb_backtrace_location_t *fi) { - switch (fi->type) { - case LOCATION_TYPE_ISEQ: - fi->iseq = (rb_iseq_t*)rb_gc_location((VALUE)fi->iseq); - break; - case LOCATION_TYPE_CFUNC: - if (fi->iseq) { - fi->iseq = (rb_iseq_t*)rb_gc_location((VALUE)fi->iseq); - } - break; - default: - break; + fi->cme = (rb_callable_method_entry_t *)rb_gc_location((VALUE)fi->cme); + if (fi->iseq) { + fi->iseq = (rb_iseq_t *)rb_gc_location((VALUE)fi->iseq); } } @@ -508,17 +555,18 @@ backtrace_update(void *ptr) bt->locary = rb_gc_location(bt->locary); } -static size_t -backtrace_memsize(const void *ptr) -{ - rb_backtrace_t *bt = (rb_backtrace_t *)ptr; - return sizeof(rb_backtrace_t) + sizeof(rb_backtrace_location_t) * bt->backtrace_size; -} - static const rb_data_type_t backtrace_data_type = { "backtrace", - {backtrace_mark, backtrace_free, backtrace_memsize, backtrace_update}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + { + backtrace_mark, + RUBY_DEFAULT_FREE, + NULL, // No external memory to report, + backtrace_update, + }, + /* Cannot set the RUBY_TYPED_EMBEDDABLE flag because the loc of frame_info + * points elements in the backtrace array. This can cause the loc to become + * incorrect if this backtrace object is moved by compaction. */ + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; int @@ -535,6 +583,16 @@ backtrace_alloc(VALUE klass) return obj; } +static VALUE +backtrace_alloc_capa(long num_frames, rb_backtrace_t **backtrace) +{ + size_t memsize = offsetof(rb_backtrace_t, backtrace) + num_frames * sizeof(rb_backtrace_location_t); + VALUE btobj = rb_data_typed_object_zalloc(rb_cBacktrace, memsize, &backtrace_data_type); + TypedData_Get_Struct(btobj, rb_backtrace_t, &backtrace_data_type, *backtrace); + return btobj; +} + + static long backtrace_size(const rb_execution_context_t *ec) { @@ -557,20 +615,18 @@ backtrace_size(const rb_execution_context_t *ec) } static bool -is_internal_location(const rb_control_frame_t *cfp) +is_rescue_or_ensure_frame(const rb_control_frame_t *cfp) { - static const char prefix[] = "<internal:"; - const size_t prefix_len = sizeof(prefix) - 1; - VALUE file = rb_iseq_path(cfp->iseq); - return strncmp(prefix, RSTRING_PTR(file), prefix_len) == 0; + enum rb_iseq_type type = ISEQ_BODY(cfp->iseq)->type; + return type == ISEQ_TYPE_RESCUE || type == ISEQ_TYPE_ENSURE; } static void -bt_update_cfunc_loc(unsigned long cfunc_counter, rb_backtrace_location_t *cfunc_loc, const rb_iseq_t *iseq, const VALUE *pc) +bt_backpatch_loc(unsigned long backpatch_counter, rb_backtrace_location_t *loc, const rb_iseq_t *iseq, const VALUE *pc) { - for (; cfunc_counter > 0; cfunc_counter--, cfunc_loc--) { - cfunc_loc->iseq = iseq; - cfunc_loc->pc = pc; + for (; backpatch_counter > 0; backpatch_counter--, loc--) { + loc->iseq = iseq; + loc->pc = pc; } } @@ -590,11 +646,11 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram const rb_control_frame_t *cfp = ec->cfp; const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); ptrdiff_t size; - rb_backtrace_t *bt; - VALUE btobj = backtrace_alloc(rb_cBacktrace); + rb_backtrace_t *bt = NULL; + VALUE btobj = Qnil; rb_backtrace_location_t *loc = NULL; - unsigned long cfunc_counter = 0; - GetCoreDataFromValue(btobj, rb_backtrace_t, bt); + unsigned long backpatch_counter = 0; + bool skip_next_frame = FALSE; // In the case the thread vm_stack or cfp is not initialized, there is no backtrace. if (end_cfp == NULL) { @@ -622,7 +678,8 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram } } - bt->backtrace = ZALLOC_N(rb_backtrace_location_t, num_frames); + btobj = backtrace_alloc_capa(num_frames, &bt); + bt->backtrace_size = 0; if (num_frames == 0) { if (start_too_large) *start_too_large = 0; @@ -635,18 +692,44 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram if (start_frame > 0) { start_frame--; } - else if (!skip_internal || !is_internal_location(cfp)) { - const rb_iseq_t *iseq = cfp->iseq; - const VALUE *pc = cfp->pc; - loc = &bt->backtrace[bt->backtrace_size++]; - loc->type = LOCATION_TYPE_ISEQ; - loc->iseq = iseq; - loc->pc = pc; - bt_update_cfunc_loc(cfunc_counter, loc-1, iseq, pc); - if (do_yield) { - bt_yield_loc(loc - cfunc_counter, cfunc_counter+1, btobj); + else { + bool internal = is_internal_location(cfp->iseq); + if (skip_internal && internal) continue; + if (!skip_next_frame) { + const rb_iseq_t *iseq = cfp->iseq; + const VALUE *pc = cfp->pc; + if (internal && backpatch_counter > 0) { + // To keep only one internal frame, discard the previous backpatch frames + bt->backtrace_size -= backpatch_counter; + backpatch_counter = 0; + } + loc = &bt->backtrace[bt->backtrace_size++]; + RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); + // internal frames (`<internal:...>`) should behave like C methods + if (internal) { + // Typically, these iseq and pc are not needed because they will be backpatched later. + // But when the call stack starts with an internal frame (i.e., prelude.rb), + // they will be used to show the `<internal:...>` location. + RB_OBJ_WRITE(btobj, &loc->iseq, iseq); + loc->pc = pc; + backpatch_counter++; + } + else { + RB_OBJ_WRITE(btobj, &loc->iseq, iseq); + if ((VM_FRAME_TYPE(cfp) & VM_FRAME_MAGIC_MASK) == VM_FRAME_MAGIC_DUMMY) { + loc->pc = NULL; // means location.first_lineno + } + else { + loc->pc = pc; + } + bt_backpatch_loc(backpatch_counter, loc-1, iseq, pc); + if (do_yield) { + bt_yield_loc(loc - backpatch_counter, backpatch_counter+1, btobj); + } + backpatch_counter = 0; + } } - cfunc_counter = 0; + skip_next_frame = is_rescue_or_ensure_frame(cfp); } } } @@ -657,21 +740,24 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram } else { loc = &bt->backtrace[bt->backtrace_size++]; - loc->type = LOCATION_TYPE_CFUNC; + RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); loc->iseq = NULL; loc->pc = NULL; - loc->mid = rb_vm_frame_method_entry(cfp)->def->original_id; - cfunc_counter++; + backpatch_counter++; } } } - if (cfunc_counter > 0) { + // When a backtrace entry corresponds to a method defined in C (e.g. rb_define_method), the reported file:line + // is the one of the caller Ruby frame, so if the last entry is a C frame we find the caller Ruby frame here. + if (backpatch_counter > 0) { for (; cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { - if (cfp->iseq && cfp->pc && (!skip_internal || !is_internal_location(cfp))) { - bt_update_cfunc_loc(cfunc_counter, loc, cfp->iseq, cfp->pc); + if (cfp->iseq && cfp->pc && !(skip_internal && is_internal_location(cfp->iseq))) { + VM_ASSERT(!skip_next_frame); // ISEQ_TYPE_RESCUE/ISEQ_TYPE_ENSURE should have a caller Ruby ISEQ, not a cfunc + bt_backpatch_loc(backpatch_counter, loc, cfp->iseq, cfp->pc); + RB_OBJ_WRITTEN(btobj, Qundef, cfp->iseq); if (do_yield) { - bt_yield_loc(loc - cfunc_counter, cfunc_counter, btobj); + bt_yield_loc(loc - backpatch_counter, backpatch_counter, btobj); } break; } @@ -682,10 +768,10 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram return btobj; } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_ec_backtrace_object(const rb_execution_context_t *ec) { - return rb_ec_partial_backtrace_object(ec, BACKTRACE_START, ALL_BACKTRACE_LINES, NULL, FALSE, FALSE); + return rb_ec_partial_backtrace_object(ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES, NULL, FALSE, FALSE); } static VALUE @@ -715,7 +801,7 @@ backtrace_to_str_ary(VALUE self) { VALUE r; rb_backtrace_t *bt; - GetCoreDataFromValue(self, rb_backtrace_t, bt); + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); r = backtrace_collect(bt, location_to_str_dmyarg, 0); RB_GC_GUARD(self); return r; @@ -725,30 +811,14 @@ VALUE rb_backtrace_to_str_ary(VALUE self) { rb_backtrace_t *bt; - GetCoreDataFromValue(self, rb_backtrace_t, bt); + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); if (!bt->strary) { - bt->strary = backtrace_to_str_ary(self); + RB_OBJ_WRITE(self, &bt->strary, backtrace_to_str_ary(self)); } return bt->strary; } -MJIT_FUNC_EXPORTED void -rb_backtrace_use_iseq_first_lineno_for_last_location(VALUE self) -{ - const rb_backtrace_t *bt; - rb_backtrace_location_t *loc; - - GetCoreDataFromValue(self, rb_backtrace_t, bt); - VM_ASSERT(bt->backtrace_size > 0); - - loc = &bt->backtrace[0]; - - VM_ASSERT(loc->type == LOCATION_TYPE_ISEQ); - - loc->pc = NULL; // means location.first_lineno -} - static VALUE location_create(rb_backtrace_location_t *srcloc, void *btobj) { @@ -757,7 +827,7 @@ location_create(rb_backtrace_location_t *srcloc, void *btobj) obj = TypedData_Make_Struct(rb_cBacktraceLocation, struct valued_frame_info, &location_data_type, vloc); vloc->loc = srcloc; - vloc->btobj = (VALUE)btobj; + RB_OBJ_WRITE(obj, &vloc->btobj, (VALUE)btobj); return obj; } @@ -767,7 +837,7 @@ backtrace_to_location_ary(VALUE self) { VALUE r; rb_backtrace_t *bt; - GetCoreDataFromValue(self, rb_backtrace_t, bt); + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); r = backtrace_collect(bt, location_create, (void *)self); RB_GC_GUARD(self); return r; @@ -777,14 +847,48 @@ VALUE rb_backtrace_to_location_ary(VALUE self) { rb_backtrace_t *bt; - GetCoreDataFromValue(self, rb_backtrace_t, bt); + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); if (!bt->locary) { - bt->locary = backtrace_to_location_ary(self); + RB_OBJ_WRITE(self, &bt->locary, backtrace_to_location_ary(self)); } return bt->locary; } +VALUE +rb_location_ary_to_backtrace(VALUE ary) +{ + if (!RB_TYPE_P(ary, T_ARRAY) || RARRAY_LEN(ary) == 0 || !rb_frame_info_p(RARRAY_AREF(ary, 0))) { + return Qfalse; + } + + rb_backtrace_t *new_backtrace; + long num_frames = RARRAY_LEN(ary); + VALUE btobj = backtrace_alloc_capa(num_frames, &new_backtrace); + + for (long index = 0; index < RARRAY_LEN(ary); index++) { + VALUE locobj = RARRAY_AREF(ary, index); + + if (!rb_frame_info_p(locobj)) { + return Qfalse; + } + + struct valued_frame_info *src_vloc; + TypedData_Get_Struct(locobj, struct valued_frame_info, &location_data_type, src_vloc); + + rb_backtrace_location_t *dst_location = &new_backtrace->backtrace[index]; + RB_OBJ_WRITE(btobj, &dst_location->cme, src_vloc->loc->cme); + RB_OBJ_WRITE(btobj, &dst_location->iseq, src_vloc->loc->iseq); + dst_location->pc = src_vloc->loc->pc; + + new_backtrace->backtrace_size++; + + RB_GC_GUARD(locobj); + } + + return btobj; +} + static VALUE backtrace_dump_data(VALUE self) { @@ -796,8 +900,8 @@ static VALUE backtrace_load_data(VALUE self, VALUE str) { rb_backtrace_t *bt; - GetCoreDataFromValue(self, rb_backtrace_t, bt); - bt->strary = str; + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); + RB_OBJ_WRITE(self, &bt->strary, str); return self; } @@ -979,7 +1083,7 @@ oldbt_print(void *data, VALUE file, int lineno, VALUE name) RSTRING_PTR(file), lineno); } else { - fprintf(fp, "\tfrom %s:%d:in `%s'\n", + fprintf(fp, "\tfrom %s:%d:in '%s'\n", RSTRING_PTR(file), lineno, RSTRING_PTR(name)); } } @@ -998,31 +1102,38 @@ vm_backtrace_print(FILE *fp) &arg); } +struct oldbt_bugreport_arg { + FILE *fp; + int count; +}; + static void oldbt_bugreport(void *arg, VALUE file, int line, VALUE method) { + struct oldbt_bugreport_arg *p = arg; + FILE *fp = p->fp; const char *filename = NIL_P(file) ? "ruby" : RSTRING_PTR(file); - if (!*(int *)arg) { - fprintf(stderr, "-- Ruby level backtrace information " + if (!p->count) { + fprintf(fp, "-- Ruby level backtrace information " "----------------------------------------\n"); - *(int *)arg = 1; + p->count = 1; } if (NIL_P(method)) { - fprintf(stderr, "%s:%d:in unknown method\n", filename, line); + fprintf(fp, "%s:%d:in unknown method\n", filename, line); } else { - fprintf(stderr, "%s:%d:in `%s'\n", filename, line, RSTRING_PTR(method)); + fprintf(fp, "%s:%d:in '%s'\n", filename, line, RSTRING_PTR(method)); } } void -rb_backtrace_print_as_bugreport(void) +rb_backtrace_print_as_bugreport(FILE *fp) { struct oldbt_arg arg; - int i = 0; + struct oldbt_bugreport_arg barg = {fp, 0}; arg.func = oldbt_bugreport; - arg.data = (int *)&i; + arg.data = &barg; backtrace_each(GET_EC(), oldbt_init, @@ -1052,7 +1163,7 @@ oldbt_print_to(void *data, VALUE file, int lineno, VALUE name) rb_str_cat2(str, "unknown method\n"); } else { - rb_str_catf(str, " `%"PRIsVALUE"'\n", name); + rb_str_catf(str, " '%"PRIsVALUE"'\n", name); } (*arg->iter)(arg->output, str); } @@ -1077,26 +1188,26 @@ rb_backtrace_each(VALUE (*iter)(VALUE recv, VALUE str), VALUE output) VALUE rb_make_backtrace(void) { - return rb_ec_backtrace_str_ary(GET_EC(), BACKTRACE_START, ALL_BACKTRACE_LINES); + return rb_ec_backtrace_str_ary(GET_EC(), RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES); } -static VALUE -ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, int to_str) +static long +ec_backtrace_range(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, long *len_ptr) { - VALUE level, vn; + VALUE level, vn, opts; long lev, n; - VALUE btval; - VALUE r; - int too_large; - rb_scan_args(argc, argv, "02", &level, &vn); + rb_scan_args(argc, argv, "02:", &level, &vn, &opts); + if (!NIL_P(opts)) { + rb_get_kwargs(opts, (ID []){0}, 0, 0, NULL); + } if (argc == 2 && NIL_P(vn)) argc--; switch (argc) { case 0: lev = lev_default + lev_plus; - n = ALL_BACKTRACE_LINES; + n = RUBY_ALL_BACKTRACE_LINES; break; case 1: { @@ -1108,10 +1219,10 @@ ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *arg rb_raise(rb_eArgError, "negative level (%ld)", lev); } lev += lev_plus; - n = ALL_BACKTRACE_LINES; + n = RUBY_ALL_BACKTRACE_LINES; break; case Qnil: - return Qnil; + return -1; default: lev = beg + lev_plus; n = len; @@ -1135,6 +1246,20 @@ ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *arg break; } + *len_ptr = n; + return lev; +} + +static VALUE +ec_backtrace_to_ary(const rb_execution_context_t *ec, int argc, const VALUE *argv, int lev_default, int lev_plus, int to_str) +{ + long lev, n; + VALUE btval, r; + int too_large; + + lev = ec_backtrace_range(ec, argc, argv, lev_default, lev_plus, &n); + if (lev < 0) return Qnil; + if (n == 0) { return rb_ary_new(); } @@ -1264,15 +1389,19 @@ rb_f_caller_locations(int argc, VALUE *argv, VALUE _) /* * call-seq: - * Thread.each_caller_location{ |loc| ... } -> nil + * Thread.each_caller_location(...) { |loc| ... } -> nil * * Yields each frame of the current execution stack as a * backtrace location object. */ static VALUE -each_caller_location(VALUE unused) +each_caller_location(int argc, VALUE *argv, VALUE _) { - rb_ec_partial_backtrace_object(GET_EC(), 2, ALL_BACKTRACE_LINES, NULL, FALSE, TRUE); + rb_execution_context_t *ec = GET_EC(); + long n, lev = ec_backtrace_range(ec, argc, argv, 1, 1, &n); + if (lev >= 0 && n != 0) { + rb_ec_partial_backtrace_object(ec, lev, n, NULL, FALSE, TRUE); + } return Qnil; } @@ -1352,7 +1481,7 @@ Init_vm_backtrace(void) rb_define_global_function("caller", rb_f_caller, -1); rb_define_global_function("caller_locations", rb_f_caller_locations, -1); - rb_define_singleton_method(rb_cThread, "each_caller_location", each_caller_location, 0); + rb_define_singleton_method(rb_cThread, "each_caller_location", each_caller_location, -1); } /* debugger API */ @@ -1364,9 +1493,8 @@ RUBY_SYMBOL_EXPORT_END struct rb_debug_inspector_struct { rb_execution_context_t *ec; rb_control_frame_t *cfp; - VALUE backtrace; VALUE contexts; /* [[klass, binding, iseq, cfp], ...] */ - long backtrace_size; + VALUE raw_backtrace; }; enum { @@ -1375,18 +1503,22 @@ enum { CALLER_BINDING_BINDING, CALLER_BINDING_ISEQ, CALLER_BINDING_CFP, + CALLER_BINDING_LOC, CALLER_BINDING_DEPTH, }; struct collect_caller_bindings_data { VALUE ary; const rb_execution_context_t *ec; + VALUE btobj; + rb_backtrace_t *bt; }; static void -collect_caller_bindings_init(void *arg, size_t size) +collect_caller_bindings_init(void *arg, size_t num_frames) { - /* */ + struct collect_caller_bindings_data *data = (struct collect_caller_bindings_data *)arg; + data->btobj = backtrace_alloc_capa(num_frames, &data->bt); } static VALUE @@ -1424,6 +1556,14 @@ collect_caller_bindings_iseq(void *arg, const rb_control_frame_t *cfp) rb_ary_store(frame, CALLER_BINDING_BINDING, GC_GUARDED_PTR(cfp)); /* create later */ rb_ary_store(frame, CALLER_BINDING_ISEQ, cfp->iseq ? (VALUE)cfp->iseq : Qnil); rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp)); + + rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++]; + RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); + RB_OBJ_WRITE(data->btobj, &loc->iseq, cfp->iseq); + loc->pc = cfp->pc; + VALUE vloc = location_create(loc, (void *)data->btobj); + rb_ary_store(frame, CALLER_BINDING_LOC, vloc); + rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp))); rb_ary_push(data->ary, frame); @@ -1440,6 +1580,14 @@ collect_caller_bindings_cfunc(void *arg, const rb_control_frame_t *cfp, ID mid) rb_ary_store(frame, CALLER_BINDING_BINDING, Qnil); /* not available */ rb_ary_store(frame, CALLER_BINDING_ISEQ, Qnil); /* not available */ rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp)); + + rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++]; + RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); + loc->iseq = NULL; + loc->pc = NULL; + VALUE vloc = location_create(loc, (void *)data->btobj); + rb_ary_store(frame, CALLER_BINDING_LOC, vloc); + rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp))); rb_ary_push(data->ary, frame); @@ -1488,15 +1636,19 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data) rb_execution_context_t *ec = GET_EC(); enum ruby_tag_type state; volatile VALUE MAYBE_UNUSED(result); + int i; /* escape all env to heap */ rb_vm_stack_to_heap(ec); dbg_context.ec = ec; dbg_context.cfp = dbg_context.ec->cfp; - dbg_context.backtrace = rb_ec_backtrace_location_ary(ec, BACKTRACE_START, ALL_BACKTRACE_LINES, FALSE); - dbg_context.backtrace_size = RARRAY_LEN(dbg_context.backtrace); dbg_context.contexts = collect_caller_bindings(ec); + dbg_context.raw_backtrace = rb_ary_new(); + for (i=0; i<RARRAY_LEN(dbg_context.contexts); i++) { + VALUE frame = rb_ary_entry(dbg_context.contexts, i); + rb_ary_push(dbg_context.raw_backtrace, rb_ary_entry(frame, CALLER_BINDING_LOC)); + } EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { @@ -1516,7 +1668,7 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data) static VALUE frame_get(const rb_debug_inspector_t *dc, long index) { - if (index < 0 || index >= dc->backtrace_size) { + if (index < 0 || index >= RARRAY_LEN(dc->contexts)) { rb_raise(rb_eArgError, "no such frame"); } return rb_ary_entry(dc->contexts, index); @@ -1569,15 +1721,15 @@ rb_debug_inspector_current_depth(void) VALUE rb_debug_inspector_backtrace_locations(const rb_debug_inspector_t *dc) { - return dc->backtrace; + return dc->raw_backtrace; } -int -rb_profile_frames(int start, int limit, VALUE *buff, int *lines) +static int +thread_profile_frames(rb_execution_context_t *ec, int start, int limit, VALUE *buff, int *lines) { int i; - const rb_execution_context_t *ec = GET_EC(); const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); + const rb_control_frame_t *top = cfp; const rb_callable_method_entry_t *cme; // If this function is called inside a thread after thread creation, but @@ -1592,15 +1744,15 @@ rb_profile_frames(int start, int limit, VALUE *buff, int *lines) // Skip dummy frame; see `rb_ec_partial_backtrace_object` for details end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp); - for (i=0; i<limit && cfp != end_cfp;) { - if (VM_FRAME_RUBYFRAME_P(cfp) && cfp->pc != 0) { + for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { + if (VM_FRAME_RUBYFRAME_P_UNCHECKED(cfp) && cfp->pc != 0) { if (start > 0) { start--; continue; } /* record frame info */ - cme = rb_vm_frame_method_entry(cfp); + cme = rb_vm_frame_method_entry_unchecked(cfp); if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ) { buff[i] = (VALUE)cme; } @@ -1608,24 +1760,68 @@ rb_profile_frames(int start, int limit, VALUE *buff, int *lines) buff[i] = (VALUE)cfp->iseq; } - if (lines) lines[i] = calc_lineno(cfp->iseq, cfp->pc); + if (lines) { + const VALUE *pc = cfp->pc; + VALUE *iseq_encoded = ISEQ_BODY(cfp->iseq)->iseq_encoded; + VALUE *pc_end = iseq_encoded + ISEQ_BODY(cfp->iseq)->iseq_size; + + // The topmost frame may have an invalid PC because the JIT + // may leave it uninitialized for speed. JIT code must update the PC + // before entering a non-leaf method (so that `caller` will work), + // so only the topmost frame could possibly have an out-of-date PC. + // ZJIT doesn't set `cfp->jit_return`, so it's not a reliable signal. + // TODO(zjit): lightweight frames potentially makes more than + // the top most frame invalid. + // + // Avoid passing invalid PC to calc_lineno() to avoid crashing. + if (cfp == top && (pc < iseq_encoded || pc > pc_end)) { + lines[i] = 0; + } + else { + lines[i] = calc_lineno(cfp->iseq, pc); + } + } i++; } else { - cme = rb_vm_frame_method_entry(cfp); + cme = rb_vm_frame_method_entry_unchecked(cfp); if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) { + if (start > 0) { + start--; + continue; + } buff[i] = (VALUE)cme; if (lines) lines[i] = 0; i++; } } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); } return i; } +int +rb_profile_frames(int start, int limit, VALUE *buff, int *lines) +{ + rb_execution_context_t *ec = rb_current_execution_context(false); + + // If there is no EC, we may be attempting to profile a non-Ruby thread or a + // M:N shared native thread which has no active Ruby thread. + if (!ec) { + return 0; + } + + return thread_profile_frames(ec, start, limit, buff, lines); +} + +int +rb_profile_thread_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines) +{ + rb_thread_t *th = rb_thread_ptr(thread); + return thread_profile_frames(th->ec, start, limit, buff, lines); +} + static const rb_iseq_t * frame2iseq(VALUE frame) { @@ -1691,7 +1887,7 @@ rb_profile_frame_absolute_path(VALUE frame) static VALUE cfunc_str = Qfalse; if (!cfunc_str) { cfunc_str = rb_str_new_literal("<cfunc>"); - rb_gc_register_mark_object(cfunc_str); + rb_vm_register_global_object(cfunc_str); } return cfunc_str; } @@ -1744,8 +1940,8 @@ rb_profile_frame_classpath(VALUE frame) if (RB_TYPE_P(klass, T_ICLASS)) { klass = RBASIC(klass)->klass; } - else if (FL_TEST(klass, FL_SINGLETON)) { - klass = rb_ivar_get(klass, id__attached__); + else if (RCLASS_SINGLETON_P(klass)) { + klass = RCLASS_ATTACHED_OBJECT(klass); if (!RB_TYPE_P(klass, T_CLASS) && !RB_TYPE_P(klass, T_MODULE)) return rb_sprintf("#<%s:%p>", rb_class2name(rb_obj_class(klass)), (void*)klass); } @@ -1761,7 +1957,7 @@ rb_profile_frame_singleton_method_p(VALUE frame) { VALUE klass = frame2klass(frame); - return RBOOL(klass && !NIL_P(klass) && FL_TEST(klass, FL_SINGLETON)); + return RBOOL(klass && !NIL_P(klass) && RCLASS_SINGLETON_P(klass)); } VALUE |
