From caa6ba1a46afa1bc696adc5fe91ee992f9570c89 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 4 Jun 2025 16:44:28 +0900 Subject: [PATCH] Make `rb_debug_inspector_backtrace_locations` return a raw backtrace Previously, a user of the debug inspector API was supposed to use `rb_debug_inspector_backtrace_locations` to get an array of `Thread::Backtrace::Location`, and then used its index to get more information from `rb_debug_inspector _frame_binding_get(index)`, etc. However, `rb_debug_inspector_backtrace_locations` returned an array of backtraces excluding rescue/ensure frames. On the other hand, `rb_debug_inspector_frame_binding_get(index)` interprets index with rescue/ensure frames. This led to inconsistency of the index, and it was very difficult to correctly use the debug inspector API. This is a minimal fix for the issue by making `rb_debug_inspector_backtrace_locations` return a raw backtrace including rescue/ensure frames. --- vm_backtrace.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/vm_backtrace.c b/vm_backtrace.c index 26e0a6fb76..9046f4aa29 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -1493,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 { @@ -1504,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 @@ -1553,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); @@ -1569,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); @@ -1617,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, 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= 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 +1721,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