summaryrefslogtreecommitdiff
path: root/vm_backtrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'vm_backtrace.c')
-rw-r--r--vm_backtrace.c266
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--;