diff options
Diffstat (limited to 'vm_backtrace.c')
| -rw-r--r-- | vm_backtrace.c | 266 |
1 files changed, 175 insertions, 91 deletions
diff --git a/vm_backtrace.c b/vm_backtrace.c index 26e0a6fb76..85a0658612 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -13,11 +13,13 @@ #include "internal.h" #include "internal/class.h" #include "internal/error.h" +#include "internal/object.h" #include "internal/vm.h" #include "iseq.h" #include "ruby/debug.h" #include "ruby/encoding.h" #include "vm_core.h" +#include "zjit.h" static VALUE rb_cBacktrace; static VALUE rb_cBacktraceLocation; @@ -44,7 +46,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; } @@ -101,9 +103,9 @@ calc_node_id(const rb_iseq_t *iseq, const VALUE *pc) int rb_vm_get_sourceline(const rb_control_frame_t *cfp) { - if (VM_FRAME_RUBYFRAME_P(cfp) && cfp->iseq) { - const rb_iseq_t *iseq = cfp->iseq; - int line = calc_lineno(iseq, cfp->pc); + if (VM_FRAME_RUBYFRAME_P(cfp) && CFP_ISEQ(cfp)) { + const rb_iseq_t *iseq = CFP_ISEQ(cfp); + int line = calc_lineno(iseq, CFP_PC(cfp)); if (line != 0) { return line; } @@ -262,6 +264,15 @@ retry: } } +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) @@ -272,7 +283,7 @@ location_cfunc_p(rb_backtrace_location_t *loc) case VM_METHOD_TYPE_CFUNC: return true; case VM_METHOD_TYPE_ISEQ: - return rb_iseq_attr_p(loc->cme->def->body.iseq.iseqptr, BUILTIN_ATTR_C_TRACE); + return is_internal_location(loc->cme->def->body.iseq.iseqptr); default: return false; } @@ -391,7 +402,7 @@ location_path_m(VALUE self) static int location_node_id(rb_backtrace_location_t *loc) { - if (loc->iseq && loc->pc) { + if (loc->iseq) { return calc_node_id(loc->iseq, loc->pc); } return -1; @@ -452,6 +463,7 @@ location_format(VALUE file, int lineno, VALUE name) else { rb_str_catf(s, "'%s'", RSTRING_PTR(name)); } + RB_GC_GUARD(name); return s; } @@ -566,14 +578,6 @@ rb_backtrace_p(VALUE obj) } static VALUE -backtrace_alloc(VALUE klass) -{ - rb_backtrace_t *bt; - VALUE obj = TypedData_Make_Struct(klass, rb_backtrace_t, &backtrace_data_type, bt); - 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); @@ -605,27 +609,18 @@ backtrace_size(const rb_execution_context_t *ec) } static bool -is_internal_location(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; -} - -static bool is_rescue_or_ensure_frame(const rb_control_frame_t *cfp) { - enum rb_iseq_type type = ISEQ_BODY(cfp->iseq)->type; + enum rb_iseq_type type = ISEQ_BODY(CFP_ISEQ(cfp))->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; } } @@ -648,7 +643,7 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram rb_backtrace_t *bt = NULL; VALUE btobj = Qnil; rb_backtrace_location_t *loc = NULL; - unsigned long cfunc_counter = 0; + 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. @@ -686,31 +681,46 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram } for (; cfp != end_cfp && (bt->backtrace_size < num_frames); cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { - if (cfp->iseq) { - if (cfp->pc) { + if (CFP_ISEQ(cfp)) { + if (CFP_PC(cfp)) { if (start_frame > 0) { start_frame--; } - else if (!(skip_internal && is_internal_location(cfp))) { + else { + bool internal = is_internal_location(CFP_ISEQ(cfp)); + if (skip_internal && internal) continue; if (!skip_next_frame) { - const rb_iseq_t *iseq = cfp->iseq; - const VALUE *pc = cfp->pc; + const rb_iseq_t *iseq = CFP_ISEQ(cfp); + const VALUE *pc = CFP_PC(cfp); + 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)); - // Ruby methods with `Primitive.attr! :c_trace` should behave like C methods - if (rb_iseq_attr_p(cfp->iseq, BUILTIN_ATTR_C_TRACE)) { - loc->iseq = NULL; - loc->pc = NULL; - cfunc_counter++; + // 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); - loc->pc = pc; - bt_update_cfunc_loc(cfunc_counter, loc-1, iseq, pc); + 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 - cfunc_counter, cfunc_counter+1, btobj); + bt_yield_loc(loc - backpatch_counter, backpatch_counter+1, btobj); } - cfunc_counter = 0; + backpatch_counter = 0; } } skip_next_frame = is_rescue_or_ensure_frame(cfp); @@ -727,21 +737,21 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); loc->iseq = NULL; loc->pc = NULL; - cfunc_counter++; + backpatch_counter++; } } } // 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 (cfunc_counter > 0) { + 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))) { + if (CFP_ISEQ(cfp) && CFP_PC(cfp) && !(skip_internal && is_internal_location(CFP_ISEQ(cfp)))) { VM_ASSERT(!skip_next_frame); // ISEQ_TYPE_RESCUE/ISEQ_TYPE_ENSURE should have a caller Ruby ISEQ, not a cfunc - bt_update_cfunc_loc(cfunc_counter, loc, cfp->iseq, cfp->pc); - RB_OBJ_WRITTEN(btobj, Qundef, cfp->iseq); + bt_backpatch_loc(backpatch_counter, loc, CFP_ISEQ(cfp), CFP_PC(cfp)); + RB_OBJ_WRITTEN(btobj, Qundef, CFP_ISEQ(cfp)); if (do_yield) { - bt_yield_loc(loc - cfunc_counter, cfunc_counter, btobj); + bt_yield_loc(loc - backpatch_counter + 1, backpatch_counter, btobj); } break; } @@ -803,22 +813,6 @@ rb_backtrace_to_str_ary(VALUE self) return bt->strary; } -void -rb_backtrace_use_iseq_first_lineno_for_last_location(VALUE self) -{ - rb_backtrace_t *bt; - rb_backtrace_location_t *loc; - - TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); - VM_ASSERT(bt->backtrace_size > 0); - - loc = &bt->backtrace[0]; - - VM_ASSERT(!loc->cme || loc->cme->def->type == VM_METHOD_TYPE_ISEQ); - - loc->pc = NULL; // means location.first_lineno -} - static VALUE location_create(rb_backtrace_location_t *srcloc, void *btobj) { @@ -858,7 +852,7 @@ rb_backtrace_to_location_ary(VALUE self) VALUE rb_location_ary_to_backtrace(VALUE ary) { - if (!RB_TYPE_P(ary, T_ARRAY) || !rb_frame_info_p(RARRAY_AREF(ary, 0))) { + if (!RB_TYPE_P(ary, T_ARRAY) || RARRAY_LEN(ary) == 0 || !rb_frame_info_p(RARRAY_AREF(ary, 0))) { return Qfalse; } @@ -961,6 +955,47 @@ backtrace_limit(VALUE self) return LONG2NUM(rb_backtrace_length_limit); } +/* :nodoc: */ +static VALUE +backtrace_clone(VALUE self) +{ + rb_backtrace_t *bt; + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); + + rb_backtrace_t *other_bt; + VALUE clone = backtrace_alloc_capa(bt->backtrace_size, &other_bt); + + rb_obj_clone_setup(self, clone, Qfalse); + + return clone; +} + +/* :nodoc: */ +static VALUE +backtrace_dup(VALUE self) +{ + rb_notimplement(); + + UNREACHABLE_RETURN(Qnil); +} + +/* :nodoc: */ +static VALUE +backtrace_initialize_copy(VALUE self, VALUE original) +{ + rb_backtrace_t *bt; + TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); + + rb_backtrace_t *original_bt; + TypedData_Get_Struct(original, rb_backtrace_t, &backtrace_data_type, original_bt); + + bt->backtrace_size = original_bt->backtrace_size; + MEMCPY(bt->backtrace, original_bt->backtrace, rb_backtrace_location_t, original_bt->backtrace_size); + rb_gc_writebarrier_remember(self); + + return Qnil; +} + VALUE rb_ec_backtrace_str_ary(const rb_execution_context_t *ec, long lev, long n) { @@ -1019,8 +1054,8 @@ backtrace_each(const rb_execution_context_t *ec, /* SDR(); */ for (i=0, cfp = start_cfp; i<size; i++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { /* fprintf(stderr, "cfp: %d\n", (rb_control_frame_t *)(ec->vm_stack + ec->vm_stack_size) - cfp); */ - if (cfp->iseq) { - if (cfp->pc) { + if (CFP_ISEQ(cfp)) { + if (CFP_PC(cfp)) { iter_iseq(arg, cfp); } } @@ -1052,8 +1087,8 @@ oldbt_init(void *ptr, size_t dmy) static void oldbt_iter_iseq(void *ptr, const rb_control_frame_t *cfp) { - const rb_iseq_t *iseq = cfp->iseq; - const VALUE *pc = cfp->pc; + const rb_iseq_t *iseq = CFP_ISEQ(cfp); + const VALUE *pc = CFP_PC(cfp); struct oldbt_arg *arg = (struct oldbt_arg *)ptr; VALUE file = arg->filename = rb_iseq_path(iseq); VALUE name = ISEQ_BODY(iseq)->location.label; @@ -1405,6 +1440,14 @@ each_caller_location(int argc, VALUE *argv, VALUE _) return Qnil; } +static VALUE +backtrace_no_allocator(VALUE klass) +{ + rb_notimplement(); + + UNREACHABLE_RETURN(Qnil); +} + /* called from Init_vm() in vm.c */ void Init_vm_backtrace(void) @@ -1415,11 +1458,18 @@ Init_vm_backtrace(void) * settings of the current session. */ rb_cBacktrace = rb_define_class_under(rb_cThread, "Backtrace", rb_cObject); - rb_define_alloc_func(rb_cBacktrace, backtrace_alloc); - rb_undef_method(CLASS_OF(rb_cBacktrace), "new"); + + // Can't undefine the allocator, as it's needed as a key by Marshal + rb_define_alloc_func(rb_cBacktrace, backtrace_no_allocator); rb_marshal_define_compat(rb_cBacktrace, rb_cArray, backtrace_dump_data, backtrace_load_data); + + rb_undef_method(CLASS_OF(rb_cBacktrace), "new"); rb_define_singleton_method(rb_cBacktrace, "limit", backtrace_limit, 0); + rb_define_method(rb_cBacktrace, "clone", backtrace_clone, 0); + rb_define_method(rb_cBacktrace, "dup", backtrace_dup, 0); + rb_define_method(rb_cBacktrace, "initialize_copy", backtrace_initialize_copy, 1); + /* * An object representation of a stack frame, initialized by * Kernel#caller_locations. @@ -1493,9 +1543,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 { @@ -1504,18 +1553,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 @@ -1547,12 +1600,21 @@ collect_caller_bindings_iseq(void *arg, const rb_control_frame_t *cfp) { struct collect_caller_bindings_data *data = (struct collect_caller_bindings_data *)arg; VALUE frame = rb_ary_new2(6); + const rb_iseq_t *iseq = CFP_ISEQ(cfp); rb_ary_store(frame, CALLER_BINDING_SELF, cfp->self); rb_ary_store(frame, CALLER_BINDING_CLASS, get_klass(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_ISEQ, iseq ? (VALUE)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, iseq); + loc->pc = CFP_PC(cfp); + 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); @@ -1569,6 +1631,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); @@ -1617,15 +1687,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, RUBY_BACKTRACE_START, RUBY_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) { @@ -1645,7 +1719,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); @@ -1698,7 +1772,7 @@ 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; } static int @@ -1722,38 +1796,48 @@ thread_profile_frames(rb_execution_context_t *ec, int start, int limit, VALUE *b end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp); for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { - if (VM_FRAME_RUBYFRAME_P(cfp) && cfp->pc != 0) { + if (VM_FRAME_RUBYFRAME_P_UNCHECKED(cfp) && CFP_PC(cfp)) { if (start > 0) { start--; continue; } /* record frame info */ - cme = rb_vm_frame_method_entry(cfp); + cme = rb_vm_frame_method_entry_unchecked(cfp); + const rb_iseq_t *iseq = CFP_ISEQ(cfp); if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ) { buff[i] = (VALUE)cme; } else { - buff[i] = (VALUE)cfp->iseq; + buff[i] = (VALUE)iseq; } if (lines) { - // The topmost frame may not have an updated PC because the JIT - // may not have set one. The JIT compiler will update the PC - // before entering a new function (so that `caller` will work), - // so only the topmost frame could possibly have an out of date PC - if (cfp == top && cfp->jit_return) { + const VALUE *pc = CFP_PC(cfp); + VALUE *iseq_encoded = ISEQ_BODY(iseq)->iseq_encoded; + VALUE *pc_end = iseq_encoded + ISEQ_BODY(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, cfp->pc); + lines[i] = calc_lineno(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--; |
