summaryrefslogtreecommitdiff
path: root/vm_backtrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'vm_backtrace.c')
-rw-r--r--vm_backtrace.c147
1 files changed, 104 insertions, 43 deletions
diff --git a/vm_backtrace.c b/vm_backtrace.c
index 12e4b771e2..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;
}
@@ -400,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;
@@ -576,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);
@@ -617,7 +611,7 @@ backtrace_size(const rb_execution_context_t *ec)
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;
}
@@ -687,17 +681,17 @@ 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 {
- bool internal = is_internal_location(cfp->iseq);
+ 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;
@@ -752,12 +746,12 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram
// 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->iseq))) {
+ 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_backpatch_loc(backpatch_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 - backpatch_counter, backpatch_counter, btobj);
+ bt_yield_loc(loc - backpatch_counter + 1, backpatch_counter, btobj);
}
break;
}
@@ -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.
@@ -1550,17 +1600,18 @@ 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, cfp->iseq);
- loc->pc = cfp->pc;
+ 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);
@@ -1745,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--;