ext/ruby_debug.c in ruby-debug-0.1.2 vs ext/ruby_debug.c in ruby-debug-0.1.3
- old
+ new
@@ -2,11 +2,11 @@
#include <ruby.h>
#include <node.h>
#include <rubysig.h>
#include <st.h>
-#define DEBUG_VERSION "0.1.2"
+#define DEBUG_VERSION "0.1.3"
typedef struct {
int thnum;
int stop_next;
int dest_frame;
@@ -27,11 +27,11 @@
} debug_frame_t;
typedef struct {
VALUE source;
VALUE pos;
- VALUE disabled;
+ VALUE expr;
} debug_breakpoint_t;
static VALUE threads_tbl = Qnil;
static VALUE breakpoints = Qnil;
static VALUE catchpoint = Qnil;
@@ -108,12 +108,12 @@
debug_check_started();
context = rb_hash_aref(threads_tbl, thread);
if(context == Qnil)
{
- context = debug_context_create(thread);
- rb_hash_aset(threads_tbl, thread, context);
+ context = debug_context_create(thread);
+ rb_hash_aset(threads_tbl, thread, context);
}
return context;
}
static void
@@ -140,16 +140,15 @@
return result;
}
static VALUE
-call_debug_command(VALUE context, int thnum, VALUE self, ID mid, VALUE file, VALUE line)
+call_debug_command(VALUE context, int thnum, VALUE binding, ID mid, VALUE file, VALUE line)
{
- VALUE id, binding;
+ VALUE id;
id = mid ? ID2SYM(mid) : Qnil;
- binding = self? create_binding(self) : Qnil;
last_debugged_thnum = thnum;
debug_suspend(mDebugger);
return rb_funcall(context, idDebugCommand, 4, file, line, id, binding);
}
@@ -160,14 +159,14 @@
VALUE frame;
debug_frame_t *top_frame;
if(RARRAY(debug_context->frames)->len > 0)
{
- frame = *RARRAY(debug_context->frames)->ptr;
- Data_Get_Struct(frame, debug_frame_t, top_frame);
- top_frame->file = file;
- top_frame->line = line;
+ frame = *RARRAY(debug_context->frames)->ptr;
+ Data_Get_Struct(frame, debug_frame_t, top_frame);
+ top_frame->file = file;
+ top_frame->line = line;
}
}
static void
save_call_frame(VALUE self, VALUE file, VALUE line, ID mid, debug_context_t *debug_context)
@@ -178,37 +177,30 @@
frame = debug_frame_create(file, line, binding, mid);
rb_ary_unshift(debug_context->frames, frame);
}
static int
-check_breakpoints(VALUE context, VALUE file, VALUE klass, VALUE pos, ID id)
+check_breakpoints(VALUE context, VALUE file, VALUE klass, VALUE pos)
{
VALUE breakpoint;
debug_breakpoint_t *debug_breakpoint;
int i;
- int result = 0;
if(RARRAY(breakpoints)->len == 0)
- return 0;
+ return -1;
for(i = 0; i < RARRAY(breakpoints)->len; i++)
{
- breakpoint = rb_ary_entry(breakpoints, i);
- Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
- if(debug_breakpoint->pos != pos && !(TYPE(pos) == T_STRING &&
- TYPE(debug_breakpoint->pos) == T_STRING && rb_str_cmp(debug_breakpoint->pos, pos) == 0))
- continue;
- if(TYPE(debug_breakpoint->source) == T_STRING &&
- rb_str_cmp(debug_breakpoint->source, file) == 0 ||
- klass != Qnil && debug_breakpoint->source == klass)
- {
- result = 1;
- rb_funcall(context, idAtBreakpoint, 2, breakpoint, id ? rb_str_new2(rb_id2name(id)) : Qnil);
- continue;
- }
-
+ breakpoint = rb_ary_entry(breakpoints, i);
+ Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
+ if(debug_breakpoint->pos != pos && !(TYPE(pos) == T_STRING &&
+ TYPE(debug_breakpoint->pos) == T_STRING && rb_str_cmp(debug_breakpoint->pos, pos) == 0))
+ continue;
+ if(rb_str_cmp(debug_breakpoint->source, file) == 0 ||
+ klass != Qnil && rb_str_cmp(debug_breakpoint->source, rb_mod_name(klass)) == 0)
+ return i;
}
- return result;
+ return -1;
}
/*
* This is a NASTY HACK. For some reasons rb_f_binding is decalred
* static in eval.c
@@ -219,43 +211,71 @@
typedef VALUE (*bind_func_t)(VALUE);
static bind_func_t f_binding = NULL;
if(f_binding == NULL)
{
- ID idBinding = rb_intern("binding");
- NODE *body, *method;
- st_lookup(RCLASS(rb_mKernel)->m_tbl, idBinding, (st_data_t *)&body);
- method = (NODE *)body->u2.value;
- f_binding = (bind_func_t)method->u1.value;
+ ID idBinding = rb_intern("binding");
+ NODE *body, *method;
+ st_lookup(RCLASS(rb_mKernel)->m_tbl, idBinding, (st_data_t *)&body);
+ method = (NODE *)body->u2.value;
+ f_binding = (bind_func_t)method->u1.value;
}
return f_binding(self);
}
static void
check_suspend(debug_context_t *debug_context)
{
if(rb_thread_critical == Qtrue)
- return;
+ return;
while(1)
{
- rb_thread_critical = Qtrue;
- if(!debug_context->suspend)
- break;
- rb_ary_push(waiting, rb_thread_current());
- debug_context->suspend = 0;
- rb_thread_stop();
+ rb_thread_critical = Qtrue;
+ if(!debug_context->suspend)
+ break;
+ rb_ary_push(waiting, rb_thread_current());
+ debug_context->suspend = 0;
+ rb_thread_stop();
}
rb_thread_critical = Qfalse;
}
+static VALUE
+get_breakpoint_at(int index)
+{
+ return rb_ary_entry(breakpoints, index);
+}
+
+static VALUE
+eval_expression(VALUE args)
+{
+ return rb_funcall(rb_mKernel, rb_intern("eval"), 2, RARRAY(args)->ptr[0], RARRAY(args)->ptr[1]);
+}
+
+static 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
debug_event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
{
- VALUE thread;
- VALUE context;
+ VALUE thread, context, binding, breakpoint;
debug_context_t *debug_context;
+ debug_breakpoint_t *debug_breakpoint;
VALUE file = Qnil, line = Qnil;
+ int breakpoint_index = -1;
static int debugging = 0;
if(debugging) return;
@@ -266,127 +286,146 @@
Data_Get_Struct(context, debug_context_t, debug_context);
check_suspend(debug_context);
if(node)
{
- file = rb_str_new2(node->nd_file);
- line = INT2FIX(nd_line(node));
+ file = rb_str_new2(node->nd_file);
+ line = INT2FIX(nd_line(node));
}
switch(event)
{
- case RUBY_EVENT_LINE:
- {
- set_frame_source(debug_context, file, line);
+ case RUBY_EVENT_LINE:
+ {
+ set_frame_source(debug_context, file, line);
- if(RTEST(tracing) || debug_context->tracing )
- {
- rb_funcall(context, idAtTracing, 2, file, line);
- }
+ if(RTEST(tracing) || debug_context->tracing )
+ {
+ rb_funcall(context, idAtTracing, 2, file, line);
+ }
- if(debug_context->dest_frame == -1 ||
- RARRAY(debug_context->frames)->len == debug_context->dest_frame)
- {
- debug_context->stop_next--;
- if(debug_context->stop_next < 0)
- debug_context->stop_next = -1;
- /* we check that we actualy moved to another line */
- if(line != Qnil && debug_context->src_line != FIX2INT(line))
- {
- debug_context->stop_line--;
- }
- }
- else if(RARRAY(debug_context->frames)->len < debug_context->dest_frame)
- {
- debug_context->stop_next = 0;
- }
+ if(debug_context->dest_frame == -1 ||
+ RARRAY(debug_context->frames)->len == debug_context->dest_frame)
+ {
+ debug_context->stop_next--;
+ if(debug_context->stop_next < 0)
+ debug_context->stop_next = -1;
+ /* we check that we actualy moved to another line */
+ if(line != Qnil && debug_context->src_line != FIX2INT(line))
+ {
+ debug_context->stop_line--;
+ }
+ }
+ else if(RARRAY(debug_context->frames)->len < debug_context->dest_frame)
+ {
+ debug_context->stop_next = 0;
+ }
- if(RARRAY(debug_context->frames)->len == 0)
- {
- save_call_frame(self, file, line, mid, debug_context);
- }
+ if(RARRAY(debug_context->frames)->len == 0)
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ }
- if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
- check_breakpoints(context, file, klass, line, mid))
- {
- /* reset all pointers */
- debug_context->dest_frame = -1;
- debug_context->src_line = -1;
- debug_context->stop_line = -1;
- debug_context->stop_next = -1;
+ if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
+ (breakpoint_index = check_breakpoints(context, file, klass, line)) != -1)
+ {
+ binding = self? create_binding(self) : Qnil;
+ /* check breakpoint expression */
+ if(breakpoint_index != -1)
+ {
+ breakpoint = get_breakpoint_at(breakpoint_index);
+ if(check_breakpoint_expression(breakpoint, binding))
+ rb_funcall(context, idAtBreakpoint, 2, breakpoint, mid ? rb_str_new2(rb_id2name(mid)) : Qnil);
+ else
+ break;
+ }
- call_debug_command(context, debug_context->thnum, self, mid, file, line);
- }
- break;
- }
- case RUBY_EVENT_C_CALL:
- {
- if(node)
- {
- set_frame_source(debug_context, file, line);
- }
- break;
- }
- case RUBY_EVENT_CALL:
- {
- save_call_frame(self, file, line, mid, debug_context);
- if(check_breakpoints(context, file, klass, rb_str_new2(rb_id2name(mid)), mid))
- {
- call_debug_command(context, debug_context->thnum, self, mid, file, line);
- }
- break;
- }
- case RUBY_EVENT_RETURN:
- case RUBY_EVENT_END:
- {
- if(RARRAY(debug_context->frames)->len == debug_context->stop_frame)
- {
- debug_context->stop_next = 1;
- debug_context->stop_frame = 0;
- }
- rb_ary_shift(debug_context->frames);
- break;
- }
- case RUBY_EVENT_CLASS:
- {
- save_call_frame(self, file, line, mid, debug_context);
- break;
- }
- case RUBY_EVENT_RAISE:
- {
- VALUE ancestors;
- VALUE expn_class, aclass;
- int i, found = 0;
+ /* reset all pointers */
+ debug_context->dest_frame = -1;
+ debug_context->src_line = -1;
+ debug_context->stop_line = -1;
+ debug_context->stop_next = -1;
- expn_class = rb_obj_class(ruby_errinfo);
- if( !NIL_P(rb_class_inherited_p(expn_class, rb_eSystemExit)) )
- {
- debug_stop(mDebugger);
- rb_exit(0);
- }
+ call_debug_command(context, debug_context->thnum, binding, mid, file, line);
+ }
+ break;
+ }
+ case RUBY_EVENT_C_CALL:
+ {
+ if(node)
+ {
+ set_frame_source(debug_context, file, line);
+ }
+ break;
+ }
+ case RUBY_EVENT_CALL:
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ breakpoint_index = check_breakpoints(context, file, klass, rb_str_new2(rb_id2name(mid)));
+ if(breakpoint_index != -1)
+ {
+ binding = self? create_binding(self) : Qnil;
+ breakpoint = get_breakpoint_at(breakpoint_index);
+ if(check_breakpoint_expression(breakpoint, binding))
+ {
+ rb_funcall(context, idAtBreakpoint, 2, breakpoint, mid ? rb_str_new2(rb_id2name(mid)) : Qnil);
+ call_debug_command(context, debug_context->thnum, binding, mid, file, line);
+ }
+ }
+ break;
+ }
+ case RUBY_EVENT_RETURN:
+ case RUBY_EVENT_END:
+ {
+ if(RARRAY(debug_context->frames)->len == debug_context->stop_frame)
+ {
+ debug_context->stop_next = 1;
+ debug_context->stop_frame = 0;
+ }
+ rb_ary_shift(debug_context->frames);
+ break;
+ }
+ case RUBY_EVENT_CLASS:
+ {
+ save_call_frame(self, file, line, mid, debug_context);
+ break;
+ }
+ case RUBY_EVENT_RAISE:
+ {
+ VALUE ancestors;
+ VALUE expn_class, aclass;
+ int i, found = 0;
- if(catchpoint == Qnil)
- break;
+ expn_class = rb_obj_class(ruby_errinfo);
+ if( !NIL_P(rb_class_inherited_p(expn_class, rb_eSystemExit)) )
+ {
+ debug_stop(mDebugger);
+ rb_exit(0);
+ }
- ancestors = rb_mod_ancestors(expn_class);
- for(i = 0; i < RARRAY(ancestors)->len; i++)
- {
- aclass = rb_ary_entry(ancestors, i);
- if(rb_str_cmp(rb_mod_name(aclass), catchpoint) == 0)
- {
- rb_funcall(context, idAtCatchpoint, 0);
- call_debug_command(context, debug_context->thnum, self, mid, file, line);
- break;
- }
- }
+ if(catchpoint == Qnil)
+ break;
- break;
- }
- case RUBY_EVENT_C_RETURN:
- {
- break;
- }
+ ancestors = rb_mod_ancestors(expn_class);
+ for(i = 0; i < RARRAY(ancestors)->len; i++)
+ {
+ aclass = rb_ary_entry(ancestors, i);
+ if(rb_str_cmp(rb_mod_name(aclass), catchpoint) == 0)
+ {
+ rb_funcall(context, idAtCatchpoint, 0);
+ binding = self? create_binding(self) : Qnil;
+ call_debug_command(context, debug_context->thnum, binding, mid, file, line);
+ break;
+ }
+ }
+
+ break;
+ }
+ case RUBY_EVENT_C_RETURN:
+ {
+ break;
+ }
}
debugging--;
}
@@ -408,14 +447,14 @@
threads_tbl = rb_hash_new();
breakpoints = rb_ary_new();
waiting = rb_ary_new();
rb_add_event_hook(debug_event_hook,
- RUBY_EVENT_LINE | RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
- RUBY_EVENT_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_CLASS |
- RUBY_EVENT_END | RUBY_EVENT_RAISE
- );
+ RUBY_EVENT_LINE | RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_CLASS |
+ RUBY_EVENT_END | RUBY_EVENT_RAISE
+ );
return Qnil;
}
static VALUE
debug_stop(VALUE self)
@@ -435,24 +474,31 @@
{
debug_breakpoint_t *breakpoint;
breakpoint = (debug_breakpoint_t *)data;
rb_gc_mark(breakpoint->source);
rb_gc_mark(breakpoint->pos);
+ rb_gc_mark(breakpoint->expr);
}
static VALUE
-debug_add_breakpoint(VALUE self, VALUE source, VALUE pos)
+debug_add_breakpoint(int argc, VALUE *argv, VALUE self)
{
+ VALUE source, pos, expr;
VALUE result;
debug_breakpoint_t *breakpoint;
debug_check_started();
+
+ if(rb_scan_args(argc, argv, "21", &source, &pos, &expr) == 2)
+ {
+ expr = Qnil;
+ }
breakpoint = ALLOC(debug_breakpoint_t);
- breakpoint->source = source;
+ breakpoint->source = StringValue(source);
breakpoint->pos = pos;
- breakpoint->disabled = Qfalse;
+ breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
result = Data_Wrap_Struct(cBreakpoint, breakpoint_mark, xfree, breakpoint);
rb_ary_push(breakpoints, result);
return result;
}
@@ -476,16 +522,16 @@
debug_set_catchpoint(VALUE self, VALUE value)
{
debug_check_started();
if (!NIL_P(value) && TYPE(value) != T_STRING) {
- rb_raise(rb_eTypeError, "value of checkpoint must be String");
+ rb_raise(rb_eTypeError, "value of checkpoint must be String");
}
if(NIL_P(value))
- catchpoint = Qnil;
+ catchpoint = Qnil;
else
- catchpoint = rb_str_dup(value);
+ catchpoint = rb_str_dup(value);
return value;
}
static VALUE
debug_interrupt(VALUE self)
@@ -508,12 +554,12 @@
{
debug_context_t *debug_context;
Data_Get_Struct(value, debug_context_t, debug_context);
if(debug_context->thnum == last_debugged_thnum)
{
- *result = value;
- return ST_STOP;
+ *result = value;
+ return ST_STOP;
}
return ST_CONTINUE;
}
static VALUE
@@ -533,12 +579,12 @@
debug_check_started();
context = find_last_context();
if(context != Qnil)
{
- Data_Get_Struct(context, debug_context_t, debug_context);
- debug_context->stop_next = 1;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->stop_next = 1;
}
return Qnil;
}
@@ -568,23 +614,23 @@
new_list = rb_ary_new();
list = rb_funcall(rb_cThread, rb_intern("list"), 0);
for(i = 0; i < RARRAY(list)->len; i++)
{
- thread = rb_ary_entry(list, i);
- context = thread_context_lookup(thread);
- rb_ary_push(new_list, context);
+ thread = rb_ary_entry(list, i);
+ context = thread_context_lookup(thread);
+ rb_ary_push(new_list, context);
}
/*
* I wonder why rb_hash_clear is declared static?
*/
rb_funcall(threads_tbl, rb_intern("clear"), 0);
for(i = 0; i < RARRAY(new_list)->len; i++)
{
- context = rb_ary_entry(new_list, i);
- Data_Get_Struct(context, debug_context_t, debug_context);
- rb_hash_aset(threads_tbl, debug_context->thread, context);
+ context = rb_ary_entry(new_list, i);
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ rb_hash_aset(threads_tbl, debug_context->thread, context);
}
return new_list;
}
@@ -604,20 +650,20 @@
context_list = debug_contexts(self);
current = thread_context_lookup(rb_thread_current());
for(i = 0; i < RARRAY(context_list)->len; i++)
{
- context = rb_ary_entry(context_list, i);
- if(current == context)
- continue;
- Data_Get_Struct(context, debug_context_t, debug_context);
- debug_context->suspend = 1;
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->suspend = 1;
}
rb_thread_critical = saved_crit;
if(rb_thread_critical == Qfalse)
- rb_thread_schedule();
+ rb_thread_schedule();
return context;
}
static VALUE
@@ -637,20 +683,20 @@
context_list = debug_contexts(self);
current = thread_context_lookup(rb_thread_current());
for(i = 0; i < RARRAY(context_list)->len; i++)
{
- context = rb_ary_entry(context_list, i);
- if(current == context)
- continue;
- Data_Get_Struct(context, debug_context_t, debug_context);
- debug_context->suspend = 0;
+ context = rb_ary_entry(context_list, i);
+ if(current == context)
+ continue;
+ Data_Get_Struct(context, debug_context_t, debug_context);
+ debug_context->suspend = 0;
}
for(i = 0; i < RARRAY(waiting)->len; i++)
{
- thread = rb_ary_entry(waiting, i);
- rb_thread_run(thread);
+ thread = rb_ary_entry(waiting, i);
+ rb_thread_run(thread);
}
rb_ary_clear(waiting);
rb_thread_critical = saved_crit;
rb_thread_schedule();
@@ -678,11 +724,11 @@
debug_check_started();
Data_Get_Struct(self, debug_context_t, debug_context);
if(FIX2INT(steps) < 0)
- rb_raise(rb_eRuntimeError, "Steps argument can't be negative.");
+ rb_raise(rb_eRuntimeError, "Steps argument can't be negative.");
debug_context->stop_next = FIX2INT(steps);
return steps;
}
@@ -695,44 +741,44 @@
debug_check_started();
Data_Get_Struct(self, debug_context_t, debug_context);
if(RARRAY(debug_context->frames)->len == 0)
- rb_raise(rb_eRuntimeError, "No frames collected.");
+ rb_raise(rb_eRuntimeError, "No frames collected.");
rb_scan_args(argc, argv, "11", &lines, &frame);
debug_context->stop_line = FIX2INT(lines);
if(argc == 1)
{
- debug_context->dest_frame = RARRAY(debug_context->frames)->len;
+ debug_context->dest_frame = RARRAY(debug_context->frames)->len;
}
else
{
- if(FIX2INT(frame) < 0 && FIX2INT(frame) >= RARRAY(debug_context->frames)->len)
- rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
- debug_context->dest_frame = FIX2INT(frame);
+ if(FIX2INT(frame) < 0 && FIX2INT(frame) >= RARRAY(debug_context->frames)->len)
+ rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
+ debug_context->dest_frame = FIX2INT(frame);
}
-
+
cur_frame = *RARRAY(debug_context->frames)->ptr;
Data_Get_Struct(cur_frame, debug_frame_t, debug_frame);
debug_context->src_line = FIX2INT(debug_frame->line);
-
+
return Qnil;
}
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) >= RARRAY(debug_context->frames)->len)
- rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
+ rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
debug_context->stop_frame = FIX2INT(frame);
-
+
return frame;
}
static VALUE
context_frames(VALUE self)
@@ -860,10 +906,20 @@
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return breakpoint->pos;
}
+static VALUE
+breakpoint_expr(VALUE self)
+{
+ debug_breakpoint_t *breakpoint;
+
+ Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
+ return breakpoint->expr;
+}
+
+
#if defined(_WIN32)
__declspec(dllexport)
#endif
void
Init_ruby_debug()
@@ -873,11 +929,11 @@
rb_define_module_function(mDebugger, "start", debug_start, 0);
rb_define_module_function(mDebugger, "stop", debug_stop, 0);
rb_define_module_function(mDebugger, "started?", debug_is_started, 0);
rb_define_module_function(mDebugger, "thnum", debug_thnum, 0);
rb_define_module_function(mDebugger, "breakpoints", debug_breakpoints, 0);
- rb_define_module_function(mDebugger, "add_breakpoint", debug_add_breakpoint, 2);
+ rb_define_module_function(mDebugger, "add_breakpoint", debug_add_breakpoint, -1);
rb_define_module_function(mDebugger, "catchpoint", debug_catchpoint, 0);
rb_define_module_function(mDebugger, "catchpoint=", debug_set_catchpoint, 1);
rb_define_module_function(mDebugger, "interrupt", debug_interrupt, 0);
rb_define_module_function(mDebugger, "interrupt_last", debug_interrupt_last, 0);
rb_define_module_function(mDebugger, "contexts", debug_contexts, 0);
@@ -906,9 +962,10 @@
rb_define_method(cFrame, "id", frame_id, 0);
cBreakpoint = rb_define_class_under(mDebugger, "Breakpoint", rb_cObject);
rb_define_method(cBreakpoint, "source", breakpoint_source, 0);
rb_define_method(cBreakpoint, "pos", breakpoint_pos, 0);
+ rb_define_method(cBreakpoint, "expr", breakpoint_expr, 0);
idDebugCommand = rb_intern("debug_command");
idAtBreakpoint = rb_intern("at_breakpoint");
idAtCatchpoint = rb_intern("at_catchpoint");
idAtTracing = rb_intern("at_tracing");