Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into pr/59

  • Loading branch information...
commit 743a2bf16f81714b3b25fce20c1ef94f45a9b979 2 parents da1657b + e98dc57
@cldwalker authored
View
4 .travis.yml
@@ -2,3 +2,7 @@ before_script: rake compile
rvm:
- 1.9.2
- 1.9.3
+- 2.0.0
+matrix:
+ allow_failures:
+ - rvm: 2.0.0
View
3  CHANGELOG.md
@@ -1,3 +1,6 @@
+## 1.4.0
+* Partial support for 2.0.0
+
## 1.3.3
* Bump ruby_core_source dependency
View
2  README.md
@@ -104,6 +104,7 @@ tutorial](http://bashdb.sourceforge.net/ruby-debug/rdebug-emacs.html)
Please report them [on github](http://github.com/cldwalker/debugger/issues).
## Known Issues
+* 2.0.0 support is only partially working and the test suite dies before it can finish.
* If you place a debugger call at the end of a block, debugging will start at the next step and
outside of your block. A simple work-around is to place a meaningless step (i.e. puts "STAY")
at the end of your block and before debugger.
@@ -128,6 +129,7 @@ Let's keep this working for the ruby community!
* Thanks to the original authors: Kent Sibilev and Mark Moseley
* Thanks to astashov for bringing in a new and improved test suite and various bug fixes.
+* Thanks to windwiny for porting to 2.0.0
* Contributors: ericpromislow, jnimety, adammck, hipe, FooBarWidget, aghull
* Fork started on awesome @relevance fridays!
View
586 ext/ruby_debug/200/breakpoint.c
@@ -0,0 +1,586 @@
+#include <ruby.h>
+#include <stdio.h>
+#include <vm_core.h>
+#include <iseq.h>
+#include "ruby_debug.h"
+
+VALUE rdebug_breakpoints = Qnil;
+VALUE rdebug_catchpoints;
+
+static VALUE cBreakpoint;
+static ID idEval;
+
+static VALUE
+eval_expression(VALUE args)
+{
+ return rb_funcall2(rb_mKernel, idEval, 2, RARRAY_PTR(args));
+}
+
+int
+check_breakpoint_hit_condition(VALUE breakpoint)
+{
+ debug_breakpoint_t *debug_breakpoint;
+
+ if(breakpoint == Qnil)
+ return 0;
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+
+ debug_breakpoint->hit_count++;
+ if (!Qtrue == debug_breakpoint->enabled) return 0;
+ switch(debug_breakpoint->hit_condition)
+ {
+ case HIT_COND_NONE:
+ return 1;
+ case HIT_COND_GE:
+ {
+ if(debug_breakpoint->hit_count >= debug_breakpoint->hit_value)
+ return 1;
+ break;
+ }
+ case HIT_COND_EQ:
+ {
+ if(debug_breakpoint->hit_count == debug_breakpoint->hit_value)
+ return 1;
+ break;
+ }
+ case HIT_COND_MOD:
+ {
+ if(debug_breakpoint->hit_count % debug_breakpoint->hit_value == 0)
+ return 1;
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+check_breakpoint_by_pos(VALUE breakpoint, char *file, int line)
+{
+ debug_breakpoint_t *debug_breakpoint;
+
+ if(breakpoint == Qnil)
+ return 0;
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if (!Qtrue == debug_breakpoint->enabled) return 0;
+ if(debug_breakpoint->type != BP_POS_TYPE)
+ return 0;
+ if(debug_breakpoint->pos.line != line)
+ return 0;
+ if(filename_cmp(debug_breakpoint->source, file))
+ return 1;
+ return 0;
+}
+
+int
+check_breakpoint_by_method(VALUE breakpoint, VALUE klass, ID mid, VALUE self)
+{
+ debug_breakpoint_t *debug_breakpoint;
+
+ if(breakpoint == Qnil)
+ return 0;
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if (!Qtrue == debug_breakpoint->enabled) return 0;
+ if(debug_breakpoint->type != BP_METHOD_TYPE)
+ return 0;
+ if(debug_breakpoint->pos.mid != mid)
+ return 0;
+ if(classname_cmp(debug_breakpoint->source, klass))
+ return 1;
+ if ((rb_type(self) == T_CLASS) &&
+ classname_cmp(debug_breakpoint->source, self))
+ return 1;
+ return 0;
+}
+
+VALUE
+check_breakpoints_by_pos(debug_context_t *debug_context, char *file, int line)
+{
+ VALUE breakpoint;
+ int i;
+
+ if(!CTX_FL_TEST(debug_context, CTX_FL_ENABLE_BKPT))
+ return Qnil;
+
+ if(check_breakpoint_by_pos(debug_context->breakpoint, file, line))
+ return debug_context->breakpoint;
+
+ if(RARRAY_LEN(rdebug_breakpoints) == 0)
+ return Qnil;
+ for(i = 0; i < RARRAY_LEN(rdebug_breakpoints); i++)
+ {
+ breakpoint = rb_ary_entry(rdebug_breakpoints, i);
+ if(check_breakpoint_by_pos(breakpoint, file, line))
+ return breakpoint;
+ }
+ return Qnil;
+}
+
+VALUE
+check_breakpoints_by_method(debug_context_t *debug_context, VALUE klass, ID mid, VALUE self)
+{
+ VALUE breakpoint;
+ int i;
+
+ if(!CTX_FL_TEST(debug_context, CTX_FL_ENABLE_BKPT))
+ return Qnil;
+
+ if(check_breakpoint_by_method(debug_context->breakpoint, klass, mid, self))
+ return debug_context->breakpoint;
+
+ if(RARRAY_LEN(rdebug_breakpoints) == 0)
+ return Qnil;
+ for(i = 0; i < RARRAY_LEN(rdebug_breakpoints); i++)
+ {
+ breakpoint = rb_ary_entry(rdebug_breakpoints, i);
+ if(check_breakpoint_by_method(breakpoint, klass, mid, self))
+ return breakpoint;
+ }
+ return Qnil;
+}
+
+int
+check_breakpoint_expression(VALUE breakpoint, VALUE binding)
+{
+ debug_breakpoint_t *debug_breakpoint;
+ VALUE args, expr_result;
+
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if(NIL_P(debug_breakpoint->expr))
+ return 1;
+
+ args = rb_ary_new3(2, debug_breakpoint->expr, binding);
+ expr_result = rb_protect(eval_expression, args, 0);
+ return RTEST(expr_result);
+}
+
+static void
+breakpoint_mark(void *data)
+{
+ debug_breakpoint_t *breakpoint;
+ breakpoint = (debug_breakpoint_t *)data;
+ rb_gc_mark(breakpoint->source);
+ rb_gc_mark(breakpoint->expr);
+}
+
+VALUE
+create_breakpoint_from_args(int argc, VALUE *argv, int id)
+{
+ VALUE source, pos, expr;
+ debug_breakpoint_t *breakpoint;
+ int type;
+
+ if(rb_scan_args(argc, argv, "21", &source, &pos, &expr) == 2)
+ {
+ expr = Qnil;
+ }
+ type = FIXNUM_P(pos) ? BP_POS_TYPE : BP_METHOD_TYPE;
+ if(type == BP_POS_TYPE)
+ source = StringValue(source);
+ else
+ pos = StringValue(pos);
+ breakpoint = ALLOC(debug_breakpoint_t);
+ breakpoint->id = id;
+ breakpoint->source = source;
+ breakpoint->type = type;
+ if(type == BP_POS_TYPE)
+ breakpoint->pos.line = FIX2INT(pos);
+ else
+ breakpoint->pos.mid = rb_intern(RSTRING_PTR(pos));
+ breakpoint->enabled = Qtrue;
+ breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
+ breakpoint->hit_count = 0;
+ breakpoint->hit_value = 0;
+ breakpoint->hit_condition = HIT_COND_NONE;
+ return Data_Wrap_Struct(cBreakpoint, breakpoint_mark, xfree, breakpoint);
+}
+
+/*
+ * call-seq:
+ * Debugger.remove_breakpoint(id) -> breakpoint
+ *
+ * Removes breakpoint by its id.
+ * <i>id</i> is an identificator of a breakpoint.
+ */
+VALUE
+rdebug_remove_breakpoint(VALUE self, VALUE id_value)
+{
+ int i;
+ int id;
+ VALUE breakpoint;
+ debug_breakpoint_t *debug_breakpoint;
+
+ id = FIX2INT(id_value);
+
+ for( i = 0; i < RARRAY_LEN(rdebug_breakpoints); i += 1 )
+ {
+ breakpoint = rb_ary_entry(rdebug_breakpoints, i);
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if(debug_breakpoint->id == id)
+ {
+ rb_ary_delete_at(rdebug_breakpoints, i);
+ return breakpoint;
+ }
+ }
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * Debugger.catchpoints -> hash
+ *
+ * Returns a current catchpoints, which is a hash exception names that will
+ * trigger a debugger when raised. The values are the number of times taht
+ * catchpoint was hit, initially 0.
+ */
+VALUE
+debug_catchpoints(VALUE self)
+{
+ debug_check_started();
+
+ return rdebug_catchpoints;
+}
+
+/*
+ * call-seq:
+ * Debugger.catchpoint(string) -> string
+ *
+ * Sets catchpoint. Returns the string passed.
+ */
+VALUE
+rdebug_add_catchpoint(VALUE self, VALUE value)
+{
+ debug_check_started();
+
+ if (TYPE(value) != T_STRING) {
+ rb_raise(rb_eTypeError, "value of a catchpoint must be String");
+ }
+ rb_hash_aset(rdebug_catchpoints, rb_str_dup(value), INT2FIX(0));
+ return value;
+}
+
+/*
+ * call-seq:
+ * context.breakpoint -> breakpoint
+ *
+ * Returns a context-specific temporary Breakpoint object.
+ */
+VALUE
+context_breakpoint(VALUE self)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return debug_context->breakpoint;
+}
+
+/*
+ * call-seq:
+ * context.set_breakpoint(source, pos, condition = nil) -> breakpoint
+ *
+ * Sets a context-specific temporary breakpoint, which can be used to implement
+ * 'Run to Cursor' debugger function. When this breakpoint is reached, it will be
+ * cleared out.
+ *
+ * <i>source</i> is a name of a file or a class.
+ * <i>pos</i> is a line number or a method name if <i>source</i> is a class name.
+ * <i>condition</i> is a string which is evaluated to +true+ when this breakpoint
+ * is activated.
+ */
+VALUE
+context_set_breakpoint(int argc, VALUE *argv, VALUE self)
+{
+ VALUE result;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ result = create_breakpoint_from_args(argc, argv, 0);
+ debug_context->breakpoint = result;
+ return result;
+}
+
+/*
+ * call-seq:
+ * breakpoint.enabled?
+ *
+ * Returns whether breakpoint is enabled or not.
+ */
+static VALUE
+breakpoint_enabled(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->enabled;
+}
+
+/*
+ * call-seq:
+ * breakpoint.enabled = bool
+ *
+ * Enables or disables breakpoint.
+ */
+static VALUE
+breakpoint_set_enabled(VALUE self, VALUE bool)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->enabled = bool;
+}
+
+/*
+ * call-seq:
+ * breakpoint.source -> string
+ *
+ * Returns a source of the breakpoint.
+ */
+static VALUE
+breakpoint_source(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->source;
+}
+
+/*
+ * call-seq:
+ * breakpoint.source = string
+ *
+ * Sets the source of the breakpoint.
+ */
+static VALUE
+breakpoint_set_source(VALUE self, VALUE value)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ breakpoint->source = StringValue(value);
+ return value;
+}
+
+/*
+ * call-seq:
+ * breakpoint.pos -> string or int
+ *
+ * Returns the position of this breakpoint.
+ */
+static VALUE
+breakpoint_pos(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ if(breakpoint->type == BP_METHOD_TYPE)
+ return rb_str_new2(rb_id2name(breakpoint->pos.mid));
+ else
+ return INT2FIX(breakpoint->pos.line);
+}
+
+/*
+ * call-seq:
+ * breakpoint.pos = string or int
+ *
+ * Sets the position of this breakpoint.
+ */
+static VALUE
+breakpoint_set_pos(VALUE self, VALUE value)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ if(breakpoint->type == BP_METHOD_TYPE)
+ {
+ breakpoint->pos.mid = rb_to_id(StringValue(value));
+ }
+ else
+ breakpoint->pos.line = FIX2INT(value);
+ return value;
+}
+
+/*
+ * call-seq:
+ * breakpoint.expr -> string
+ *
+ * Returns a codition expression when this breakpoint should be activated.
+ */
+static VALUE
+breakpoint_expr(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->expr;
+}
+
+/*
+ * call-seq:
+ * breakpoint.expr = string | nil
+ *
+ * Sets the codition expression when this breakpoint should be activated.
+ */
+static VALUE
+breakpoint_set_expr(VALUE self, VALUE expr)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
+ return expr;
+}
+
+/*
+ * call-seq:
+ * breakpoint.id -> int
+ *
+ * Returns id of the breakpoint.
+ */
+static VALUE
+breakpoint_id(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return INT2FIX(breakpoint->id);
+}
+
+/*
+ * call-seq:
+ * breakpoint.hit_count -> int
+ *
+ * Returns the hit count of the breakpoint.
+ */
+static VALUE
+breakpoint_hit_count(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return INT2FIX(breakpoint->hit_count);
+}
+
+/*
+ * call-seq:
+ * breakpoint.hit_value -> int
+ *
+ * Returns the hit value of the breakpoint.
+ */
+static VALUE
+breakpoint_hit_value(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return INT2FIX(breakpoint->hit_value);
+}
+
+/*
+ * call-seq:
+ * breakpoint.hit_value = int
+ *
+ * Sets the hit value of the breakpoint.
+ */
+static VALUE
+breakpoint_set_hit_value(VALUE self, VALUE value)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ breakpoint->hit_value = FIX2INT(value);
+ return value;
+}
+
+/*
+ * call-seq:
+ * breakpoint.hit_condition -> symbol
+ *
+ * Returns the hit condition of the breakpoint:
+ *
+ * +nil+ if it is an unconditional breakpoint, or
+ * :greater_or_equal, :equal, :modulo
+ */
+static VALUE
+breakpoint_hit_condition(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ switch(breakpoint->hit_condition)
+ {
+ case HIT_COND_GE:
+ return ID2SYM(rb_intern("greater_or_equal"));
+ case HIT_COND_EQ:
+ return ID2SYM(rb_intern("equal"));
+ case HIT_COND_MOD:
+ return ID2SYM(rb_intern("modulo"));
+ case HIT_COND_NONE:
+ default:
+ return Qnil;
+ }
+}
+
+/*
+ * call-seq:
+ * breakpoint.hit_condition = symbol
+ *
+ * Sets the hit condition of the breakpoint which must be one of the following values:
+ *
+ * +nil+ if it is an unconditional breakpoint, or
+ * :greater_or_equal(:ge), :equal(:eq), :modulo(:mod)
+ */
+static VALUE
+breakpoint_set_hit_condition(VALUE self, VALUE value)
+{
+ debug_breakpoint_t *breakpoint;
+ ID id_value;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ id_value = rb_to_id(value);
+
+ if(rb_intern("greater_or_equal") == id_value || rb_intern("ge") == id_value)
+ breakpoint->hit_condition = HIT_COND_GE;
+ else if(rb_intern("equal") == id_value || rb_intern("eq") == id_value)
+ breakpoint->hit_condition = HIT_COND_EQ;
+ else if(rb_intern("modulo") == id_value || rb_intern("mod") == id_value)
+ breakpoint->hit_condition = HIT_COND_MOD;
+ else
+ rb_raise(rb_eArgError, "Invalid condition parameter");
+ return value;
+}
+
+/*
+ * Document-class: Breakpoint
+ *
+ * == Summary
+ *
+ * This class represents a breakpoint. It defines position of the breakpoint and
+ * condition when this breakpoint should be triggered.
+ */
+void
+Init_breakpoint()
+{
+ cBreakpoint = rb_define_class_under(mDebugger, "Breakpoint", rb_cObject);
+ rb_define_method(cBreakpoint, "enabled=", breakpoint_set_enabled, 1);
+ rb_define_method(cBreakpoint, "enabled?", breakpoint_enabled, 0);
+ rb_define_method(cBreakpoint, "expr", breakpoint_expr, 0);
+ rb_define_method(cBreakpoint, "expr=", breakpoint_set_expr, 1);
+ rb_define_method(cBreakpoint, "hit_condition", breakpoint_hit_condition, 0);
+ rb_define_method(cBreakpoint, "hit_condition=", breakpoint_set_hit_condition, 1);
+ rb_define_method(cBreakpoint, "hit_count", breakpoint_hit_count, 0);
+ rb_define_method(cBreakpoint, "hit_value", breakpoint_hit_value, 0);
+ rb_define_method(cBreakpoint, "hit_value=", breakpoint_set_hit_value, 1);
+ rb_define_method(cBreakpoint, "id", breakpoint_id, 0);
+ rb_define_method(cBreakpoint, "pos", breakpoint_pos, 0);
+ rb_define_method(cBreakpoint, "pos=", breakpoint_set_pos, 1);
+ rb_define_method(cBreakpoint, "source", breakpoint_source, 0);
+ rb_define_method(cBreakpoint, "source=", breakpoint_set_source, 1);
+ idEval = rb_intern("eval");
+ rdebug_catchpoints = rb_hash_new();
+
+}
+
+
View
2,692 ext/ruby_debug/200/ruby_debug.c
@@ -0,0 +1,2692 @@
+#include <ruby.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <vm_core.h>
+#include <iseq.h>
+#include <version.h>
+#include <eval_intern.h>
+#include <insns.inc>
+#include <insns_info.inc>
+#include "ruby_debug.h"
+
+#define DEBUG_VERSION "0.11"
+
+#define FRAME_N(n) (&debug_context->frames[debug_context->stack_size-(n)-1])
+#define GET_FRAME (FRAME_N(check_frame_number(debug_context, frame)))
+
+#ifndef min
+#define min(x,y) ((x) < (y) ? (x) : (y))
+#endif
+
+#define STACK_SIZE_INCREMENT 128
+
+RUBY_EXTERN int rb_vm_get_sourceline(const rb_control_frame_t *cfp); /* from vm.c */
+
+/* from iseq.c */
+#ifdef HAVE_RB_ISEQ_COMPILE_ON_BASE
+VALUE rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE absolute_path, VALUE line, rb_block_t *base_block, VALUE opt);
+#elif RB_ISEQ_COMPILE_5ARGS
+RUBY_EXTERN VALUE rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE filepath, VALUE line, VALUE opt);
+#else
+RUBY_EXTERN VALUE rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE line, VALUE opt);
+#endif
+
+typedef struct {
+ st_table *tbl;
+} threads_table_t;
+
+static VALUE tracing = Qfalse;
+static VALUE locker = Qnil;
+static VALUE post_mortem = Qfalse;
+static VALUE keep_frame_binding = Qfalse;
+static VALUE debug = Qfalse;
+static VALUE track_frame_args = Qfalse;
+
+static VALUE last_context = Qnil;
+static VALUE last_thread = Qnil;
+static debug_context_t *last_debug_context = NULL;
+
+VALUE rdebug_threads_tbl = Qnil; /* Context for each of the threads */
+VALUE mDebugger; /* Ruby Debugger Module object */
+
+static VALUE opt_call_c_function;
+static VALUE cThreadsTable;
+static VALUE cContext;
+static VALUE cDebugThread;
+
+static VALUE rb_mObjectSpace;
+
+static ID idAtBreakpoint;
+static ID idAtCatchpoint;
+static ID idAtLine;
+static ID idAtReturn;
+static ID idAtTracing;
+static ID idList;
+
+static int start_count = 0;
+static int thnum_max = 0;
+static int bkp_count = 0;
+static int last_debugged_thnum = -1;
+static unsigned long last_check = 0;
+static unsigned long hook_count = 0;
+
+static VALUE create_binding(VALUE);
+static VALUE debug_stop(VALUE);
+static void save_current_position(debug_context_t *);
+static VALUE context_copy_args(debug_frame_t *);
+static VALUE context_copy_locals(debug_context_t *,debug_frame_t *, VALUE);
+static void context_suspend_0(debug_context_t *);
+static void context_resume_0(debug_context_t *);
+static void copy_scalar_args(debug_frame_t *);
+
+typedef struct locked_thread_t {
+ VALUE thread_id;
+ struct locked_thread_t *next;
+} locked_thread_t;
+
+static locked_thread_t *locked_head = NULL;
+static locked_thread_t *locked_tail = NULL;
+
+/* "Step", "Next" and "Finish" do their work by saving information
+ about where to stop next. reset_stopping_points removes/resets this
+ information. */
+inline static const char *
+get_event_name(rb_event_flag_t _event)
+{
+ switch (_event) {
+ case RUBY_EVENT_LINE:
+ return "line";
+ case RUBY_EVENT_CLASS:
+ return "class";
+ case RUBY_EVENT_END:
+ return "end";
+ case RUBY_EVENT_CALL:
+ return "call";
+ case RUBY_EVENT_RETURN:
+ return "return";
+ case RUBY_EVENT_C_CALL:
+ return "c-call";
+ case RUBY_EVENT_C_RETURN:
+ return "c-return";
+ case RUBY_EVENT_RAISE:
+ return "raise";
+ default:
+ return "unknown";
+ }
+}
+
+inline static void
+reset_stepping_stop_points(debug_context_t *debug_context)
+{
+ debug_context->dest_frame = -1;
+ debug_context->stop_line = -1;
+ debug_context->stop_next = -1;
+}
+
+inline static VALUE
+real_class(VALUE klass)
+{
+ if (klass) {
+ if (TYPE(klass) == T_ICLASS) {
+ return RBASIC(klass)->klass;
+ }
+ else if (FL_TEST(klass, FL_SINGLETON)) {
+ return rb_iv_get(klass, "__attached__");
+ }
+ }
+ return klass;
+}
+
+inline static VALUE
+ref2id(VALUE obj)
+{
+ return obj;
+}
+
+inline static VALUE
+id2ref(VALUE id)
+{
+ return id;
+}
+
+inline static VALUE
+context_thread_0(debug_context_t *debug_context)
+{
+ return id2ref(debug_context->thread_id);
+}
+
+static inline const rb_data_type_t *
+threadptr_data_type(void)
+{
+ static const rb_data_type_t *thread_data_type;
+ if (!thread_data_type)
+ {
+ VALUE current_thread = rb_thread_current();
+ thread_data_type = RTYPEDDATA_TYPE(current_thread);
+ }
+ return thread_data_type;
+}
+
+#define ruby_threadptr_data_type *threadptr_data_type()
+
+// On ruby 1.9.3-xxx GET_THREAD is a macro, on ruby 2.0.0-p0 is a function. <ruby-ver>/vm_core.h
+// CAREFUL: ruby_current_thread, GET_THREAD, rb_thread_set_current_raw
+#if GET_THREAD
+ #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
+ #define GET_THREAD2 GET_THREAD
+#else
+#warning "ruby 2.0.0-p0 GET_THREAD is a function."
+ rb_thread_t *ruby_current_thread;
+ rb_thread_t *GET_THREAD2(void)
+ {
+ ruby_current_thread = ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()));
+ return GET_THREAD();
+ }
+#endif
+
+static int
+is_in_locked(VALUE thread_id)
+{
+ locked_thread_t *node;
+
+ if(!locked_head)
+ return 0;
+
+ for(node = locked_head; node != locked_tail; node = node->next)
+ {
+ if(node->thread_id == thread_id) return 1;
+ }
+ return 0;
+}
+
+static void
+add_to_locked(VALUE thread)
+{
+ locked_thread_t *node;
+ VALUE thread_id = ref2id(thread);
+
+ if(is_in_locked(thread_id))
+ return;
+
+ node = ALLOC(locked_thread_t);
+ node->thread_id = thread_id;
+ node->next = NULL;
+ if(locked_tail)
+ locked_tail->next = node;
+ locked_tail = node;
+ if(!locked_head)
+ locked_head = node;
+}
+
+static VALUE
+remove_from_locked(void)
+{
+ VALUE thread;
+ locked_thread_t *node;
+
+ if(locked_head == NULL)
+ return Qnil;
+ node = locked_head;
+ locked_head = locked_head->next;
+ if(locked_tail == node)
+ locked_tail = NULL;
+ thread = id2ref(node->thread_id);
+ xfree(node);
+ return thread;
+}
+
+static int is_living_thread(VALUE thread);
+
+static int
+threads_table_mark_keyvalue(st_data_t key, st_data_t value, st_data_t tbl)
+{
+ VALUE thread = id2ref((VALUE)key);
+ if (!value)
+ return ST_CONTINUE;
+
+ rb_gc_mark((VALUE)value);
+ if (is_living_thread(thread))
+ rb_gc_mark(thread);
+ else
+ st_insert((st_table *)tbl, key, 0);
+
+ return ST_CONTINUE;
+}
+
+static void
+threads_table_mark(void* data)
+{
+ threads_table_t *threads_table = (threads_table_t*)data;
+ st_table *tbl = threads_table->tbl;
+ st_foreach(tbl, threads_table_mark_keyvalue, (st_data_t)tbl);
+}
+
+static void
+threads_table_free(void* data)
+{
+ threads_table_t *threads_table = (threads_table_t*)data;
+ st_free_table(threads_table->tbl);
+ xfree(threads_table);
+}
+
+static VALUE
+threads_table_create(void)
+{
+ threads_table_t *threads_table;
+
+ threads_table = ALLOC(threads_table_t);
+ threads_table->tbl = st_init_numtable();
+ return Data_Wrap_Struct(cThreadsTable, threads_table_mark, threads_table_free, threads_table);
+}
+
+static void
+threads_table_clear(VALUE table)
+{
+ threads_table_t *threads_table;
+
+ Data_Get_Struct(table, threads_table_t, threads_table);
+ st_clear(threads_table->tbl);
+}
+
+static int
+is_thread_alive(VALUE thread)
+{
+ rb_thread_t *th;
+ GetThreadPtr(thread, th);
+ return th->status != THREAD_KILLED;
+}
+
+static int
+is_living_thread(VALUE thread)
+{
+ return rb_obj_is_kind_of(thread, rb_cThread) && is_thread_alive(thread);
+}
+
+static int
+threads_table_check_i(st_data_t key, st_data_t value, st_data_t dummy)
+{
+ VALUE thread;
+
+ if(!value)
+ {
+ return ST_DELETE;
+ }
+ thread = id2ref((VALUE)key);
+ if(!is_living_thread(thread))
+ {
+ return ST_DELETE;
+ }
+ return ST_CONTINUE;
+}
+
+static void
+check_thread_contexts(void)
+{
+ threads_table_t *threads_table;
+
+ Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);
+ st_foreach(threads_table->tbl, threads_table_check_i, 0);
+}
+
+/*
+ * call-seq:
+ * Debugger.started? -> bool
+ *
+ * Returns +true+ the debugger is started.
+ */
+static VALUE
+debug_is_started(VALUE self)
+{
+ return IS_STARTED ? Qtrue : Qfalse;
+}
+
+static void
+debug_context_mark(void *data)
+{
+ debug_frame_t *frame;
+ int i;
+
+ debug_context_t *debug_context = (debug_context_t *)data;
+ for(i = 0; i < debug_context->stack_size; i++)
+ {
+ frame = &(debug_context->frames[i]);
+ rb_gc_mark(frame->binding);
+ rb_gc_mark(frame->self);
+ rb_gc_mark(frame->arg_ary);
+ if(frame->dead)
+ {
+ rb_gc_mark(frame->info.copy.locals);
+ rb_gc_mark(frame->info.copy.args);
+ }
+ }
+ rb_gc_mark(debug_context->breakpoint);
+}
+
+static void
+debug_context_free(void *data)
+{
+ debug_context_t *debug_context = (debug_context_t *)data;
+ xfree(debug_context->frames);
+}
+
+static VALUE
+debug_context_create(VALUE thread)
+{
+ debug_context_t *debug_context;
+
+ debug_context = ALLOC(debug_context_t);
+ debug_context-> thnum = ++thnum_max;
+
+ debug_context->last_file = NULL;
+ debug_context->last_line = 0;
+ debug_context->flags = 0;
+
+ debug_context->stop_next = -1;
+ debug_context->dest_frame = -1;
+ debug_context->stop_line = -1;
+ debug_context->stop_frame = -1;
+ debug_context->stop_reason = CTX_STOP_NONE;
+ debug_context->stack_len = STACK_SIZE_INCREMENT;
+ debug_context->frames = ALLOC_N(debug_frame_t, STACK_SIZE_INCREMENT);
+ debug_context->stack_size = 0;
+ debug_context->thread_id = ref2id(thread);
+ debug_context->breakpoint = Qnil;
+ debug_context->jump_pc = NULL;
+ debug_context->jump_cfp = NULL;
+ debug_context->old_iseq_catch = NULL;
+ debug_context->thread_pause = 0;
+ if(rb_obj_class(thread) == cDebugThread)
+ CTX_FL_SET(debug_context, CTX_FL_IGNORE);
+ return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, debug_context);
+}
+
+static VALUE
+debug_context_dup(debug_context_t *debug_context, VALUE self)
+{
+ debug_context_t *new_debug_context;
+ debug_frame_t *new_frame, *old_frame;
+ int i;
+
+ new_debug_context = ALLOC(debug_context_t);
+ memcpy(new_debug_context, debug_context, sizeof(debug_context_t));
+ new_debug_context->stop_next = -1;
+ new_debug_context->dest_frame = -1;
+ new_debug_context->stop_line = -1;
+ new_debug_context->stop_frame = -1;
+ new_debug_context->breakpoint = Qnil;
+ CTX_FL_SET(new_debug_context, CTX_FL_DEAD);
+ new_debug_context->frames = ALLOC_N(debug_frame_t, debug_context->stack_size);
+ new_debug_context->stack_len = debug_context->stack_size;
+ memcpy(new_debug_context->frames, debug_context->frames, sizeof(debug_frame_t) * debug_context->stack_size);
+ for(i = 0; i < debug_context->stack_size; i++)
+ {
+ new_frame = &(new_debug_context->frames[i]);
+ old_frame = &(debug_context->frames[i]);
+ new_frame->dead = 1;
+ new_frame->info.copy.args = context_copy_args(old_frame);
+ new_frame->info.copy.locals = context_copy_locals(debug_context, old_frame, self);
+ }
+ return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, new_debug_context);
+}
+
+static void
+thread_context_lookup(VALUE thread, VALUE *context, debug_context_t **debug_context, int create)
+{
+ threads_table_t *threads_table;
+ VALUE thread_id;
+ debug_context_t *l_debug_context;
+
+ debug_check_started();
+
+ if(last_thread == thread && last_context != Qnil)
+ {
+ *context = last_context;
+ if(debug_context)
+ *debug_context = last_debug_context;
+ return;
+ }
+ thread_id = ref2id(thread);
+ Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);
+ if(!st_lookup(threads_table->tbl, thread_id, context) || !*context)
+ {
+ if (create)
+ {
+ *context = debug_context_create(thread);
+ st_insert(threads_table->tbl, thread_id, *context);
+ }
+ else
+ {
+ *context = 0;
+ if (debug_context)
+ *debug_context = NULL;
+ return;
+ }
+ }
+
+ Data_Get_Struct(*context, debug_context_t, l_debug_context);
+ if(debug_context)
+ *debug_context = l_debug_context;
+
+ last_thread = thread;
+ last_context = *context;
+ last_debug_context = l_debug_context;
+}
+
+static VALUE
+call_at_line_unprotected(VALUE args)
+{
+ VALUE context;
+ context = *RARRAY_PTR(args);
+ return rb_funcall2(context, idAtLine, RARRAY_LEN(args) - 1, RARRAY_PTR(args) + 1);
+}
+
+static VALUE
+call_at_line(VALUE context, debug_context_t *debug_context, VALUE file, VALUE line)
+{
+ VALUE args;
+
+ last_debugged_thnum = debug_context->thnum;
+ save_current_position(debug_context);
+
+ args = rb_ary_new3(3, context, file, line);
+ return rb_protect(call_at_line_unprotected, args, 0);
+}
+
+static void
+save_call_frame(rb_event_flag_t _event, debug_context_t *debug_context, VALUE self, char *file, int line, ID mid)
+{
+ VALUE binding;
+ debug_frame_t *debug_frame;
+ int frame_n;
+
+ binding = self && RTEST(keep_frame_binding)? create_binding(self) : Qnil;
+
+ frame_n = debug_context->stack_size++;
+ if(frame_n >= debug_context->stack_len)
+ {
+ debug_context->stack_len += STACK_SIZE_INCREMENT;
+ debug_context->frames = REALLOC_N(debug_context->frames, debug_frame_t, debug_context->stack_len);
+ }
+ debug_frame = &debug_context->frames[frame_n];
+ debug_frame->file = file;
+ debug_frame->line = line;
+ debug_frame->binding = binding;
+ debug_frame->id = mid;
+ debug_frame->orig_id = mid;
+ debug_frame->dead = 0;
+ debug_frame->self = self;
+ debug_frame->arg_ary = Qnil;
+ debug_frame->argc = GET_THREAD2()->cfp->iseq->argc;
+ debug_frame->info.runtime.cfp = GET_THREAD2()->cfp;
+#if VM_DEBUG_BP_CHECK
+ debug_frame->info.runtime.bp = GET_THREAD2()->cfp->bp_check;
+#else
+ debug_frame->info.runtime.bp = GET_THREAD2()->cfp->bp;
+#endif
+ debug_frame->info.runtime.block_iseq = GET_THREAD2()->cfp->block_iseq;
+ debug_frame->info.runtime.block_pc = NULL;
+ debug_frame->info.runtime.last_pc = GET_THREAD2()->cfp->pc;
+ if (RTEST(track_frame_args))
+ copy_scalar_args(debug_frame);
+}
+
+
+#if defined DOSISH
+#define isdirsep(x) ((x) == '/' || (x) == '\\')
+#else
+#define isdirsep(x) ((x) == '/')
+#endif
+
+int
+filename_cmp(VALUE source, char *file)
+{
+ char *source_ptr, *file_ptr;
+ int s_len, f_len, min_len;
+ int s,f;
+ int dirsep_flag = 0;
+
+ s_len = RSTRING_LEN(source);
+ f_len = strlen(file);
+ min_len = min(s_len, f_len);
+
+ source_ptr = RSTRING_PTR(source);
+ file_ptr = file;
+
+ for( s = s_len - 1, f = f_len - 1; s >= s_len - min_len && f >= f_len - min_len; s--, f-- )
+ {
+ if((source_ptr[s] == '.' || file_ptr[f] == '.') && dirsep_flag)
+ return 1;
+ if(isdirsep(source_ptr[s]) && isdirsep(file_ptr[f]))
+ dirsep_flag = 1;
+#ifdef DOSISH_DRIVE_LETTER
+ else if (s == 0)
+ return(toupper(source_ptr[s]) == toupper(file_ptr[f]));
+#endif
+ else if(source_ptr[s] != file_ptr[f])
+ return 0;
+ }
+ return 1;
+}
+
+static VALUE
+create_binding(VALUE self)
+{
+ return(rb_binding_new());
+}
+
+inline static debug_frame_t *
+get_top_frame(debug_context_t *debug_context)
+{
+ if(debug_context->stack_size == 0)
+ return NULL;
+ else
+ return &(debug_context->frames[debug_context->stack_size-1]);
+}
+
+inline static void
+save_top_binding(debug_context_t *debug_context, VALUE binding)
+{
+ debug_frame_t *debug_frame;
+ debug_frame = get_top_frame(debug_context);
+ if(debug_frame)
+ debug_frame->binding = binding;
+}
+
+inline static void
+set_frame_source(rb_event_flag_t event, debug_context_t *debug_context, VALUE self, char *file, int line, ID mid)
+{
+ debug_frame_t *top_frame;
+ top_frame = get_top_frame(debug_context);
+ if(top_frame)
+ {
+ if (top_frame->info.runtime.block_iseq == GET_THREAD2()->cfp->iseq)
+ {
+ top_frame->info.runtime.block_pc = GET_THREAD2()->cfp->pc;
+ top_frame->binding = create_binding(self); /* block entered; need to rebind */
+ }
+ else if ((top_frame->info.runtime.block_pc != NULL) && (GET_THREAD2()->cfp->pc == top_frame->info.runtime.block_pc))
+ {
+ top_frame->binding = create_binding(self); /* block re-entered; need to rebind */
+ }
+
+ top_frame->info.runtime.block_iseq = GET_THREAD2()->cfp->block_iseq;
+ if (event == RUBY_EVENT_LINE)
+ top_frame->info.runtime.last_pc = GET_THREAD2()->cfp->pc;
+ top_frame->self = self;
+ top_frame->file = file;
+ top_frame->line = line;
+ top_frame->id = mid;
+ }
+}
+
+inline static void
+reset_frame_mid(debug_context_t *debug_context)
+{
+ debug_frame_t *top_frame;
+ top_frame = get_top_frame(debug_context);
+ if(top_frame)
+ {
+ top_frame->id = 0;
+ }
+}
+
+static void
+save_current_position(debug_context_t *debug_context)
+{
+ debug_frame_t *debug_frame;
+
+ debug_frame = get_top_frame(debug_context);
+ if(!debug_frame) return;
+ debug_context->last_file = debug_frame->file;
+ debug_context->last_line = debug_frame->line;
+ CTX_FL_UNSET(debug_context, CTX_FL_ENABLE_BKPT);
+ CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);
+ CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);
+}
+
+inline static int
+c_call_new_frame_p(VALUE klass, ID mid)
+{
+ klass = real_class(klass);
+ if(rb_block_given_p()) return 1;
+ if(klass == rb_cProc || klass == rb_mKernel || klass == rb_cModule) return 1;
+ return 0;
+}
+
+static void
+call_at_line_check(VALUE self, debug_context_t *debug_context, VALUE breakpoint, VALUE context, char *file, int line)
+{
+ VALUE binding = self? create_binding(self) : Qnil;
+ save_top_binding(debug_context, binding);
+
+ debug_context->stop_reason = CTX_STOP_STEP;
+
+ /* check breakpoint expression */
+ if(breakpoint != Qnil)
+ {
+ if(!check_breakpoint_expression(breakpoint, binding))
+ return;// TODO
+ if(!check_breakpoint_hit_condition(breakpoint))
+ return;// TODO
+ if(breakpoint != debug_context->breakpoint)
+ {
+ debug_context->stop_reason = CTX_STOP_BREAKPOINT;
+ rb_funcall(context, idAtBreakpoint, 1, breakpoint);
+ }
+ else
+ debug_context->breakpoint = Qnil;
+ }
+
+ reset_stepping_stop_points(debug_context);
+ call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
+}
+
+static struct iseq_catch_table_entry *
+create_catch_table(debug_context_t *debug_context, unsigned long cont)
+{
+ struct iseq_catch_table_entry *catch_table = &debug_context->catch_table.tmp_catch_table;
+
+ GET_THREAD2()->parse_in_eval++;
+ GET_THREAD2()->mild_compile_error++;
+ /* compiling with option Qfalse (no options) prevents debug hook calls during this catch routine */
+#ifdef HAVE_RB_ISEQ_COMPILE_ON_BASE
+ catch_table->iseq = rb_iseq_compile_with_option(
+ rb_str_new_cstr(""), rb_str_new_cstr("(exception catcher)"), Qnil, INT2FIX(1), NULL, Qfalse);
+#elif RB_ISEQ_COMPILE_5ARGS
+ catch_table->iseq = rb_iseq_compile_with_option(
+ rb_str_new_cstr(""), rb_str_new_cstr("(exception catcher)"), Qnil, INT2FIX(1), Qfalse);
+#else
+ catch_table->iseq = rb_iseq_compile_with_option(
+ rb_str_new_cstr(""), rb_str_new_cstr("(exception catcher)"), INT2FIX(1), Qfalse);
+#endif
+ GET_THREAD2()->mild_compile_error--;
+ GET_THREAD2()->parse_in_eval--;
+
+ catch_table->type = CATCH_TYPE_RESCUE;
+ catch_table->start = 0;
+ catch_table->end = ULONG_MAX;
+ catch_table->cont = cont;
+ catch_table->sp = 0;
+
+ return(catch_table);
+}
+
+#ifdef RUBY_EVENT_VM
+static int
+set_thread_event_flag_i(st_data_t key, st_data_t val, st_data_t flag)
+{
+ VALUE thval = (VALUE)key;
+ rb_thread_t *th;
+ GetThreadPtr(thval, th);
+ th->event_flags |= RUBY_EVENT_VM;
+
+ return(ST_CONTINUE);
+}
+#endif
+
+static void
+debug_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
+{
+ VALUE context;
+ VALUE breakpoint = Qnil, binding = Qnil;
+ debug_context_t *debug_context;
+ char *file = (char*)rb_sourcefile();
+ int line = rb_sourceline();
+ int moved = 0;
+ rb_thread_t *thread = GET_THREAD2();
+ struct rb_iseq_struct *iseq = thread->cfp->iseq;
+
+ hook_count++;
+
+ if (((iseq == NULL) || (file == NULL)) && (event != RUBY_EVENT_RAISE))
+ return;
+ thread_context_lookup(thread->self, &context, &debug_context, 1);
+
+ if ((event == RUBY_EVENT_LINE) || (event == RUBY_EVENT_CALL))
+ {
+ mid = iseq->defined_method_id;
+ klass = iseq->klass;
+ }
+
+#ifdef ID_ALLOCATOR
+ if (mid == ID_ALLOCATOR) return;
+#endif
+
+ /* return if thread is marked as 'ignored'.
+ debugger's threads are marked this way
+ */
+ if(CTX_FL_TEST(debug_context, CTX_FL_IGNORE)) return;
+
+ while(1)
+ {
+ /* halt execution of the current thread if the debugger
+ is activated in another
+ */
+ while(locker != Qnil && locker != thread->self)
+ {
+ add_to_locked(thread->self);
+ rb_thread_stop();
+ }
+
+ /* stop the current thread if it's marked as suspended */
+ if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND) && locker != thread->self)
+ {
+ CTX_FL_SET(debug_context, CTX_FL_WAS_RUNNING);
+ rb_thread_stop();
+ }
+ else break;
+ }
+
+ /* return if the current thread is the locker */
+ if (locker != Qnil) return;
+
+ /* only the current thread can proceed */
+ locker = thread->self;
+
+ /* restore catch tables removed for jump */
+ if (debug_context->old_iseq_catch != NULL)
+ {
+ int i = 0;
+ while (debug_context->old_iseq_catch[i].iseq != NULL)
+ {
+ debug_context->old_iseq_catch[i].iseq->catch_table = debug_context->old_iseq_catch[i].catch_table;
+ debug_context->old_iseq_catch[i].iseq->catch_table_size = debug_context->old_iseq_catch[i].catch_table_size;
+ i++;
+ }
+ free(debug_context->old_iseq_catch);
+ debug_context->old_iseq_catch = NULL;
+ }
+
+#ifdef RUBY_EVENT_VM
+ /* make sure all threads have event flag set so we'll get its events */
+ st_foreach(thread->vm->living_threads, set_thread_event_flag_i, 0);
+#endif
+
+ /* remove any frames that are now out of scope */
+ while(debug_context->stack_size > 0)
+ {
+#if VM_DEBUG_BP_CHECK
+ if (debug_context->frames[debug_context->stack_size - 1].info.runtime.bp <= thread->cfp->bp_check)
+#else
+ if (debug_context->frames[debug_context->stack_size - 1].info.runtime.bp <= thread->cfp->bp)
+#endif
+ break;
+ debug_context->stack_size--;
+ }
+
+ if (debug_context->thread_pause)
+ {
+ debug_context->thread_pause = 0;
+ debug_context->stop_next = 1;
+ debug_context->dest_frame = -1;
+ moved = 1;
+ }
+ else
+ {
+ /* ignore a skipped section of code */
+ if (CTX_FL_TEST(debug_context, CTX_FL_SKIPPED))
+ goto cleanup;
+
+ if ((event == RUBY_EVENT_LINE) && (debug_context->stack_size > 0) &&
+ (get_top_frame(debug_context)->line == line) && (get_top_frame(debug_context)->info.runtime.cfp->iseq == iseq) &&
+ !CTX_FL_TEST(debug_context, CTX_FL_CATCHING))
+ {
+ /* Sometimes duplicate RUBY_EVENT_LINE messages get generated by the compiler.
+ * Ignore them. */
+ goto cleanup;
+ }
+ }
+
+ if(debug == Qtrue)
+ fprintf(stderr, "%s:%d [%s] %s\n", file, line, get_event_name(event), rb_id2name(mid));
+
+ /* There can be many event calls per line, but we only want
+ *one* breakpoint per line. */
+ if(debug_context->last_line != line || debug_context->last_file == NULL ||
+ strcmp(debug_context->last_file, file) != 0)
+ {
+ CTX_FL_SET(debug_context, CTX_FL_ENABLE_BKPT);
+ moved = 1;
+ }
+
+ if(event != RUBY_EVENT_LINE)
+ CTX_FL_SET(debug_context, CTX_FL_STEPPED);
+
+ switch(event)
+ {
+ case RUBY_EVENT_LINE:
+ {
+ if(debug_context->stack_size == 0)
+ save_call_frame(event, debug_context, self, file, line, mid);
+ else
+ set_frame_source(event, debug_context, self, file, line, mid);
+
+ if (CTX_FL_TEST(debug_context, CTX_FL_CATCHING))
+ {
+ debug_frame_t *top_frame = get_top_frame(debug_context);
+
+ if (top_frame != NULL)
+ {
+ rb_control_frame_t *cfp = top_frame->info.runtime.cfp;
+ int hit_count;
+
+ /* restore the proper catch table */
+ cfp->iseq->catch_table_size = debug_context->catch_table.old_catch_table_size;
+ cfp->iseq->catch_table = debug_context->catch_table.old_catch_table;
+
+ /* send catchpoint notification */
+ hit_count = INT2FIX(FIX2INT(rb_hash_aref(rdebug_catchpoints,
+ debug_context->catch_table.mod_name)+1));
+ rb_hash_aset(rdebug_catchpoints, debug_context->catch_table.mod_name, hit_count);
+ debug_context->stop_reason = CTX_STOP_CATCHPOINT;
+ rb_funcall(context, idAtCatchpoint, 1, debug_context->catch_table.errinfo);
+ if(self && binding == Qnil)
+ binding = create_binding(self);
+ save_top_binding(debug_context, binding);
+ call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
+ }
+
+ /* now allow the next exception to be caught */
+ CTX_FL_UNSET(debug_context, CTX_FL_CATCHING);
+ break;
+ }
+
+ if(RTEST(tracing) || CTX_FL_TEST(debug_context, CTX_FL_TRACING))
+ rb_funcall(context, idAtTracing, 2, rb_str_new2(file), INT2FIX(line));
+
+ if(debug_context->dest_frame == -1 ||
+ debug_context->stack_size == debug_context->dest_frame)
+ {
+ if(moved || !CTX_FL_TEST(debug_context, CTX_FL_FORCE_MOVE))
+ debug_context->stop_next--;
+ if(debug_context->stop_next < 0)
+ debug_context->stop_next = -1;
+ if(moved || (CTX_FL_TEST(debug_context, CTX_FL_STEPPED) &&
+ !CTX_FL_TEST(debug_context, CTX_FL_FORCE_MOVE)))
+ {
+ debug_context->stop_line--;
+ CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);
+ }
+ }
+ else if(debug_context->stack_size < debug_context->dest_frame)
+ {
+ debug_context->stop_next = 0;
+ }
+
+ if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
+ (breakpoint = check_breakpoints_by_pos(debug_context, file, line)) != Qnil)
+ {
+ call_at_line_check(self, debug_context, breakpoint, context, file, line);
+ }
+ break;
+ }
+ case RUBY_EVENT_CALL:
+ {
+ save_call_frame(event, debug_context, self, file, line, mid);
+ breakpoint = check_breakpoints_by_method(debug_context, klass, mid, self);
+ if(breakpoint != Qnil)
+ {
+ debug_frame_t *debug_frame;
+ debug_frame = get_top_frame(debug_context);
+ if(debug_frame)
+ binding = debug_frame->binding;
+ if(NIL_P(binding) && self)
+ binding = create_binding(self);
+ save_top_binding(debug_context, binding);
+
+ if(!check_breakpoint_expression(breakpoint, binding))
+ break;
+ if(!check_breakpoint_hit_condition(breakpoint))
+ break;
+ if(breakpoint != debug_context->breakpoint)
+ {
+ debug_context->stop_reason = CTX_STOP_BREAKPOINT;
+ rb_funcall(context, idAtBreakpoint, 1, breakpoint);
+ }
+ else
+ debug_context->breakpoint = Qnil;
+ call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
+ break;
+ }
+ breakpoint = check_breakpoints_by_pos(debug_context, file, line);
+ if (breakpoint != Qnil)
+ call_at_line_check(self, debug_context, breakpoint, context, file, line);
+ break;
+ }
+ case RUBY_EVENT_C_CALL:
+ {
+ if(c_call_new_frame_p(klass, mid))
+ save_call_frame(event, debug_context, self, file, line, mid);
+ else
+ set_frame_source(event, debug_context, self, file, line, mid);
+ break;
+ }
+ case RUBY_EVENT_C_RETURN:
+ {
+ /* note if a block is given we fall through! */
+ if(!rb_method_boundp(klass, mid, 0) || !c_call_new_frame_p(klass, mid))
+ break;
+ }
+ case RUBY_EVENT_RETURN:
+ case RUBY_EVENT_END:
+ {
+ if(debug_context->stack_size == debug_context->stop_frame)
+ {
+ debug_context->stop_next = 1;
+ debug_context->stop_frame = 0;
+ /* NOTE: can't use call_at_line function here to trigger a debugger event.
+ this can lead to segfault. We should only unroll the stack on this event.
+ */
+ }
+ while(debug_context->stack_size > 0)
+ {
+ debug_context->stack_size--;
+#if VM_DEBUG_BP_CHECK
+ if (debug_context->frames[debug_context->stack_size].info.runtime.bp <= GET_THREAD2()->cfp->bp_check)
+#else
+ if (debug_context->frames[debug_context->stack_size].info.runtime.bp <= GET_THREAD2()->cfp->bp)
+#endif
+ break;
+ }
+ CTX_FL_SET(debug_context, CTX_FL_ENABLE_BKPT);
+
+ break;
+ }
+ case RUBY_EVENT_CLASS:
+ {
+ reset_frame_mid(debug_context);
+ save_call_frame(event, debug_context, self, file, line, mid);
+ break;
+ }
+ case RUBY_EVENT_RAISE:
+ {
+ VALUE ancestors;
+ VALUE expn_class, aclass;
+ int i;
+
+// set_frame_source(event, debug_context, self, file, line, mid);
+
+ if(post_mortem == Qtrue && self)
+ {
+ binding = create_binding(self);
+ rb_ivar_set(rb_errinfo(), rb_intern("@__debug_file"), rb_str_new2(file));
+ rb_ivar_set(rb_errinfo(), rb_intern("@__debug_line"), INT2FIX(line));
+ rb_ivar_set(rb_errinfo(), rb_intern("@__debug_binding"), binding);
+ rb_ivar_set(rb_errinfo(), rb_intern("@__debug_context"), debug_context_dup(debug_context, self));
+ }
+
+ expn_class = rb_obj_class(rb_errinfo());
+
+ /* This code goes back to the earliest days of ruby-debug. It
+ tends to disallow catching an exception via the
+ "catchpoint" command. To address this one possiblilty is to
+ move this after testing for catchponts. Kent however thinks
+ there may be a misfeature in Ruby's eval.c: the problem was
+ in the fact that Ruby doesn't reset exception flag on the
+ current thread before it calls a notification handler.
+
+ See also the #ifdef'd code below as well.
+ */
+#ifdef NORMAL_CODE
+ if( !NIL_P(rb_class_inherited_p(expn_class, rb_eSystemExit)) )
+ {
+ debug_stop(mDebugger);
+ break;
+ }
+#endif
+
+ if (rdebug_catchpoints == Qnil ||
+ (debug_context->stack_size == 0) ||
+ CTX_FL_TEST(debug_context, CTX_FL_CATCHING) ||
+#ifdef _ST_NEW_
+ st_get_num_entries(RHASH_TBL(rdebug_catchpoints)) == 0)
+#else
+ (RHASH_TBL(rdebug_catchpoints)->num_entries) == 0)
+#endif
+ break;
+
+ ancestors = rb_mod_ancestors(expn_class);
+ for(i = 0; i < RARRAY_LEN(ancestors); i++)
+ {
+ VALUE mod_name;
+ VALUE hit_count;
+
+ aclass = rb_ary_entry(ancestors, i);
+ mod_name = rb_mod_name(aclass);
+ hit_count = rb_hash_aref(rdebug_catchpoints, mod_name);
+ if (hit_count != Qnil)
+ {
+ debug_frame_t *top_frame = get_top_frame(debug_context);
+ rb_control_frame_t *cfp = top_frame->info.runtime.cfp;
+
+ /* save the current catch table */
+ CTX_FL_SET(debug_context, CTX_FL_CATCHING);
+ debug_context->catch_table.old_catch_table_size = cfp->iseq->catch_table_size;
+ debug_context->catch_table.old_catch_table = cfp->iseq->catch_table;
+ debug_context->catch_table.mod_name = mod_name;
+ debug_context->catch_table.errinfo = rb_errinfo();
+
+ /* create a new catch table to catch this exception, and put it in the current iseq */
+ cfp->iseq->catch_table_size = 1;
+ cfp->iseq->catch_table =
+ create_catch_table(debug_context, top_frame->info.runtime.last_pc - cfp->iseq->iseq_encoded - insn_len(BIN(trace)));
+ break;
+ }
+ }
+
+ /* If we stop the debugger, we may not be able to trace into
+ code that has an exception handler wrapped around it. So
+ the alternative is to force the user to do his own
+ Debugger.stop. */
+#ifdef NORMAL_CODE_MOVING_AFTER_
+ if( !NIL_P(rb_class_inherited_p(expn_class, rb_eSystemExit)) )
+ {
+ debug_stop(mDebugger);
+ break;
+ }
+#endif
+
+ break;
+ }
+ }
+
+ cleanup:
+
+ debug_context->stop_reason = CTX_STOP_NONE;
+
+ /* check that all contexts point to alive threads */
+ if(hook_count - last_check > 3000)
+ {
+ check_thread_contexts();
+ last_check = hook_count;
+ }
+
+ /* release a lock */
+ locker = Qnil;
+
+ /* let the next thread to run */
+ {
+ VALUE next_thread = remove_from_locked();
+ if(next_thread != Qnil)
+ rb_thread_run(next_thread);
+ }
+}
+
+static VALUE
+debug_stop_i(VALUE self)
+{
+ if(IS_STARTED)
+ debug_stop(self);
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * Debugger.start_ -> bool
+ * Debugger.start_ { ... } -> bool
+ *
+ * This method is internal and activates the debugger. Use
+ * Debugger.start (from <tt>lib/ruby-debug-base.rb</tt>) instead.
+ *
+ * The return value is the value of !Debugger.started? <i>before</i>
+ * issuing the +start+; That is, +true+ is returned, unless debugger
+ * was previously started.
+
+ * If a block is given, it starts debugger and yields to block. When
+ * the block is finished executing it stops the debugger with
+ * Debugger.stop method. Inside the block you will probably want to
+ * have a call to Debugger.debugger. For example:
+ * Debugger.start{debugger; foo} # Stop inside of foo
+ *
+ * Also, ruby-debug only allows
+ * one invocation of debugger at a time; nested Debugger.start's
+ * have no effect and you can't use this inside the debugger itself.
+ *
+ * <i>Note that if you want to completely remove the debugger hook,
+ * you must call Debugger.stop as many times as you called
+ * Debugger.start method.</i>
+ */
+static VALUE
+debug_start(VALUE self)
+{
+ VALUE result;
+ start_count++;
+
+ if(IS_STARTED)
+ result = Qfalse;
+ else
+ {
+ locker = Qnil;
+ rdebug_breakpoints = rb_ary_new();
+ rdebug_catchpoints = rb_hash_new();
+ rdebug_threads_tbl = threads_table_create();
+
+ rb_add_event_hook(debug_event_hook, RUBY_EVENT_ALL, Qnil);
+ result = Qtrue;
+ }
+
+ if(rb_block_given_p())
+ rb_ensure(rb_yield, self, debug_stop_i, self);
+
+ return result;
+}
+
+/*
+ * call-seq:
+ * Debugger.stop -> bool
+ *
+ * This method disables the debugger. It returns +true+ if the debugger is disabled,
+ * otherwise it returns +false+.
+ *
+ * <i>Note that if you want to complete remove the debugger hook,
+ * you must call Debugger.stop as many times as you called
+ * Debugger.start method.</i>
+ */
+static VALUE
+debug_stop(VALUE self)
+{
+ debug_check_started();
+
+ start_count--;
+ if(start_count)
+ return Qfalse;
+
+ rb_remove_event_hook(debug_event_hook);
+
+ locker = Qnil;
+ rdebug_breakpoints = Qnil;
+ rdebug_threads_tbl = Qnil;
+
+ return Qtrue;
+}
+
+static int
+find_last_context_func(st_data_t key, st_data_t value, st_data_t *result_arg)
+{
+ debug_context_t *debug_context;
+ VALUE *result = (VALUE*)result_arg;
+ if(!value)
+ return ST_CONTINUE;
+
+ Data_Get_Struct((VALUE)value, debug_context_t, debug_context);
+ if(debug_context->thnum == last_debugged_thnum)
+ {
+ *result = value;
+ return ST_STOP;
+ }
+ return ST_CONTINUE;
+}
+
+/*
+ * call-seq:
+ * Debugger.last_interrupted -> context
+ *
+ * Returns last debugged context.
+ */
+static VALUE
+debug_last_interrupted(VALUE self)
+{
+ VALUE result = Qnil;
+ threads_table_t *threads_table;
+
+ debug_check_started();
+
+ Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);
+
+ st_foreach(threads_table->tbl, find_last_context_func, (st_data_t)&result);
+ return result;
+}
+
+/*
+ * call-seq:
+ * Debugger.current_context -> context
+ *
+ * Returns current context.
+ * <i>Note:</i> Debugger.current_context.thread == Thread.current
+ */
+static VALUE
+debug_current_context(VALUE self)
+{
+ VALUE thread, context;
+
+ debug_check_started();
+
+ thread = rb_thread_current();
+ thread_context_lookup(thread, &context, NULL, 1);
+
+ return context;
+}
+
+/*
+ * call-seq:
+ * Debugger.thread_context(thread) -> context
+ *
+ * Returns context of the thread passed as an argument.
+ */
+static VALUE
+debug_thread_context(VALUE self, VALUE thread)
+{
+ VALUE context;
+
+ debug_check_started();
+ thread_context_lookup(thread, &context, NULL, 1);
+ return context;
+}
+
+/*
+ * call-seq:
+ * Debugger.contexts -> array
+ *
+ * Returns an array of all contexts.
+ */
+static VALUE
+debug_contexts(VALUE self)
+{
+ volatile VALUE list;
+ volatile VALUE new_list;
+ VALUE thread, context;
+ threads_table_t *threads_table;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ new_list = rb_ary_new();
+ list = rb_funcall(rb_cThread, idList, 0);
+ for(i = 0; i < RARRAY_LEN(list); i++)
+ {
+ thread = rb_ary_entry(list, i);
+ thread_context_lookup(thread, &context, NULL, 1);
+ rb_ary_push(new_list, context);
+ }
+ threads_table_clear(rdebug_threads_tbl);
+ Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);
+ for(i = 0; i < RARRAY_LEN(new_list); i++)
+ {
+ context = rb_ary_entry(new_list, i);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ st_insert(threads_table->tbl, debug_context->thread_id, context);
+ }
+
+ return new_list;
+}
+
+/*
+ * call-seq:
+ * Debugger.suspend -> Debugger
+ *
+ * Suspends all contexts.
+ */
+static VALUE
+debug_suspend(VALUE self)
+{
+ VALUE current, context;
+ VALUE context_list;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ context_list = debug_contexts(self);
+ thread_context_lookup(rb_thread_current(), &current, NULL, 1);
+
+ for(i = 0; i < RARRAY_LEN(context_list); i++)
+ {
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ context_suspend_0(debug_context);
+ }
+
+ return self;
+}
+
+/*
+ * call-seq:
+ * Debugger.resume -> Debugger
+ *
+ * Resumes all contexts.
+ */
+static VALUE
+debug_resume(VALUE self)
+{
+ VALUE current, context;
+ VALUE context_list;
+ debug_context_t *debug_context;
+ int i;
+
+ debug_check_started();
+
+ context_list = debug_contexts(self);
+
+ thread_context_lookup(rb_thread_current(), &current, NULL, 1);
+ for(i = 0; i < RARRAY_LEN(context_list); i++)
+ {
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ context_resume_0(debug_context);
+ }
+
+ rb_thread_schedule();
+
+ return self;
+}
+
+/*
+ * call-seq:
+ * Debugger.tracing -> bool
+ *
+ * Returns +true+ if the global tracing is activated.
+ */
+static VALUE
+debug_tracing(VALUE self)
+{
+ return tracing;
+}
+
+/*
+ * call-seq:
+ * Debugger.tracing = bool
+ *
+ * Sets the global tracing flag.
+ */
+static VALUE
+debug_set_tracing(VALUE self, VALUE value)
+{
+ tracing = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/*
+ * call-seq:
+ * Debugger.post_mortem? -> bool
+ *
+ * Returns +true+ if post-moterm debugging is enabled.
+ */
+static VALUE
+debug_post_mortem(VALUE self)
+{
+ return post_mortem;
+}
+
+/*
+ * call-seq:
+ * Debugger.post_mortem = bool
+ *
+ * Sets post-moterm flag.
+ * FOR INTERNAL USE ONLY.
+ */
+static VALUE
+debug_set_post_mortem(VALUE self, VALUE value)
+{
+ debug_check_started();
+
+ post_mortem = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/*
+ * call-seq:
+ * Debugger.track_fame_args? -> bool
+ *
+ * Returns +true+ if the debugger track frame argument values on calls.
+ */
+static VALUE
+debug_track_frame_args(VALUE self)
+{
+ return track_frame_args;
+}
+
+/*
+ * call-seq:
+ * Debugger.track_frame_args = bool
+ *
+ * Setting to +true+ will make the debugger save argument info on calls.
+ */
+static VALUE
+debug_set_track_frame_args(VALUE self, VALUE value)
+{
+ track_frame_args = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/*
+ * call-seq:
+ * Debugger.keep_frame_binding? -> bool
+ *
+ * Returns +true+ if the debugger will collect frame bindings.
+ */
+static VALUE
+debug_keep_frame_binding(VALUE self)
+{
+ return keep_frame_binding;
+}
+
+/*
+ * call-seq:
+ * Debugger.keep_frame_binding = bool
+ *
+ * Setting to +true+ will make the debugger create frame bindings.
+ */
+static VALUE
+debug_set_keep_frame_binding(VALUE self, VALUE value)
+{
+ keep_frame_binding = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/* :nodoc: */
+static VALUE
+debug_debug(VALUE self)
+{
+ return debug;
+}
+
+/* :nodoc: */
+static VALUE
+debug_set_debug(VALUE self, VALUE value)
+{
+ debug = RTEST(value) ? Qtrue : Qfalse;
+ return value;
+}
+
+/* :nodoc: */
+static VALUE
+debug_thread_inherited(VALUE klass)
+{
+ rb_raise(rb_eRuntimeError, "Can't inherit Debugger::DebugThread class");
+}
+
+/*
+ * call-seq:
+ * Debugger.debug_load(file, stop = false, increment_start = false) -> nil
+ *
+ * Same as Kernel#load but resets current context's frames.
+ * +stop+ parameter forces the debugger to stop at the first line of code in the +file+
+ * +increment_start+ determines if start_count should be incremented. When
+ * control threads are used, they have to be set up before loading the
+ * debugger; so here +increment_start+ will be false.
+ * FOR INTERNAL USE ONLY.
+ */
+static VALUE
+debug_debug_load(int argc, VALUE *argv, VALUE self)
+{
+ VALUE file, stop, context, increment_start;
+ debug_context_t *debug_context;
+ int state = 0;
+
+ if(rb_scan_args(argc, argv, "12", &file, &stop, &increment_start) == 1)
+ {
+ stop = Qfalse;
+ increment_start = Qtrue;
+ }
+
+ debug_start(self);
+ if (Qfalse == increment_start) start_count--;
+
+ context = debug_current_context(self);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->stack_size = 0;
+ if(RTEST(stop))
+ debug_context->stop_next = 1;
+ /* Initializing $0 to the script's path */
+ ruby_script(RSTRING_PTR(file));
+ rb_load_protect(file, 0, &state);
+ if (0 != state)
+ {
+ VALUE errinfo = rb_errinfo();
+ debug_suspend(self);
+ reset_stepping_stop_points(debug_context);
+ rb_set_errinfo(Qnil);
+ return errinfo;
+ }
+
+ /* We should run all at_exit handler's in order to provide,
+ * for instance, a chance to run all defined test cases */
+ rb_exec_end_proc();
+
+ /* We could have issued a Debugger.stop inside the debug
+ session. */
+ if (start_count > 0)
+ debug_stop(self);
+
+ return Qnil;
+}
+
+static VALUE
+set_current_skipped_status(VALUE status)
+{
+ VALUE context;
+ debug_context_t *debug_context;
+
+ context = debug_current_context(Qnil);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ if(status)
+ CTX_FL_SET(debug_context, CTX_FL_SKIPPED);
+ else
+ CTX_FL_UNSET(debug_context, CTX_FL_SKIPPED);
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * Debugger.skip { block } -> obj or nil
+ *
+ * The code inside of the block is escaped from the debugger.
+ */
+static VALUE
+debug_skip(VALUE self)
+{
+ if (!rb_block_given_p()) {
+ rb_raise(rb_eArgError, "called without a block");
+ }
+ if(!IS_STARTED)
+ return rb_yield(Qnil);
+ set_current_skipped_status(Qtrue);
+ return rb_ensure(rb_yield, Qnil, set_current_skipped_status, Qfalse);
+}
+
+static VALUE
+debug_at_exit_c(VALUE proc)
+{
+ return rb_funcall(proc, rb_intern("call"), 0);
+}
+
+static void
+debug_at_exit_i(VALUE proc)
+{
+ if(!IS_STARTED)
+ {
+ debug_at_exit_c(proc);
+ }
+ else
+ {
+ set_current_skipped_status(Qtrue);
+ rb_ensure(debug_at_exit_c, proc, set_current_skipped_status, Qfalse);
+ }
+}
+
+/*
+ * call-seq:
+ * Debugger.debug_at_exit { block } -> proc
+ *
+ * Register <tt>at_exit</tt> hook which is escaped from the debugger.
+ * FOR INTERNAL USE ONLY.
+ */
+static VALUE
+debug_at_exit(VALUE self)
+{
+ VALUE proc;
+ if (!rb_block_given_p())
+ rb_raise(rb_eArgError, "called without a block");
+ proc = rb_block_proc();
+ rb_set_end_proc(debug_at_exit_i, proc);
+ return proc;
+}
+
+/*
+ * call-seq:
+ * context.step(steps, force = false)
+ *
+ * Stops the current context after a number of +steps+ are made.
+ * +force+ parameter (if true) ensures that the cursor moves from the current line.
+ */
+static VALUE
+context_stop_next(int argc, VALUE *argv, VALUE self)
+{
+ VALUE steps, force;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+
+ rb_scan_args(argc, argv, "11", &steps, &force);
+ if(FIX2INT(steps) < 0)
+ rb_raise(rb_eRuntimeError, "Steps argument can't be negative.");
+
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ debug_context->stop_next = FIX2INT(steps);
+ if(RTEST(force))
+ CTX_FL_SET(debug_context, CTX_FL_FORCE_MOVE);
+ else
+ CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);
+
+ return steps;
+}
+
+/*
+ * call-seq:
+ * context.step_over(steps, frame = nil, force = false)
+ *
+ * Steps over a +steps+ number of times.
+ * Make step over operation on +frame+, by default the current frame.
+ * +force+ parameter (if true) ensures that the cursor moves from the current line.
+ */
+static VALUE
+context_step_over(int argc, VALUE *argv, VALUE self)
+{
+ VALUE lines, frame, force;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ if(debug_context->stack_size == 0)
+ rb_raise(rb_eRuntimeError, "No frames collected.");
+
+ rb_scan_args(argc, argv, "12", &lines, &frame, &force);
+ debug_context->stop_line = FIX2INT(lines);
+ CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);
+ if(frame == Qnil)
+ {
+ debug_context->dest_frame = debug_context->stack_size;
+ }
+ else
+ {
+ if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->stack_size)
+ rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
+ debug_context->dest_frame = debug_context->stack_size - FIX2INT(frame);
+ }
+ if(RTEST(force))
+ CTX_FL_SET(debug_context, CTX_FL_FORCE_MOVE);
+ else
+ CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);
+
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * context.stop_frame(frame)
+ *
+ * Stops when a frame with number +frame+ is activated. Implements +finish+ and +next+ commands.
+ */
+static VALUE
+context_stop_frame(VALUE self, VALUE frame)
+{
+ debug_context_t *debug_context;
+
+ debug_check_started();
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->stack_size)
+ rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
+ debug_context->stop_frame = debug_context->stack_size - FIX2INT(frame);
+
+ return frame;
+}
+
+inline static int
+check_frame_number(debug_context_t *debug_context, VALUE frame)
+{
+ int frame_n;
+
+ frame_n = FIX2INT(frame);
+ if(frame_n < 0 || frame_n >= debug_context->stack_size)
+ rb_raise(rb_eArgError, "Invalid frame number %d, stack (0...%d)",
+ frame_n, debug_context->stack_size - 1);
+ return frame_n;
+}
+
+static int
+optional_frame_position(int argc, VALUE *argv) {
+ unsigned int i_scanned;
+ VALUE level;
+
+ if ((argc > 1) || (argc < 0))
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 or 1)", argc);
+ i_scanned = rb_scan_args(argc, argv, "01", &level);
+ if (0 == i_scanned) {
+ level = INT2FIX(0);
+ }
+ return level;
+}
+
+/*
+ * call-seq:
+ * context.frame_args_info(frame_position=0) -> list
+ if track_frame_args or nil otherwise
+ *
+ * Returns info saved about call arguments (if any saved).
+ */
+static VALUE
+context_frame_args_info(int argc, VALUE *argv, VALUE self)
+{
+ VALUE frame;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+ frame = optional_frame_position(argc, argv);
+ Data_Get_Struct(self, debug_context_t, debug_context);
+
+ return RTEST(track_frame_args) ? GET_FRAME->arg_ary : Qnil;
+}
+
+/*
+ * call-seq:
+ * context.frame_binding(frame_position=0) -> binding
+ *
+ * Returns frame's binding.
+ */
+static VALUE
+context_frame_binding(int argc, VALUE *argv, VALUE self)
+{
+ VALUE frame;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+ frame = optional_frame_position(argc, argv);
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ return GET_FRAME->binding;
+}
+
+/*
+ * call-seq:
+ * context.frame_method(frame_position=0) -> sym
+ *
+ * Returns the sym of the called method.
+ */
+static VALUE
+context_frame_id(int argc, VALUE *argv, VALUE self)
+{
+ ID id;
+ VALUE frame;
+ VALUE frame_id;
+ debug_context_t *debug_context;
+ rb_control_frame_t *cfp;
+
+ debug_check_started();
+ frame = optional_frame_position(argc, argv);
+ Data_Get_Struct(self, debug_context_t, debug_context);
+
+ cfp = GET_FRAME->info.runtime.cfp;
+#if defined HAVE_RB_CONTROL_FRAME_T_METHOD_ID
+ frame_id = RUBYVM_CFUNC_FRAME_P(cfp) ? cfp->method_id : cfp->iseq->defined_method_id;
+#elif defined HAVE_RB_METHOD_ENTRY_T_CALLED_ID
+ frame_id = RUBYVM_CFUNC_FRAME_P(cfp) ? cfp->me->called_id : cfp->iseq->defined_method_id;
+#endif
+ return frame_id ? ID2SYM(frame_id) : Qnil;
+}
+
+/*
+ * call-seq:
+ * context.frame_line(frame_position) -> int
+ *
+ * Returns the line number in the file.
+ */
+static VALUE
+context_frame_line(int argc, VALUE *argv, VALUE self)
+{
+ VALUE frame;
+ debug_context_t *debug_context;
+ rb_thread_t *th;
+ rb_control_frame_t *cfp;
+ VALUE *pc;
+
+ debug_check_started();
+ frame = optional_frame_position(argc, argv);
+ Data_Get_Struct(self, debug_context_t, debug_context);
+ GetThreadPtr(context_thread_0(debug_context), th);
+
+ pc = GET_FRAME->info.runtime.last_pc;
+ cfp = GET_FRAME->info.runtime.cfp;
+ while (cfp >= th->cfp)
+ {
+ if ((cfp->iseq != NULL) && (pc >= cfp->iseq->iseq_encoded) && (pc < cfp->iseq->iseq_encoded + cfp->iseq->iseq_size))
+ return(INT2FIX(rb_vm_get_sourceline(cfp)));
+ cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
+ }
+
+ return(INT2FIX(0));
+}
+
+/*
+ * call-seq:
+ * context.frame_file(frame_position) -> string
+ *
+ * Returns the name of the file.
+ */
+static VALUE
+context_frame_file(int argc, VALUE *argv, VALUE self)
+{
+ VALUE frame;
+ debug_context_t *debug_context;
+
+ debug_check_started();
+ frame = optional_frame_position(argc, argv);
+ Data_Get_Struct(self, debug_context_t, debug_context);
+
+ return rb_str_new_cstr(GET_FRAME->file);
+ //return(GET_FRAME->info.runtime.cfp->iseq->filename);
+}
+
+static int
+arg_value_is_small(VALUE val)
+{
+ switch (TYPE(val)) {
+ case T_FIXNUM: case T_FLOAT: case T_CLASS:
+ case T_NIL: case T_MODULE: case T_FILE:
+ case T_TRUE: case T_FALSE: case T_UNDEF:
+ return 1;
+ default:
+ return SYMBOL_P(val);
+ }
+}
+
+/*
+ * Save scalar arguments or a class name.
+ */
+static void
+copy_scalar_args(debug_frame_t *debug_frame)
+{
+ rb_control_frame_t *cfp;
+ rb_iseq_t *iseq;
+
+ cfp = debug_frame->info.runtime.cfp;
+ iseq = cfp->iseq;
+
+ if (iseq->local_table && iseq->argc)
+ {
+ int i;
+ VALUE val;
+
+ debug_frame->arg_ary = rb_ary_new2(iseq->argc);
+ for (i = 0; i < iseq->argc; i++)
+ {
+ if (!rb_is_local_id(iseq->local_table[i])) continue; /* skip flip states */
+
+#ifdef HAVE_RB_CONTROL_FRAME_T_EP
+ val = *(cfp->ep - iseq->local_size + i);
+#else
+ val = *(cfp->dfp - iseq->local_size + i);
+#endif
+
+ if (arg_value_is_small(val))
+ rb_ary_push(debug_frame->arg_ary, val);
+ else
+ rb_ary_push(debug_frame->arg_ary, rb_str_new2(rb_obj_classname(val)));
+ }
+ }
+}
+
+/*
+ * call-seq:
+ * context.copy_args(frame) -> list of args
+ *
+ * Returns a array of argument names.
+ */
+static VALUE
+context_copy_args(debug_frame_t *debug_frame)
+{
+ rb_control_frame_t *cfp;
+ rb_iseq_t *iseq;
+
+ cfp = debug_frame->info.runtime.cfp;
+ iseq = cfp->iseq;
+
+ if (iseq->local_table && iseq->argc)
+ {
+ int i;
+ VALUE list;
+
+ list = rb_ary_new2(iseq->argc);
+ for (i = 0; i < iseq->argc; i++)
+ {
+ if (!rb_is_local_id(iseq->local_table[i])) continue; /* skip flip states */
+ rb_ary_push(list, rb_id2str(iseq->local_table[i]));
+ }
+
+ return(list);
+ }
+ return(rb_ary_new2(0));
+}
+
+static VALUE
+context_copy_locals(debug_context_t *debug_context, debug_frame_t *debug_frame, VALUE self)
+{
+ int i;
+ rb_control_frame_t *cfp;
+ rb_iseq_t *iseq;
+ VALUE hash;
+
+ cfp = debug_frame->info.runtime.cfp;
+ iseq = cfp->iseq;
+ hash = rb_hash_new();
+
+ if (iseq->local_table != NULL)
+ {
+ /* Note rb_iseq_disasm() is instructive in coming up with this code */
+ for (i = 0; i < iseq->local_table_size; i++)
+ {
+ VALUE str = rb_id2str(iseq->local_table[i]);
+ if (str != 0)
+#ifdef HAVE_RB_CONTROL_FRAME_T_EP
+ rb_hash_aset(hash, str, *(cfp->ep - iseq->local_size + i));
+#else
+ rb_hash_aset(hash, str, *(cfp->dfp - iseq->local_size + i));
+#endif
+ }
+ }
+
+ iseq = cfp->block_iseq;
+ if ((iseq != NULL) && (iseq->local_table != NULL) && (iseq != cfp->iseq))
+ {
+ rb_thread_t *th;
+ rb_control_frame_t *block_frame = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
+ GetThreadPtr(context_thread_0(debug_context), th);
+ while (block_frame > (rb_control_frame_t*)th->stack)
+ {
+ if (block_frame->iseq == cfp->block_iseq)
+ {
+ for (i = 0; i < iseq->local_table_size; i++)
+ {
+ VALUE str = rb_id2str(iseq->local_table[i]);
+ if (str != 0)
+#ifdef HAVE_RB_CONTROL_FRAME_T_EP
+ rb_hash_aset(hash, str, *(block_frame->ep - iseq->local_table_size + i - 1));
+#else
+ rb_hash_aset(hash, str, *(block_frame->dfp - iseq->local_table_size + i - 1));
+#endif
+ }
+ return(hash);
+ }
+ block_frame = RUBY_VM_NEXT_CONTROL_FRAME(block_frame);
+ }
+ }
+
+ return(hash);
+}
+
+/*
+ * call-seq:
+ * context.frame_locals(frame) -> hash
+ *
+ * Returns frame's local variables.
+ */
+static VALUE
+context_frame_locals(int argc, VALUE *argv, VALUE self)