<NME> ruby_debug.c
<BEF> #include <ruby.h>
#include <stdio.h>
#include <ctype.h>
#include <vm_core.h>
#include <iseq.h>
#include <version.h>
#include <eval_intern.h>
#include <gc.h>
#include <insns.inc>
#include <insns_info.inc>
#include "ruby_debug.h"

#define DEBUG_VERSION "0.12"

#define GET_CFP     debug_context->cfp[check_frame_number(debug_context, frame)]
#define ZFREE(ptr)  do { if(ptr != NULL) { free(ptr); ptr = NULL; } } while(0)

#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif

RUBY_EXTERN int rb_vm_get_sourceline(const rb_control_frame_t *cfp); /* from vm.c */

/* from iseq.c */
#ifdef 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 hook_off           = Qtrue;
static VALUE tracing            = Qfalse;
static VALUE locker             = Qnil;
static VALUE debug_flag         = Qfalse;
static VALUE catchall           = Qtrue;
static VALUE skip_next_exception= 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 bin_opt_call_c_function;
static VALUE bin_getdynamic;
static VALUE bin_throw;
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 ID id_binding_n;
static ID id_frame_binding;

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 debug_stop(VALUE);
static void save_current_position(debug_context_t *);
static void context_suspend_0(debug_context_t *);
static void context_resume_0(debug_context_t *);
static void thread_context_lookup(VALUE, VALUE *, debug_context_t **, int);
static VALUE debug_current_context(VALUE self);
static int find_prev_line_start(rb_control_frame_t *cfp);


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";
  }
}

static void
debug_runtime_error(debug_context_t *debug_context, const char *err)
{
    if (debug_context != NULL) CTX_FL_SET(debug_context, CTX_FL_IGNORE);
    rb_raise(rb_eRuntimeError, err);
}

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 void *
ruby_method_ptr(VALUE class, ID meth_id)
{
#ifndef HAVE_RB_METHOD_ENTRY
    NODE *body, *method;
    st_lookup(RCLASS_M_TBL(class), meth_id, (st_data_t *)&body);
    method = (NODE *)body->u2.value;
    return (void *)method->u2.node->u1.value;
#else
    rb_method_entry_t * method;
    method = rb_method_entry(class, meth_id);
#ifdef HAVE_ST_BODY
    return (void *)method->body.cfunc.func;
#else
    return (void *)method->def->body.cfunc.func;
#endif
#endif
}

inline static VALUE
ref2id(VALUE obj)
{
    return rb_obj_id(obj);
}

static VALUE
id2ref_unprotected(VALUE id)
{
    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);
    static id2ref_func_t f_id2ref = NULL;
    if(f_id2ref == NULL)
    {
        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));
    }
    return f_id2ref(rb_mObjectSpace, id);
}

static VALUE
id2ref_error()
{
    if(debug_flag == Qtrue)
        rb_p(rb_errinfo());
    return Qnil;
}

static VALUE
id2ref(VALUE id)
{
    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);
}

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()

#define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))

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;

static VALUE
is_thread_alive(VALUE thread)
{
    typedef VALUE (*thread_alive_func_t)(VALUE);
    static thread_alive_func_t f_thread_alive = NULL;
    if(!f_thread_alive)
    {
        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));
    }
    return f_thread_alive(thread);
}

static int

    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);
}

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_THREAD()->parse_in_eval++;
    GET_THREAD()->mild_compile_error++;
    /* compiling with option Qfalse (no options) prevents debug hook calls during this catch routine
     */
#ifdef 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_THREAD()->mild_compile_error--;
    GET_THREAD()->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);
}

static rb_control_frame_t *
FUNC_FASTCALL(do_jump)(rb_thread_t *th, rb_control_frame_t *cfp)
{
    VALUE context;
    debug_context_t *debug_context;
    rb_control_frame_t *jump_cfp;
    VALUE *jump_pc;

    thread_context_lookup(th->self, &context, &debug_context, 0);
    if (debug_context == NULL)
        debug_runtime_error(NULL, "Lost context in jump");
    cfp->pc[-2] = debug_context->saved_jump_ins[0];
    cfp->pc[-1] = debug_context->saved_jump_ins[1];

    if ((debug_context->jump_pc < debug_context->jump_cfp->iseq->iseq_encoded) ||
        (debug_context->jump_pc >= debug_context->jump_cfp->iseq->iseq_encoded + debug_context->jump_cfp->iseq->iseq_size))
        debug_runtime_error(debug_context, "Invalid jump PC target");

    jump_cfp = debug_context->jump_cfp;
    jump_pc = debug_context->jump_pc;
    debug_context->jump_pc = NULL;
    debug_context->jump_cfp = NULL;
    if (!CTX_FL_TEST(debug_context, CTX_FL_EXCEPTION_TEST))
    {
        debug_context->last_line = 0;
        debug_context->last_file = NULL;
        debug_context->stop_next = 1;
    }

    if (cfp < jump_cfp)
    {
        /* save all intermediate-frame catch tables
           +1 for target frame
           +1 for array terminator
         */
        int frames = jump_cfp - cfp + 2;
        debug_context->old_iseq_catch = (iseq_catch_t*)malloc(frames * sizeof(iseq_catch_t));
        MEMZERO(debug_context->old_iseq_catch, iseq_catch_t, frames);
        frames = 0;
        do
        {
            if (cfp->iseq != NULL)
            {
                debug_context->old_iseq_catch[frames].iseq = cfp->iseq;
                debug_context->old_iseq_catch[frames].catch_table = cfp->iseq->catch_table;
                debug_context->old_iseq_catch[frames].catch_table_size = cfp->iseq->catch_table_size;
                cfp->iseq->catch_table = NULL;
                cfp->iseq->catch_table_size = 0;

                frames++;
            }
            cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
        } while (cfp <= jump_cfp);

        jump_cfp->iseq->catch_table_size = 1;
        jump_cfp->iseq->catch_table =
            create_catch_table(debug_context, jump_pc - jump_cfp->iseq->iseq_encoded);
        jump_cfp->iseq->catch_table->sp = -1;

        JUMP_TAG(TAG_RAISE);
    }
    else if (cfp > jump_cfp)
        debug_runtime_error(debug_context, "Invalid jump frame target");

    cfp->pc = jump_pc;
    return(cfp);
}

static rb_control_frame_t *
FUNC_FASTCALL(do_catchall)(rb_thread_t *th, rb_control_frame_t *cfp)
{
    int size;
    VALUE context;
    debug_context_t *debug_context;

    thread_context_lookup(th->self, &context, &debug_context, 0);
    if (debug_context == NULL)
        debug_runtime_error(NULL, "Lost context in catchall");
    if (debug_context->saved_frames == NULL)
        return(cfp); /* re-throw exception */

    if (CTX_FL_TEST(debug_context, CTX_FL_RETHROW))
    {
        cfp->pc[-2] = debug_context->saved_jump_ins[0];
        cfp->pc[-1] = debug_context->saved_jump_ins[1];
    }
    else
    {
        CTX_FL_SET(debug_context, CTX_FL_CATCHING);
        CTX_FL_UNSET(debug_context, CTX_FL_EXCEPTION_TEST);
    }

    /* restore the call frame state */
    ZFREE(debug_context->cfp);
    debug_context->cfp_count = debug_context->saved_cfp_count;
    debug_context->saved_cfp_count = 0;
    size = sizeof(rb_control_frame_t*) * debug_context->cfp_count;
    debug_context->cfp = (rb_control_frame_t**)malloc(size);
    memcpy(debug_context->cfp, debug_context->saved_cfp, size);
    ZFREE(debug_context->saved_cfp);

    size = sizeof(rb_control_frame_t) *
        ((debug_context->cfp[debug_context->cfp_count-1] - debug_context->cfp[0]) + 1);
    memcpy(debug_context->cfp[0], debug_context->saved_frames, size);

    ZFREE(debug_context->saved_frames);
    th->cfp = debug_context->cfp[0];
    th->cfp->pc = th->cfp->iseq->iseq_encoded + find_prev_line_start(th->cfp);
    return(th->cfp);
}

static void
create_exception_catchall(debug_context_t *debug_context)
{
    rb_iseq_t *iseq;
    struct iseq_catch_table_entry *entry;
    rb_control_frame_t *cfp = GET_THREAD()->cfp;
    while (cfp->iseq == NULL)
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);

    debug_context->start_cfp = cfp;
    if (catchall == Qfalse)
        return;

    iseq = cfp->iseq;
    iseq->catch_table_size++;
    entry = ALLOC_N(struct iseq_catch_table_entry, iseq->catch_table_size);

    if (iseq->catch_table_size > 1)
    {
        MEMCPY(entry, iseq->catch_table, struct iseq_catch_table_entry, iseq->catch_table_size-1);
        xfree(iseq->catch_table);
        iseq->catch_table = entry;
        entry += iseq->catch_table_size-1;
    }
    else
        iseq->catch_table = entry;

    memset(&debug_context->catch_iseq, 0, sizeof(struct rb_iseq_struct));
    memset(&debug_context->catch_cref_stack, 0, sizeof(struct RNode));
    debug_context->catch_rdata.basic.flags = RUBY_T_DATA;
    debug_context->catch_rdata.basic.klass = 0;
    debug_context->catch_rdata.dmark = NULL;
    debug_context->catch_rdata.dfree = NULL;
    debug_context->catch_rdata.data = &debug_context->catch_iseq;
    debug_context->catch_iseq.type = ISEQ_TYPE_RESCUE;
    debug_context->catch_iseq.name = rb_str_new_cstr("(exception catcher)");
    debug_context->catch_iseq.filename = rb_str_new_cstr("(exception catcher)");
    debug_context->catch_iseq.iseq = debug_context->iseq_insn;
    debug_context->catch_iseq.iseq_encoded = NULL;
    debug_context->catch_iseq.iseq[0] = BIN(opt_call_c_function);
    debug_context->catch_iseq.iseq[1] = (VALUE)do_catchall;
    debug_context->catch_iseq.iseq[2] = BIN(getdynamic);
    debug_context->catch_iseq.iseq[3] = 1; /* #$!, only value in local_table */
    debug_context->catch_iseq.iseq[4] = 0;
    debug_context->catch_iseq.iseq[5] = BIN(throw);
    debug_context->catch_iseq.iseq[6] = 0;
    debug_context->catch_iseq.iseq_size = sizeof(debug_context->iseq_insn) / sizeof(VALUE);
    debug_context->catch_iseq.mark_ary = rb_ary_new();
    debug_context->catch_iseq.line_info_table = &debug_context->catch_info_entry;
    debug_context->catch_iseq.line_info_size = 1;
    debug_context->catch_iseq.local_size = 1;
    debug_context->catch_iseq.local_table = &debug_context->local_table;
    debug_context->catch_iseq.local_table[0] = rb_intern("#$!");
    debug_context->catch_iseq.arg_simple = 1;
    debug_context->catch_iseq.arg_rest = -1;
    debug_context->catch_iseq.arg_block = -1;
    debug_context->catch_iseq.stack_max = 1;
    debug_context->catch_iseq.local_iseq = &debug_context->catch_iseq;
    debug_context->catch_iseq.self = (VALUE)&debug_context->catch_rdata;
    debug_context->catch_iseq.cref_stack = &debug_context->catch_cref_stack;
    debug_context->catch_info_entry.position = 0;
    debug_context->catch_info_entry.line_no = 1;
#if defined HAVE_TYPE_STRUCT_ISEQ_INSN_INFO_ENTRY
    debug_context->catch_info_entry.sp = 0;
#endif
    debug_context->catch_cref_stack.flags = 1052;

    entry->type = CATCH_TYPE_RESCUE;
    entry->iseq = debug_context->catch_iseq.self;
    entry->start = 0;
    entry->end = ULONG_MAX;
    entry->cont = 0;
    entry->sp = 0;

    rb_iseq_translate_threaded_code(&debug_context->catch_iseq);
}

/*
 *   call-seq:
 *      Debugger.started? -> bool
 *
 *   Returns +true+ the debugger is started.
 *   Deprecated.
 */
static VALUE
debug_is_started(VALUE self)
{
    return Qtrue;
}

static void
debug_context_mark(void *data)
{
    debug_context_t *debug_context = (debug_context_t *)data;
    rb_gc_mark(debug_context->breakpoint);
}

static void
debug_context_free(void *data)
{
    debug_context_t *debug_context = (debug_context_t *)data;
}

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->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->last_exception = Qnil;
    debug_context->thread_pause = 0;
    debug_context->cfp_count = 0;
    debug_context->start_cfp = NULL;
    debug_context->cur_cfp = NULL;
    debug_context->top_cfp = NULL;
    debug_context->catch_cfp = NULL;
    debug_context->saved_frames = NULL;
    debug_context->saved_cfp = NULL;
    debug_context->saved_cfp_count = 0;
    debug_context->cfp = NULL;
    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 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;

    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));
}

#if defined DOSISH
#define isdirsep(x) ((x) == '/' || (x) == '\\')
#else
#define isdirsep(x) ((x) == '/')
#endif

int
filename_cmp(VALUE source, const char *file)
{
    const 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
binding_alloc(VALUE klass)
{
    VALUE obj;
    rb_binding_t *bind;
    static const rb_data_type_t *binding_data_type;
    if (!binding_data_type) {
        VALUE b = rb_binding_new();
        binding_data_type = RTYPEDDATA_TYPE(b);
    }
    obj = TypedData_Make_Struct(klass, rb_binding_t, binding_data_type, bind);
    return obj;
}

static void
save_current_position(debug_context_t *debug_context)
{
    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 ((klass == rb_mKernel) && (strcmp(rb_id2name(mid), "raise") == 0)) return 0;
    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, const char *file, int line)
{
    debug_context->stop_reason = CTX_STOP_STEP;

    /* check breakpoint expression */
    if(breakpoint != Qnil)
    {
        if(!check_breakpoint_expression(breakpoint, rb_binding_new()))
            return;
        if(!check_breakpoint_hit_condition(breakpoint))
            return;
        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 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);
}

static int
find_prev_line_start(rb_control_frame_t *cfp)
{
#if defined HAVE_TYPE_STRUCT_ISEQ_LINE_INFO_ENTRY
    const rb_iseq_t *iseq = cfp->iseq;
    return rb_iseq_line_no(iseq, cfp->pc - iseq->iseq_encoded - 1);
#else
    int i, pos, line_no;
    pos = cfp->pc - cfp->iseq->iseq_encoded;
    for (i = 0; i < cfp->iseq->insn_info_size; i++)
    {
        if (cfp->iseq->insn_info_table[i].position != pos)
            continue;

        if (i == 0) return(0); // TODO

        pos = -1;
        line_no = cfp->iseq->insn_info_table[--i].line_no;
        do {
            if (cfp->iseq->insn_info_table[i].line_no == line_no)
                pos = cfp->iseq->insn_info_table[i].position;
            else
                break;
            i--;
        } while (i >= 0);

        return(pos);
    }
    return(-1);
#endif
}

static rb_control_frame_t *
FUNC_FASTCALL(do_catch)(rb_thread_t *th, rb_control_frame_t *cfp)
{
    VALUE context;
    debug_context_t *debug_context;

    thread_context_lookup(th->self, &context, &debug_context, 0);
    if (debug_context == NULL)
        debug_runtime_error(NULL, "Lost context in catch");
    cfp->pc[-2] = debug_context->saved_jump_ins[0];
    cfp->pc[-1] = debug_context->saved_jump_ins[1];

    cfp->pc = debug_context->jump_pc;

    debug_context->jump_pc = NULL;
    debug_context->jump_cfp = NULL;

    /* 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;
    CTX_FL_SET(debug_context, CTX_FL_CATCHING);
    th->cfp->sp--;

    return(cfp);
}

static int
catch_exception(debug_context_t *debug_context, VALUE mod_name)
{
    int prev_line_start;
    rb_control_frame_t *cfp = GET_THREAD()->cfp;
    while (cfp->pc == 0 || cfp->iseq == 0)
	cfp++;

    prev_line_start = find_prev_line_start(cfp);
    if (prev_line_start < 0)
        return(0);

    /* save the current catch table */
    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, 0);

    /* hijack the currently running code so that we can change the frame PC */
    debug_context->saved_jump_ins[0] = cfp->iseq->iseq_encoded[0];
    debug_context->saved_jump_ins[1] = cfp->iseq->iseq_encoded[1];
    cfp->iseq->iseq_encoded[0] = bin_opt_call_c_function;
    cfp->iseq->iseq_encoded[1] = (VALUE)do_catch;
    debug_context->jump_pc = cfp->iseq->iseq_encoded + prev_line_start;
    debug_context->jump_cfp = NULL;
    return(1);
}

static void
set_cfp(debug_context_t *debug_context)
{
    int cfp_count = 0;
    int has_changed = 0;
    rb_control_frame_t *cfp = debug_context->cur_cfp;
    while (cfp <= debug_context->start_cfp)
    {
        if (cfp->iseq != NULL)
        {
            if (cfp->pc != NULL)
            {
                if ((cfp_count < debug_context->cfp_count) && (debug_context->cfp[cfp_count] != cfp))
                    has_changed = 1;
                cfp_count++;
            }
        }
        else if (RUBYVM_CFUNC_FRAME_P(cfp))
        {
            if ((cfp_count < debug_context->cfp_count) && (debug_context->cfp[cfp_count] != cfp))
                has_changed = 1;
            cfp_count++;
        }
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
    }

    //if (cfp_count == debug_context->cfp_count && !has_changed)
      //  return;

    ZFREE(debug_context->cfp);
    debug_context->cfp = (rb_control_frame_t**)malloc(sizeof(rb_control_frame_t) * cfp_count);
    debug_context->cfp_count = 0;

    cfp = debug_context->cur_cfp;
    while (cfp <= debug_context->start_cfp)
    {
        if (cfp->iseq != NULL)
            if (cfp->pc != NULL) debug_context->cfp[debug_context->cfp_count++] = cfp;
        else if (RUBYVM_CFUNC_FRAME_P(cfp))
            debug_context->cfp[debug_context->cfp_count++] = cfp;
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
    }
}

static void
save_frames(debug_context_t *debug_context)
{
    int size;
    VALUE thread;
    rb_thread_t *th;

    if (debug_context->cfp_count == 0)
        return;

    /* save the entire call frame state */
    thread = rb_thread_current();
    GetThreadPtr(thread, th);
    for (size = 0; size < debug_context->cfp_count; size++)
    {
        rb_binding_t *bind;
        VALUE bindval = binding_alloc(rb_cBinding);
        GetBindingPtr(bindval, bind);
        bind->env = rb_vm_make_env_object(th, debug_context->cfp[size]);
    }

    debug_context->catch_table.mod_name = rb_obj_class(rb_errinfo());
    debug_context->catch_table.errinfo = rb_errinfo();

    debug_context->catch_cfp = GET_THREAD()->cfp;
    ZFREE(debug_context->saved_frames);

    size = sizeof(rb_control_frame_t) *
        ((debug_context->cfp[debug_context->cfp_count-1] - debug_context->cfp[0]) + 1);
    debug_context->saved_frames = (rb_control_frame_t*)malloc(size);
    memcpy(debug_context->saved_frames, debug_context->cfp[0], size);

    ZFREE(debug_context->saved_cfp);
    size = sizeof(rb_control_frame_t*) * debug_context->cfp_count;
    debug_context->saved_cfp = (rb_control_frame_t**)malloc(size);
    memcpy(debug_context->saved_cfp, debug_context->cfp, size);
    debug_context->saved_cfp_count = debug_context->cfp_count;

    CTX_FL_SET(debug_context, CTX_FL_EXCEPTION_TEST);
}

static int
try_thread_lock(rb_thread_t *th, debug_context_t *debug_context)
{
    while(1)
    {
        /* halt execution of the current thread if the debugger
           is activated in another
         */
        while(locker != Qnil && locker != th->self)
        {
            add_to_locked(th->self);
            rb_thread_stop();
        }

        /* stop the current thread if it's marked as suspended */
        if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND) && locker != th->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(0);

    /* only the current thread can proceed */
    locker = th->self;
    return(1);
}

static int
handle_raise_event(rb_thread_t *th, debug_context_t *debug_context)
{
    VALUE ancestors;
    VALUE aclass;
    int i;

    ZFREE(debug_context->saved_frames);
    if (CTX_FL_TEST(debug_context, CTX_FL_RETHROW))
    {
        CTX_FL_UNSET(debug_context, CTX_FL_RETHROW);
        return(0);
    }
    if (debug_context->cfp_count == 0 || !try_thread_lock(th, debug_context))
        return(0);

    if (skip_next_exception == Qtrue)
    {
        skip_next_exception = Qfalse;
        debug_context->last_exception = rb_errinfo();
        return(1);
    }

    if (rb_errinfo() == debug_context->last_exception)
        return(1);

    debug_context->last_exception = Qnil;

    if (rdebug_catchpoints == Qnil ||
        (debug_context->cfp_count == 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
	)
    {
        if (catchall == Qfalse) return(1);
    }

    ancestors = rb_mod_ancestors(rb_obj_class(rb_errinfo()));
    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)
        {
            if (!catch_exception(debug_context, mod_name))
                debug_runtime_error(debug_context, "Could not catch exception");
            return(1);
        }
    }

    if (catchall == Qtrue)
        save_frames(debug_context);
    return(1);
}

static void
debug_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
{
    VALUE context;
    VALUE breakpoint = Qnil;
    debug_context_t *debug_context;
    int moved = 0;
    rb_thread_t *th;
    struct rb_iseq_struct *iseq;
    const char *file;
    int line = 0;

    if (hook_off == Qtrue)
        return;

    th   = GET_THREAD();
    iseq = th->cfp->iseq;
    hook_count++;
    thread_context_lookup(th->self, &context, &debug_context, 1);

    /* return if thread is marked as 'ignored'.
       debugger's threads are marked this way
     */
    if(CTX_FL_TEST(debug_context, CTX_FL_IGNORE)) return;

    if (debug_context->top_cfp && th->cfp >= debug_context->top_cfp)
        return;
    debug_context->catch_cfp = NULL;

    if (event == RUBY_EVENT_RAISE)
    {
        if (handle_raise_event(th, debug_context))
            goto cleanup;
        return;
    }

    if (iseq == NULL || th->cfp->pc == NULL)
        return;

    if (event == RUBY_EVENT_LINE && CTX_FL_TEST(debug_context, CTX_FL_EXCEPTION_TEST))
    {
        if (iseq->type == ISEQ_TYPE_ENSURE)
        {
            /* don't allow "ensure" blocks to execute yet: jump out of here by re-throwing exception */
            VALUE *jump_pc =
                iseq->iseq_encoded + iseq->iseq_size - insn_len(BIN(getdynamic)) - insn_len(BIN(throw));

            if (jump_pc[0] != bin_getdynamic || jump_pc[insn_len(BIN(getdynamic))] != bin_throw)
                debug_runtime_error(debug_context, "Unexpected instructions in ENSURE block");

            debug_context->jump_cfp = th->cfp;
            debug_context->jump_pc = jump_pc;
            debug_context->saved_jump_ins[0] = th->cfp->pc[0];
            debug_context->saved_jump_ins[1] = th->cfp->pc[1];
            th->cfp->pc[0] = bin_opt_call_c_function;
            th->cfp->pc[1] = (VALUE)do_jump;
            CTX_FL_SET(debug_context, CTX_FL_ENSURE_SKIPPED);
            return;
        }
        else if (iseq->type == ISEQ_TYPE_RESCUE)
        {
            CTX_FL_UNSET(debug_context, CTX_FL_EXCEPTION_TEST);
            if (CTX_FL_TEST(debug_context, CTX_FL_ENSURE_SKIPPED))
            {
                /* exception was caught by the code; need to start the whole thing over */
                debug_context->saved_jump_ins[0] = th->cfp->pc[0];
                debug_context->saved_jump_ins[1] = th->cfp->pc[1];
                th->cfp->pc[0] = bin_opt_call_c_function;
                th->cfp->pc[1] = (VALUE)do_catchall;
                CTX_FL_UNSET(debug_context, CTX_FL_ENSURE_SKIPPED);
                CTX_FL_SET(debug_context, CTX_FL_RETHROW);
                return;
            }
        }
    }

    if (iseq->defined_method_id == id_binding_n &&
        (self == rb_mKernel || self == mDebugger || klass == cContext || klass == 0))
        return;
    if (iseq->defined_method_id == id_frame_binding && (klass == cContext || klass == 0))
        return;

    if (event == RUBY_EVENT_LINE || event == RUBY_EVENT_CALL)
    {
        mid = iseq->defined_method_id;
        klass = iseq->klass;
    }

    if (mid == ID_ALLOCATOR) return;

    if (!try_thread_lock(th, debug_context))
        return;

    if (iseq->type != ISEQ_TYPE_RESCUE && iseq->type != ISEQ_TYPE_ENSURE)
    {
        if (debug_context->start_cfp == NULL || th->cfp > debug_context->start_cfp)
            create_exception_catchall(debug_context);
    }

    /* 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++;
        }
        ZFREE(debug_context->old_iseq_catch);
    }

    /* make sure all threads have event flag set so we'll get its events */
    st_foreach(th->vm->living_threads, set_thread_event_flag_i, 0);

    if (debug_context->thread_pause)
    {
        debug_context->thread_pause = 0;
        debug_context->stop_next = 1;
        debug_context->dest_frame = -1;
        moved = 1;
    }

    /* ignore a skipped section of code */
    if (CTX_FL_TEST(debug_context, CTX_FL_SKIPPED))
        goto cleanup;

    debug_context->cur_cfp = th->cfp;
    if (iseq->type != ISEQ_TYPE_RESCUE && iseq->type != ISEQ_TYPE_ENSURE)
        set_cfp(debug_context);

    /* There can be many event calls per line, but we only want
     *one* breakpoint per line. */
    line = rb_sourceline();
    file = RSTRING_PTR(iseq->filename);
    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 (CTX_FL_TEST(debug_context, CTX_FL_CATCHING))
        {
            int hit_count;

            /* 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);
            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->cfp_count == 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->cfp_count < 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:
    {
        breakpoint = check_breakpoints_by_method(debug_context, klass, mid, self);
        if(breakpoint != Qnil)
        {
            if(!check_breakpoint_expression(breakpoint, rb_binding_new()))
                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_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->cfp_count == 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.
             */
        }
        CTX_FL_SET(debug_context, CTX_FL_ENABLE_BKPT);

        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);
    }
}

/*
 *   call-seq:
 *      Debugger.start_ -> bool
 *      Debugger.start_ { ... } -> bool
 *
 *   Deprecated.
 *
 */
static VALUE
debug_start(VALUE self)
{
    hook_off = Qfalse;
    return(Qtrue);
}

/*
 *   call-seq:
 *      Debugger.stop -> bool
 *
 *   Deprecated.
 */
static VALUE
debug_stop(VALUE self)
{
    hook_off = Qtrue;
    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;

    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;

    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;

    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;

    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;

    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;

    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;
}

/* :nodoc: */
static VALUE
debug_debug(VALUE self)
{
    return debug_flag;
}

/* :nodoc: */
static VALUE
debug_set_debug(VALUE self, VALUE value)
{
    debug_flag = RTEST(value) ? Qtrue : Qfalse;
    return value;
}

/* :nodoc: */
static VALUE
debug_catchall(VALUE self)
{
    return catchall;
}

/* :nodoc: */
static VALUE
debug_set_catchall(VALUE self, VALUE value)
{
    catchall = RTEST(value) ? Qtrue : Qfalse;
    return value;
}

/* :nodoc: */
static VALUE
debug_skip_next_exception(VALUE self)
{
    skip_next_exception = Qtrue;
    return(Qtrue);
}

/* :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;
    }

    context = debug_current_context(self);
    Data_Get_Struct(context, debug_context_t, debug_context);
    debug_context->start_cfp = NULL;
    debug_context->top_cfp = GET_THREAD()->cfp;
    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();

    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");
    }
    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)
{
    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;

    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;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if(debug_context->cfp_count == 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->cfp_count;
    }
    else
    {
        if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->cfp_count)
            rb_raise(rb_eRuntimeError, "Destination frame is out of range.");
        debug_context->dest_frame = debug_context->cfp_count - 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;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->cfp_count)
        rb_raise(rb_eRuntimeError, "Stop frame is out of range.");
    debug_context->stop_frame = debug_context->cfp_count - 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->cfp_count)
    rb_raise(rb_eArgError, "Invalid frame number %d, stack (0...%d)",
        frame_n, debug_context->cfp_count - 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_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;
    rb_control_frame_t *cfp;
    rb_thread_t *th;
    VALUE bindval;
    rb_binding_t *bind;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    GetThreadPtr(context_thread_0(debug_context), th);
    cfp = GET_CFP;

    bindval = binding_alloc(rb_cBinding);
    GetBindingPtr(bindval, bind);
    bind->env = rb_vm_make_env_object(th, cfp);
    return bindval;
}

/*
 *   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)
{
    VALUE frame;
    debug_context_t *debug_context;
    rb_control_frame_t *cfp;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    cfp = GET_CFP;
    if (cfp->iseq == NULL || cfp->iseq->defined_method_id == 0) return(Qnil);

#if defined HAVE_RB_CONTROL_FRAME_T_METHOD_ID
    return(RUBYVM_CFUNC_FRAME_P(cfp) ? ID2SYM(cfp->method_id) : ID2SYM(cfp->iseq->defined_method_id));
#elif defined HAVE_RB_METHOD_ENTRY_T_CALLED_ID
    return(RUBYVM_CFUNC_FRAME_P(cfp) ? ID2SYM(cfp->me->called_id) : ID2SYM(cfp->iseq->defined_method_id));
#endif
}

/*
 *   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_control_frame_t *cfp;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    cfp = GET_CFP;
    return(INT2FIX(rb_vm_get_sourceline(cfp)));
}

/*
 *   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;
    rb_control_frame_t *cfp;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    cfp = GET_CFP;
    while (cfp <= debug_context->start_cfp)
    {
        if (cfp->iseq != NULL)
            return(cfp->iseq->filename);
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
    }
    return(Qnil);
}

/*
 *   call-seq:
 *      context.frame_locals(frame) -> hash
 *
 *   Returns frame's local variables.
 */
static VALUE
context_frame_locals(int argc, VALUE *argv, VALUE self)
{
    VALUE frame;
    debug_context_t *debug_context;
    int i;
    rb_control_frame_t *cfp;
    rb_iseq_t *iseq;
    VALUE hash;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    cfp = GET_CFP;
    iseq = cfp->iseq;
    hash = rb_hash_new();

    if (iseq != NULL && 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)
                rb_hash_aset(hash, str, *(cfp->dfp - iseq->local_size + i));
        }
    }

    return(hash);
}

/*
 *   call-seq:
 *      context.frame_args(frame_position=0) -> list
 *
 *   Returns frame's argument parameters
 */
static VALUE
context_frame_args(int argc, VALUE *argv, VALUE self)
{
    VALUE frame;
    debug_context_t *debug_context;
    rb_control_frame_t *cfp;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);

    cfp = GET_CFP;
    if (cfp && cfp->iseq && cfp->iseq->local_table && cfp->iseq->argc)
    {
        int i;
        VALUE list;

        list = rb_ary_new2(cfp->iseq->argc);
        for (i = 0; i < cfp->iseq->argc; i++)
        {
            if (!rb_is_local_id(cfp->iseq->local_table[i])) continue; /* skip flip states */
            rb_ary_push(list, rb_id2str(cfp->iseq->local_table[i]));
        }

        return(list);
    }
    return(rb_ary_new2(0));
}

/*
 *   call-seq:
 *      context.frame_self(frame_postion=0) -> obj
 *
 *   Returns self object of the frame.
 */
static VALUE
context_frame_self(int argc, VALUE *argv, VALUE self)
{
    VALUE frame;
    debug_context_t *debug_context;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);

    return(GET_CFP->self);
}

/*
 *   call-seq:
 *      context.frame_class(frame_position) -> obj
 *
 *   Returns the real class of the frame.
 *   It could be different than context.frame_self(frame).class
 */
static VALUE
context_frame_class(int argc, VALUE *argv, VALUE self)
{
    VALUE klass;
    VALUE frame;
    debug_context_t *debug_context;
    rb_control_frame_t *cfp;

    frame = optional_frame_position(argc, argv);
    Data_Get_Struct(self, debug_context_t, debug_context);
    cfp = GET_CFP;

    if (cfp->iseq == NULL) return(Qnil);
    klass = real_class(cfp->iseq->klass);
    if(TYPE(klass) == T_CLASS || TYPE(klass) == T_MODULE)
        return klass;
    return Qnil;
}


/*
 *   call-seq:
 *      context.stack_size-> int
 *
 *   Returns the size of the context stack.
 */
static VALUE
context_stack_size(VALUE self)
{
    debug_context_t *debug_context;
    Data_Get_Struct(self, debug_context_t, debug_context);
    return(INT2FIX(debug_context->cfp_count));
}

/*
 *   call-seq:
 *      context.stack_inc-> bool
 *
 *   Increments the top of the call stack to the previous valid frame, if possible.
 */
static VALUE
context_stack_inc(VALUE self)
{
    debug_context_t *debug_context;
    rb_control_frame_t *cfp;
    rb_thread_t *th;

    Data_Get_Struct(self, debug_context_t, debug_context);
    GetThreadPtr(context_thread_0(debug_context), th);
    cfp = debug_context->start_cfp;

    while (cfp < RUBY_VM_END_CONTROL_FRAME(th))
    {
        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
        if (cfp->iseq != NULL && cfp->pc != NULL)
        {
            debug_context->start_cfp = cfp;
            if (GET_THREAD() == th)
                set_cfp(debug_context);
            return(Qtrue);
        }
    }
    return(Qfalse);
}

/*
 *   call-seq:
 *      context.stack_dec-> bool
 *
 *   Decrements the top of the call stack to the next valid frame, if possible.
 */
static VALUE
context_stack_dec(VALUE self)
{
    debug_context_t *debug_context;
    rb_control_frame_t *cfp;
    rb_thread_t *th;

    Data_Get_Struct(self, debug_context_t, debug_context);
    GetThreadPtr(context_thread_0(debug_context), th);
    cfp = debug_context->start_cfp;

    while (cfp >= debug_context->cur_cfp)
    {
        cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
        if (cfp->iseq != NULL && cfp->pc != NULL)
        {
            debug_context->start_cfp = cfp;
            if (GET_THREAD() == th)
                set_cfp(debug_context);
            return(Qtrue);
        }
    }
    return(Qfalse);
}

/*
 *   call-seq:
 *      context.thread -> thread
 *
 *   Returns a thread this context is associated with.
 */
static VALUE
context_thread(VALUE self)
{
    debug_context_t *debug_context;
    Data_Get_Struct(self, debug_context_t, debug_context);
    return(id2ref(debug_context->thread_id));
}

/*
 *   call-seq:
 *      context.thnum -> int
 *
 *   Returns the context's number.
 */
static VALUE
context_thnum(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);

    return INT2FIX(debug_context->thnum);
}

static void
context_suspend_0(debug_context_t *debug_context)
{
    VALUE status;

    status = rb_funcall(context_thread_0(debug_context), rb_intern("status"), 0);
    if(rb_str_cmp(status, rb_str_new2("run")) == 0)
      CTX_FL_SET(debug_context, CTX_FL_WAS_RUNNING);
    else if(rb_str_cmp(status, rb_str_new2("sleep")) == 0)
      CTX_FL_UNSET(debug_context, CTX_FL_WAS_RUNNING);
    else
      return;
    CTX_FL_SET(debug_context, CTX_FL_SUSPEND);
}

static void
context_resume_0(debug_context_t *debug_context)
{
    if(!CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))
      return;
    CTX_FL_UNSET(debug_context, CTX_FL_SUSPEND);
    if(CTX_FL_TEST(debug_context, CTX_FL_WAS_RUNNING))
      rb_thread_wakeup(context_thread_0(debug_context));
}

/*
 *   call-seq:
 *      context.suspend -> nil
 *
 *   Suspends the thread when it is running.
 */
static VALUE
context_suspend(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))
        rb_raise(rb_eRuntimeError, "Already suspended.");
    context_suspend_0(debug_context);
    return Qnil;
}

/*
 *   call-seq:
 *      context.suspended? -> bool
 *
 *   Returns +true+ if the thread is suspended by debugger.
 */
static VALUE
context_is_suspended(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    return CTX_FL_TEST(debug_context, CTX_FL_SUSPEND) ? Qtrue : Qfalse;
}

/*
 *   call-seq:
 *      context.resume -> nil
 *
 *   Resumes the thread from the suspended mode.
 */
static VALUE
context_resume(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if(!CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))
        rb_raise(rb_eRuntimeError, "Thread is not suspended.");
    context_resume_0(debug_context);
    return Qnil;
}

/*
 *   call-seq:
 *      context.tracing -> bool
 *
 *   Returns the tracing flag for the current context.
 */
static VALUE
context_tracing(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    return CTX_FL_TEST(debug_context, CTX_FL_TRACING) ? Qtrue : Qfalse;
}

/*
 *   call-seq:
 *      context.tracing = bool
 *
 *   Controls the tracing for this context.
 */
static VALUE
context_set_tracing(VALUE self, VALUE value)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if(RTEST(value))
        CTX_FL_SET(debug_context, CTX_FL_TRACING);
    else
        CTX_FL_UNSET(debug_context, CTX_FL_TRACING);
    return value;
}

/*
 *   call-seq:
 *      context.ignored? -> bool
 *
 *   Returns the ignore flag for the current context.
 */
static VALUE
context_ignored(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    return CTX_FL_TEST(debug_context, CTX_FL_IGNORE) ? Qtrue : Qfalse;
}

/*
 *   call-seq:
 *      context.dead? -> bool
 *
 *   Returns +true+ if context doesn't represent a live context and is created
 *   during post-mortem exception handling.
 */
static VALUE
context_dead(VALUE self)
{
    debug_context_t *debug_context;

    Data_Get_Struct(self, debug_context_t, debug_context);
    return CTX_FL_TEST(debug_context, CTX_FL_DEAD) ? Qtrue : Qfalse;
}

/*
 *   call-seq:
 *      context.stop_reason -> sym
 *
 *   Returns the reason for the stop. It maybe of the following values:
 *   :initial, :step, :breakpoint, :catchpoint, :post-mortem
 */
static VALUE
context_stop_reason(VALUE self)
{
    debug_context_t *debug_context;
    const char * sym_name;

    Data_Get_Struct(self, debug_context_t, debug_context);

    switch(debug_context->stop_reason)
    {
        case CTX_STOP_STEP:
            sym_name = "step";
            break;
        case CTX_STOP_BREAKPOINT:
            sym_name = "breakpoint";
            break;
        case CTX_STOP_CATCHPOINT:
            sym_name = "catchpoint";
            break;
        case CTX_STOP_NONE:
        default:
            sym_name = "none";
    }
    if(CTX_FL_TEST(debug_context, CTX_FL_DEAD))
        sym_name = "post-mortem";

    return ID2SYM(rb_intern(sym_name));
}

/*
 *   call-seq:
 *      context.jump(line, file) -> bool
 *
 *   Returns +true+ if jump to +line+ in filename +file+ was successful.
 */
static VALUE
context_jump(VALUE self, VALUE line, VALUE file)
{
    debug_context_t *debug_context;
    int i;
    rb_thread_t *th;
    rb_control_frame_t *cfp;
    rb_control_frame_t *cfp_end;
    rb_control_frame_t *cfp_start = NULL;

    Data_Get_Struct(self, debug_context_t, debug_context);
    GetThreadPtr(context_thread_0(debug_context), th);
    line = FIX2INT(line);

    cfp_start = debug_context->cur_cfp;
    cfp_end = RUBY_VM_END_CONTROL_FRAME(th);
    cfp = cfp_start;
    if (cfp_start == NULL)
        return(INT2FIX(2)); /* couldn't find frame; should never happen */
    if ((cfp->pc - cfp->iseq->iseq_encoded) >= (cfp->iseq->iseq_size - 1))
        return(INT2FIX(1)); /* no space for opt_call_c_function hijack */

    /* find target frame to jump to */
    while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, cfp_end))
    {
        if ((cfp->iseq != NULL) && (rb_str_cmp(file, cfp->iseq->filename) == 0))
        {
            for (i = 0; i < cfp->iseq->line_info_size; i++)
            {
                if (cfp->iseq->line_info_table[i].line_no != line)
                    continue;

                /* hijack the currently running code so that we can change the frame PC */
                debug_context->saved_jump_ins[0] = cfp_start->pc[0];
                debug_context->saved_jump_ins[1] = cfp_start->pc[1];
                cfp_start->pc[0] = bin_opt_call_c_function;
                cfp_start->pc[1] = (VALUE)do_jump;

                debug_context->jump_cfp = cfp;
                debug_context->jump_pc =
                    cfp->iseq->iseq_encoded + cfp->iseq->line_info_table[i].position;

                return(INT2FIX(0)); /* success */
            }
        }

        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
    }

    return(INT2FIX(3)); /* couldn't find a line and file frame match */
}

/*
 *   call-seq:
 *      context.break -> bool
 *
 *   Returns +true+ if context is currently running and set flag to break it at next line
 */
static VALUE
context_pause(VALUE self)
{
    debug_context_t *debug_context;
    rb_thread_t *th;

    Data_Get_Struct(self, debug_context_t, debug_context);
    if (CTX_FL_TEST(debug_context, CTX_FL_DEAD))
        return(Qfalse);

    GetThreadPtr(context_thread_0(debug_context), th);
    if (th == GET_THREAD())
        return(Qfalse);

    debug_context->thread_pause = 1;
    return(Qtrue);
}


/*
 *   Document-class: Context
 *
 *   == Summary
 *
 *   Debugger keeps a single instance of this class for each Ruby thread.
 */
static void
Init_context(void)
{
    cContext = rb_define_class_under(mDebugger, "Context", rb_cObject);
    rb_define_method(cContext, "stop_next=", context_stop_next, -1);
    rb_define_method(cContext, "step", context_stop_next, -1);
    rb_define_method(cContext, "step_over", context_step_over, -1);
    rb_define_method(cContext, "stop_frame=", context_stop_frame, 1);
    rb_define_method(cContext, "thread", context_thread, 0);
    rb_define_method(cContext, "thnum", context_thnum, 0);
    rb_define_method(cContext, "stop_reason", context_stop_reason, 0);
    rb_define_method(cContext, "suspend", context_suspend, 0);
    rb_define_method(cContext, "suspended?", context_is_suspended, 0);
    rb_define_method(cContext, "resume", context_resume, 0);
    rb_define_method(cContext, "tracing", context_tracing, 0);
    rb_define_method(cContext, "tracing=", context_set_tracing, 1);
    rb_define_method(cContext, "ignored?", context_ignored, 0);
    rb_define_method(cContext, "frame_args", context_frame_args, -1);
    rb_define_method(cContext, "frame_binding", context_frame_binding, -1);
    rb_define_method(cContext, "frame_class", context_frame_class, -1);
    rb_define_method(cContext, "frame_file", context_frame_file, -1);
    rb_define_method(cContext, "frame_id", context_frame_id, -1);
    rb_define_method(cContext, "frame_line", context_frame_line, -1);
    rb_define_method(cContext, "frame_locals", context_frame_locals, -1);
    rb_define_method(cContext, "frame_method", context_frame_id, -1);
    rb_define_method(cContext, "frame_self", context_frame_self, -1);
    rb_define_method(cContext, "stack_size", context_stack_size, 0);
    rb_define_method(cContext, "stack_inc", context_stack_inc, 0);
    rb_define_method(cContext, "stack_dec", context_stack_dec, 0);
    rb_define_method(cContext, "dead?", context_dead, 0);
    rb_define_method(cContext, "breakpoint",
             context_breakpoint, 0);      /* in breakpoint.c */
    rb_define_method(cContext, "set_breakpoint",
             context_set_breakpoint, -1); /* in breakpoint.c */
    rb_define_method(cContext, "jump", context_jump, 2);
    rb_define_method(cContext, "pause", context_pause, 0);
}

/*
 *   call-seq:
 *      Debugger.breakpoints -> array
 *
 *   Returns an array of breakpoints.
 */
static VALUE
debug_breakpoints(VALUE self)
{
    return rdebug_breakpoints;
}

/*
 *   call-seq:
 *      Debugger.add_breakpoint(source, pos, condition = nil) -> breakpoint
 *
 *   Adds a new breakpoint.
 *   <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.
 */
static VALUE
debug_add_breakpoint(int argc, VALUE *argv, VALUE self)
{
    VALUE result;

    result = create_breakpoint_from_args(argc, argv, ++bkp_count);
    rb_ary_push(rdebug_breakpoints, result);
    return result;
}

VALUE translate_insns(VALUE bin)
{
    rb_iseq_t iseq;
    iseq.iseq = &bin;
    iseq.iseq_size = 1;
    iseq.iseq_encoded = NULL;

    rb_iseq_translate_threaded_code(&iseq);
    if (iseq.iseq_encoded != iseq.iseq)
    {
        bin = iseq.iseq_encoded[0];
        xfree(iseq.iseq_encoded);
    }
    return(bin);
}

#define TRANSLATE_INSNS(op) bin_##op = translate_insns(BIN(op))

/*
 *   Document-class: Debugger
 *
 *   == Summary
 *
 *   This is a singleton class allows controlling the debugger. Use it to start/stop debugger,
 *   set/remove breakpoints, etc.
 */
void
Init_ruby_debug(void)
{
    TRANSLATE_INSNS(opt_call_c_function);
    TRANSLATE_INSNS(getdynamic);
    TRANSLATE_INSNS(throw);

    mDebugger = rb_define_module("Debugger");
    rb_define_const(mDebugger, "VERSION", rb_str_new2(DEBUG_VERSION));
    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, "breakpoints", debug_breakpoints, 0);
    rb_define_module_function(mDebugger, "add_breakpoint", debug_add_breakpoint, -1);
    rb_define_module_function(mDebugger, "remove_breakpoint",
                  rdebug_remove_breakpoint,
                  1);                        /* in breakpoint.c */
    rb_define_module_function(mDebugger, "add_catchpoint",
                  rdebug_add_catchpoint, 1); /* in breakpoint.c */
    rb_define_module_function(mDebugger, "catchpoints",
                  debug_catchpoints, 0);     /* in breakpoint.c */
    rb_define_module_function(mDebugger, "last_context", debug_last_interrupted, 0);
    rb_define_module_function(mDebugger, "contexts", debug_contexts, 0);
    rb_define_module_function(mDebugger, "current_context", debug_current_context, 0);
    rb_define_module_function(mDebugger, "thread_context", debug_thread_context, 1);
    rb_define_module_function(mDebugger, "suspend", debug_suspend, 0);
    rb_define_module_function(mDebugger, "resume", debug_resume, 0);
    rb_define_module_function(mDebugger, "tracing", debug_tracing, 0);
    rb_define_module_function(mDebugger, "tracing=", debug_set_tracing, 1);
    rb_define_module_function(mDebugger, "debug_load", debug_debug_load, -1);
    rb_define_module_function(mDebugger, "skip", debug_skip, 0);
    rb_define_module_function(mDebugger, "debug_at_exit", debug_at_exit, 0);
    rb_define_module_function(mDebugger, "debug", debug_debug, 0);
    rb_define_module_function(mDebugger, "debug=", debug_set_debug, 1);
    rb_define_module_function(mDebugger, "catchall", debug_catchall, 0);
    rb_define_module_function(mDebugger, "catchall=", debug_set_catchall, 1);
    rb_define_module_function(mDebugger, "skip_next_exception", debug_skip_next_exception, 0);

    cThreadsTable = rb_define_class_under(mDebugger, "ThreadsTable", rb_cObject);

    cDebugThread  = rb_define_class_under(mDebugger, "DebugThread", rb_cThread);
    rb_define_singleton_method(cDebugThread, "inherited",
                   debug_thread_inherited, 1);

    Init_context();
    Init_breakpoint();

    idAtBreakpoint = rb_intern("at_breakpoint");
    idAtCatchpoint = rb_intern("at_catchpoint");
    idAtLine       = rb_intern("at_line");
    idAtReturn     = rb_intern("at_return");
    idAtTracing    = rb_intern("at_tracing");
    idList         = rb_intern("list");

    rb_mObjectSpace = rb_const_get(rb_mKernel, rb_intern("ObjectSpace"));

    rb_global_variable(&last_context);
    rb_global_variable(&last_thread);
    rb_global_variable(&locker);
    rb_global_variable(&rdebug_breakpoints);
    rb_global_variable(&rdebug_catchpoints);
    rb_global_variable(&rdebug_threads_tbl);

    /* start the debugger hook */
    id_binding_n       = rb_intern("binding_n");
    id_frame_binding   = rb_intern("frame_binding");
    hook_off           = Qfalse;
    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);
}

<MSG> store Thread itself in debug_context_t, not object ID.

<DFF> @@ -144,55 +144,16 @@ real_class(VALUE klass)
     return klass;
 }
 
-inline static void *
-ruby_method_ptr(VALUE class, ID meth_id)
-{
-#ifndef HAVE_RB_METHOD_ENTRY
-    NODE *body, *method;
-    st_lookup(RCLASS_M_TBL(class), meth_id, (st_data_t *)&body);
-    method = (NODE *)body->u2.value;
-    return (void *)method->u2.node->u1.value;
-#else
-    rb_method_entry_t * method;
-    method = rb_method_entry(class, meth_id);
-#ifdef HAVE_ST_BODY
-    return (void *)method->body.cfunc.func;
-#else
-    return (void *)method->def->body.cfunc.func;
-#endif
-#endif
-}
-
 inline static VALUE
 ref2id(VALUE obj)
 {
-    return rb_obj_id(obj);
-}
-
-static VALUE
-id2ref_unprotected(VALUE id)
-{
-    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);
-    static id2ref_func_t f_id2ref = NULL;
-    if(f_id2ref == NULL)
-    {
-        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));
-    }
-    return f_id2ref(rb_mObjectSpace, id);
-}
-
-static VALUE
-id2ref_error()
-{
-    if(debug_flag == Qtrue)
-        rb_p(rb_errinfo());
-    return Qnil;
+    return obj;
 }
 
-static VALUE
+inline static VALUE
 id2ref(VALUE id)
 {
-    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);
+    return id;
 }
 
 inline static VALUE
@@ -310,13 +271,9 @@ threads_table_clear(VALUE table)
 static VALUE
 is_thread_alive(VALUE thread)
 {
-    typedef VALUE (*thread_alive_func_t)(VALUE);
-    static thread_alive_func_t f_thread_alive = NULL;
-    if(!f_thread_alive)
-    {
-        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));
-    }
-    return f_thread_alive(thread);
+    rb_thread_t *th;
+    GetThreadPtr(thread, th);
+    return th->status != THREAD_KILLED;
 }
 
 static int

{'commit': 'diff --git a/ext/ruby_debug/extconf.rb b/ext/ruby_debug/extconf.rb\nindex 8ce56ba..b39b01c 100644\n--- a/ext/ruby_debug/extconf.rb\n+++ b/ext/ruby_debug/extconf.rb\n@@ -11,10 +11,6 @@ hdrs = proc {\n     have_struct_member("rb_method_entry_t", "called_id", "method.h") or\n     have_struct_member("rb_control_frame_t", "method_id", "method.h")\n   end and\n-  begin\n-    have_func("rb_method_entry", "method.h") or\n-    have_func("rb_method_node", "node.h")\n-  end and\n   have_header("vm_core.h") and have_header("iseq.h") and have_header("insns.inc") and\n   have_header("insns_info.inc") and have_header("eval_intern.h") or break\n   if checking_for(checking_message("if rb_iseq_compile_with_option was added an argument filepath")) do\ndiff --git a/ext/ruby_debug/ruby_debug.c b/ext/ruby_debug/ruby_debug.c\nindex 37b6792..df9035a 100644\n--- a/ext/ruby_debug/ruby_debug.c\n+++ b/ext/ruby_debug/ruby_debug.c\n@@ -144,55 +144,16 @@ real_class(VALUE klass)\n     return klass;\n }\n \n-inline static void *\n-ruby_method_ptr(VALUE class, ID meth_id)\n-{\n-#ifndef HAVE_RB_METHOD_ENTRY\n-    NODE *body, *method;\n-    st_lookup(RCLASS_M_TBL(class), meth_id, (st_data_t *)&body);\n-    method = (NODE *)body->u2.value;\n-    return (void *)method->u2.node->u1.value;\n-#else\n-    rb_method_entry_t * method;\n-    method = rb_method_entry(class, meth_id);\n-#ifdef HAVE_ST_BODY\n-    return (void *)method->body.cfunc.func;\n-#else\n-    return (void *)method->def->body.cfunc.func;\n-#endif\n-#endif\n-}\n-\n inline static VALUE\n ref2id(VALUE obj)\n {\n-    return rb_obj_id(obj);\n-}\n-\n-static VALUE\n-id2ref_unprotected(VALUE id)\n-{\n-    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);\n-    static id2ref_func_t f_id2ref = NULL;\n-    if(f_id2ref == NULL)\n-    {\n-        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));\n-    }\n-    return f_id2ref(rb_mObjectSpace, id);\n-}\n-\n-static VALUE\n-id2ref_error()\n-{\n-    if(debug_flag == Qtrue)\n-        rb_p(rb_errinfo());\n-    return Qnil;\n+    return obj;\n }\n \n-static VALUE\n+inline static VALUE\n id2ref(VALUE id)\n {\n-    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);\n+    return id;\n }\n \n inline static VALUE\n@@ -310,13 +271,9 @@ threads_table_clear(VALUE table)\n static VALUE\n is_thread_alive(VALUE thread)\n {\n-    typedef VALUE (*thread_alive_func_t)(VALUE);\n-    static thread_alive_func_t f_thread_alive = NULL;\n-    if(!f_thread_alive)\n-    {\n-        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));\n-    }\n-    return f_thread_alive(thread);\n+    rb_thread_t *th;\n+    GetThreadPtr(thread, th);\n+    return th->status != THREAD_KILLED;\n }\n \n static int\n', 'commit_subject': 'store Thread itself in debug_context_t, not object ID.', 'repo_name': 'mark-moseley/ruby-debug', 'committer': {'name': 'Nobuyoshi Nakada', 'email': '7ff6b48c65ca5c8d3a6019be17b241e7b4763d9e@ruby-lang.org', 'time_sec': '1315977380', 'tz_offset': '540', 'date': {'seconds': '1315977380'}}, 'commit_tree': 'dc97171c897402103874ae229225b44d9095281f', 'commit_message': 'store Thread itself in debug_context_t, not object ID.\n', 'language_name': 'Ruby', 'license': 'bsd-2-clause', 'hunks': '@@ -144,55 +144,16 @@ real_class(VALUE klass)\n     return klass;\n }\n \n-inline static void *\n-ruby_method_ptr(VALUE class, ID meth_id)\n-{\n-#ifndef HAVE_RB_METHOD_ENTRY\n-    NODE *body, *method;\n-    st_lookup(RCLASS_M_TBL(class), meth_id, (st_data_t *)&body);\n-    method = (NODE *)body->u2.value;\n-    return (void *)method->u2.node->u1.value;\n-#else\n-    rb_method_entry_t * method;\n-    method = rb_method_entry(class, meth_id);\n-#ifdef HAVE_ST_BODY\n-    return (void *)method->body.cfunc.func;\n-#else\n-    return (void *)method->def->body.cfunc.func;\n-#endif\n-#endif\n-}\n-\n inline static VALUE\n ref2id(VALUE obj)\n {\n-    return rb_obj_id(obj);\n-}\n-\n-static VALUE\n-id2ref_unprotected(VALUE id)\n-{\n-    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);\n-    static id2ref_func_t f_id2ref = NULL;\n-    if(f_id2ref == NULL)\n-    {\n-        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));\n-    }\n-    return f_id2ref(rb_mObjectSpace, id);\n-}\n-\n-static VALUE\n-id2ref_error()\n-{\n-    if(debug_flag == Qtrue)\n-        rb_p(rb_errinfo());\n-    return Qnil;\n+    return obj;\n }\n \n-static VALUE\n+inline static VALUE\n id2ref(VALUE id)\n {\n-    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);\n+    return id;\n }\n \n inline static VALUE\n@@ -310,13 +271,9 @@ threads_table_clear(VALUE table)\n static VALUE\n is_thread_alive(VALUE thread)\n {\n-    typedef VALUE (*thread_alive_func_t)(VALUE);\n-    static thread_alive_func_t f_thread_alive = NULL;\n-    if(!f_thread_alive)\n-    {\n-        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));\n-    }\n-    return f_thread_alive(thread);\n+    rb_thread_t *th;\n+    GetThreadPtr(thread, th);\n+    return th->status != THREAD_KILLED;\n }\n \n static int\n', 'addition_count': 6, 'deletion_count': 49, 'src_file': 'a/ext/ruby_debug/ruby_debug.c', 'tgt_file': 'b/ext/ruby_debug/ruby_debug.c', 'file_extension': '.c', 'before_file': '#include <ruby.h>\n#include <stdio.h>\n#include <ctype.h>\n#include <vm_core.h>\n#include <iseq.h>\n#include <version.h>\n#include <eval_intern.h>\n#include <gc.h>\n#include <insns.inc>\n#include <insns_info.inc>\n#include "ruby_debug.h"\n\n#define DEBUG_VERSION "0.12"\n\n#define GET_CFP     debug_context->cfp[check_frame_number(debug_context, frame)]\n#define ZFREE(ptr)  do { if(ptr != NULL) { free(ptr); ptr = NULL; } } while(0)\n\n#ifndef min\n#define min(x,y) ((x) < (y) ? (x) : (y))\n#endif\n\nRUBY_EXTERN int rb_vm_get_sourceline(const rb_control_frame_t *cfp); /* from vm.c */\n\n/* from iseq.c */\n#ifdef RB_ISEQ_COMPILE_5ARGS\nRUBY_EXTERN VALUE rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE filepath, VALUE line, VALUE opt);\n#else\nRUBY_EXTERN VALUE rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE line, VALUE opt);\n#endif\n\ntypedef struct {\n    st_table *tbl;\n} threads_table_t;\n\nstatic VALUE hook_off           = Qtrue;\nstatic VALUE tracing            = Qfalse;\nstatic VALUE locker             = Qnil;\nstatic VALUE debug_flag         = Qfalse;\nstatic VALUE catchall           = Qtrue;\nstatic VALUE skip_next_exception= Qfalse;\n\nstatic VALUE last_context = Qnil;\nstatic VALUE last_thread  = Qnil;\nstatic debug_context_t *last_debug_context = NULL;\n\nVALUE rdebug_threads_tbl = Qnil; /* Context for each of the threads */\nVALUE mDebugger;                 /* Ruby Debugger Module object */\n\nstatic VALUE bin_opt_call_c_function;\nstatic VALUE bin_getdynamic;\nstatic VALUE bin_throw;\nstatic VALUE cThreadsTable;\nstatic VALUE cContext;\nstatic VALUE cDebugThread;\n\nstatic VALUE rb_mObjectSpace;\n\nstatic ID idAtBreakpoint;\nstatic ID idAtCatchpoint;\nstatic ID idAtLine;\nstatic ID idAtReturn;\nstatic ID idAtTracing;\nstatic ID idList;\nstatic ID id_binding_n;\nstatic ID id_frame_binding;\n\nstatic int thnum_max = 0;\nstatic int bkp_count = 0;\nstatic int last_debugged_thnum = -1;\nstatic unsigned long last_check = 0;\nstatic unsigned long hook_count = 0;\n\nstatic VALUE debug_stop(VALUE);\nstatic void save_current_position(debug_context_t *);\nstatic void context_suspend_0(debug_context_t *);\nstatic void context_resume_0(debug_context_t *);\nstatic void thread_context_lookup(VALUE, VALUE *, debug_context_t **, int);\nstatic VALUE debug_current_context(VALUE self);\nstatic int find_prev_line_start(rb_control_frame_t *cfp);\n\n\ntypedef struct locked_thread_t {\n    VALUE thread_id;\n    struct locked_thread_t *next;\n} locked_thread_t;\n\nstatic locked_thread_t *locked_head = NULL;\nstatic locked_thread_t *locked_tail = NULL;\n\n/* "Step", "Next" and "Finish" do their work by saving information\n   about where to stop next. reset_stopping_points removes/resets this\n   information. */\ninline static const char *\nget_event_name(rb_event_flag_t event)\n{\n  switch (event) {\n    case RUBY_EVENT_LINE:\n      return "line";\n    case RUBY_EVENT_CLASS:\n      return "class";\n    case RUBY_EVENT_END:\n      return "end";\n    case RUBY_EVENT_CALL:\n      return "call";\n    case RUBY_EVENT_RETURN:\n      return "return";\n    case RUBY_EVENT_C_CALL:\n      return "c-call";\n    case RUBY_EVENT_C_RETURN:\n      return "c-return";\n    case RUBY_EVENT_RAISE:\n      return "raise";\n    default:\n      return "unknown";\n  }\n}\n\nstatic void\ndebug_runtime_error(debug_context_t *debug_context, const char *err)\n{\n    if (debug_context != NULL) CTX_FL_SET(debug_context, CTX_FL_IGNORE);\n    rb_raise(rb_eRuntimeError, err);\n}\n\ninline static void\nreset_stepping_stop_points(debug_context_t *debug_context)\n{\n    debug_context->dest_frame = -1;\n    debug_context->stop_line  = -1;\n    debug_context->stop_next  = -1;\n}\n\ninline static VALUE\nreal_class(VALUE klass)\n{\n    if (klass) {\n        if (TYPE(klass) == T_ICLASS) {\n            return RBASIC(klass)->klass;\n        }\n        else if (FL_TEST(klass, FL_SINGLETON)) {\n            return rb_iv_get(klass, "__attached__");\n        }\n    }\n    return klass;\n}\n\ninline static void *\nruby_method_ptr(VALUE class, ID meth_id)\n{\n#ifndef HAVE_RB_METHOD_ENTRY\n    NODE *body, *method;\n    st_lookup(RCLASS_M_TBL(class), meth_id, (st_data_t *)&body);\n    method = (NODE *)body->u2.value;\n    return (void *)method->u2.node->u1.value;\n#else\n    rb_method_entry_t * method;\n    method = rb_method_entry(class, meth_id);\n#ifdef HAVE_ST_BODY\n    return (void *)method->body.cfunc.func;\n#else\n    return (void *)method->def->body.cfunc.func;\n#endif\n#endif\n}\n\ninline static VALUE\nref2id(VALUE obj)\n{\n    return rb_obj_id(obj);\n}\n\nstatic VALUE\nid2ref_unprotected(VALUE id)\n{\n    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);\n    static id2ref_func_t f_id2ref = NULL;\n    if(f_id2ref == NULL)\n    {\n        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));\n    }\n    return f_id2ref(rb_mObjectSpace, id);\n}\n\nstatic VALUE\nid2ref_error()\n{\n    if(debug_flag == Qtrue)\n        rb_p(rb_errinfo());\n    return Qnil;\n}\n\nstatic VALUE\nid2ref(VALUE id)\n{\n    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);\n}\n\ninline static VALUE\ncontext_thread_0(debug_context_t *debug_context)\n{\n    return id2ref(debug_context->thread_id);\n}\n\nstatic inline const rb_data_type_t *\nthreadptr_data_type(void)\n{\n    static const rb_data_type_t *thread_data_type;\n    if (!thread_data_type) {\n\tVALUE current_thread = rb_thread_current();\n\tthread_data_type = RTYPEDDATA_TYPE(current_thread);\n    }\n    return thread_data_type;\n}\n\n#define ruby_threadptr_data_type *threadptr_data_type()\n\n#define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))\n\nstatic int\nis_in_locked(VALUE thread_id)\n{\n    locked_thread_t *node;\n\n    if(!locked_head)\n        return 0;\n\n    for(node = locked_head; node != locked_tail; node = node->next)\n    {\n        if(node->thread_id == thread_id) return 1;\n    }\n    return 0;\n}\n\nstatic void\nadd_to_locked(VALUE thread)\n{\n    locked_thread_t *node;\n    VALUE thread_id = ref2id(thread);\n\n    if(is_in_locked(thread_id))\n        return;\n\n    node = ALLOC(locked_thread_t);\n    node->thread_id = thread_id;\n    node->next = NULL;\n    if(locked_tail)\n        locked_tail->next = node;\n    locked_tail = node;\n    if(!locked_head)\n        locked_head = node;\n}\n\nstatic VALUE\nremove_from_locked(void)\n{\n    VALUE thread;\n    locked_thread_t *node;\n\n    if(locked_head == NULL)\n        return Qnil;\n    node = locked_head;\n    locked_head = locked_head->next;\n    if(locked_tail == node)\n        locked_tail = NULL;\n    thread = id2ref(node->thread_id);\n    xfree(node);\n    return thread;\n}\n\nstatic int is_living_thread(VALUE thread);\n\nstatic int\nthreads_table_mark_keyvalue(st_data_t key, st_data_t value, st_data_t tbl)\n{\n    VALUE thread = id2ref((VALUE)key);\n    if (!value) {\n        return ST_CONTINUE;\n    }\n    rb_gc_mark((VALUE)value);\n    if (is_living_thread(thread)) {\n\trb_gc_mark(thread);\n    }\n    else {\n\tst_insert((st_table *)tbl, key, 0);\n    }\n    return ST_CONTINUE;\n}\n\nstatic void\nthreads_table_mark(void* data)\n{\n    threads_table_t *threads_table = (threads_table_t*)data;\n    st_table *tbl = threads_table->tbl;\n    st_foreach(tbl, threads_table_mark_keyvalue, (st_data_t)tbl);\n}\n\nstatic void\nthreads_table_free(void* data)\n{\n    threads_table_t *threads_table = (threads_table_t*)data;\n    st_free_table(threads_table->tbl);\n    xfree(threads_table);\n}\n\nstatic VALUE\nthreads_table_create(void)\n{\n    threads_table_t *threads_table;\n\nstatic VALUE\nis_thread_alive(VALUE thread)\n{\n    typedef VALUE (*thread_alive_func_t)(VALUE);\n    static thread_alive_func_t f_thread_alive = NULL;\n    if(!f_thread_alive)\n    {\n        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));\n    }\n    return f_thread_alive(thread);\n}\n\nstatic int\n\n    Data_Get_Struct(table, threads_table_t, threads_table);\n    st_clear(threads_table->tbl);\n}\n\nstatic int\nis_thread_alive(VALUE thread)\n{\n    rb_thread_t *th;\n    GetThreadPtr(thread, th);\n    return th->status != THREAD_KILLED;\n}\n\nstatic int\nis_living_thread(VALUE thread)\n{\n    return rb_obj_is_kind_of(thread, rb_cThread) && is_thread_alive(thread);\n}\n\nstatic int\nthreads_table_check_i(st_data_t key, st_data_t value, st_data_t dummy)\n{\n    VALUE thread;\n\n    if(!value)\n    {\n        return ST_DELETE;\n    }\n    thread = id2ref((VALUE)key);\n    if(!is_living_thread(thread))\n    {\n        return ST_DELETE;\n    }\n    return ST_CONTINUE;\n}\n\nstatic void\ncheck_thread_contexts(void)\n{\n    threads_table_t *threads_table;\n\n    Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);\n    st_foreach(threads_table->tbl, threads_table_check_i, 0);\n}\n\nstatic struct iseq_catch_table_entry *\ncreate_catch_table(debug_context_t *debug_context, unsigned long cont)\n{\n    struct iseq_catch_table_entry *catch_table = &debug_context->catch_table.tmp_catch_table;\n\n    GET_THREAD()->parse_in_eval++;\n    GET_THREAD()->mild_compile_error++;\n    /* compiling with option Qfalse (no options) prevents debug hook calls during this catch routine\n     */\n#ifdef RB_ISEQ_COMPILE_5ARGS\n    catch_table->iseq = rb_iseq_compile_with_option(\n        rb_str_new_cstr(""), rb_str_new_cstr("(exception catcher)"), Qnil, INT2FIX(1), Qfalse);\n#else\n    catch_table->iseq = rb_iseq_compile_with_option(\n        rb_str_new_cstr(""), rb_str_new_cstr("(exception catcher)"), INT2FIX(1), Qfalse);\n#endif\n    GET_THREAD()->mild_compile_error--;\n    GET_THREAD()->parse_in_eval--;\n\n    catch_table->type = CATCH_TYPE_RESCUE;\n    catch_table->start = 0;\n    catch_table->end = ULONG_MAX;\n    catch_table->cont = cont;\n    catch_table->sp = 0;\n\n    return(catch_table);\n}\n\nstatic rb_control_frame_t *\nFUNC_FASTCALL(do_jump)(rb_thread_t *th, rb_control_frame_t *cfp)\n{\n    VALUE context;\n    debug_context_t *debug_context;\n    rb_control_frame_t *jump_cfp;\n    VALUE *jump_pc;\n\n    thread_context_lookup(th->self, &context, &debug_context, 0);\n    if (debug_context == NULL)\n        debug_runtime_error(NULL, "Lost context in jump");\n    cfp->pc[-2] = debug_context->saved_jump_ins[0];\n    cfp->pc[-1] = debug_context->saved_jump_ins[1];\n\n    if ((debug_context->jump_pc < debug_context->jump_cfp->iseq->iseq_encoded) ||\n        (debug_context->jump_pc >= debug_context->jump_cfp->iseq->iseq_encoded + debug_context->jump_cfp->iseq->iseq_size))\n        debug_runtime_error(debug_context, "Invalid jump PC target");\n\n    jump_cfp = debug_context->jump_cfp;\n    jump_pc = debug_context->jump_pc;\n    debug_context->jump_pc = NULL;\n    debug_context->jump_cfp = NULL;\n    if (!CTX_FL_TEST(debug_context, CTX_FL_EXCEPTION_TEST))\n    {\n        debug_context->last_line = 0;\n        debug_context->last_file = NULL;\n        debug_context->stop_next = 1;\n    }\n\n    if (cfp < jump_cfp)\n    {\n        /* save all intermediate-frame catch tables\n           +1 for target frame\n           +1 for array terminator\n         */\n        int frames = jump_cfp - cfp + 2;\n        debug_context->old_iseq_catch = (iseq_catch_t*)malloc(frames * sizeof(iseq_catch_t));\n        MEMZERO(debug_context->old_iseq_catch, iseq_catch_t, frames);\n        frames = 0;\n        do\n        {\n            if (cfp->iseq != NULL)\n            {\n                debug_context->old_iseq_catch[frames].iseq = cfp->iseq;\n                debug_context->old_iseq_catch[frames].catch_table = cfp->iseq->catch_table;\n                debug_context->old_iseq_catch[frames].catch_table_size = cfp->iseq->catch_table_size;\n                cfp->iseq->catch_table = NULL;\n                cfp->iseq->catch_table_size = 0;\n\n                frames++;\n            }\n            cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n        } while (cfp <= jump_cfp);\n\n        jump_cfp->iseq->catch_table_size = 1;\n        jump_cfp->iseq->catch_table =\n            create_catch_table(debug_context, jump_pc - jump_cfp->iseq->iseq_encoded);\n        jump_cfp->iseq->catch_table->sp = -1;\n\n        JUMP_TAG(TAG_RAISE);\n    }\n    else if (cfp > jump_cfp)\n        debug_runtime_error(debug_context, "Invalid jump frame target");\n\n    cfp->pc = jump_pc;\n    return(cfp);\n}\n\nstatic rb_control_frame_t *\nFUNC_FASTCALL(do_catchall)(rb_thread_t *th, rb_control_frame_t *cfp)\n{\n    int size;\n    VALUE context;\n    debug_context_t *debug_context;\n\n    thread_context_lookup(th->self, &context, &debug_context, 0);\n    if (debug_context == NULL)\n        debug_runtime_error(NULL, "Lost context in catchall");\n    if (debug_context->saved_frames == NULL)\n        return(cfp); /* re-throw exception */\n\n    if (CTX_FL_TEST(debug_context, CTX_FL_RETHROW))\n    {\n        cfp->pc[-2] = debug_context->saved_jump_ins[0];\n        cfp->pc[-1] = debug_context->saved_jump_ins[1];\n    }\n    else\n    {\n        CTX_FL_SET(debug_context, CTX_FL_CATCHING);\n        CTX_FL_UNSET(debug_context, CTX_FL_EXCEPTION_TEST);\n    }\n\n    /* restore the call frame state */\n    ZFREE(debug_context->cfp);\n    debug_context->cfp_count = debug_context->saved_cfp_count;\n    debug_context->saved_cfp_count = 0;\n    size = sizeof(rb_control_frame_t*) * debug_context->cfp_count;\n    debug_context->cfp = (rb_control_frame_t**)malloc(size);\n    memcpy(debug_context->cfp, debug_context->saved_cfp, size);\n    ZFREE(debug_context->saved_cfp);\n\n    size = sizeof(rb_control_frame_t) *\n        ((debug_context->cfp[debug_context->cfp_count-1] - debug_context->cfp[0]) + 1);\n    memcpy(debug_context->cfp[0], debug_context->saved_frames, size);\n\n    ZFREE(debug_context->saved_frames);\n    th->cfp = debug_context->cfp[0];\n    th->cfp->pc = th->cfp->iseq->iseq_encoded + find_prev_line_start(th->cfp);\n    return(th->cfp);\n}\n\nstatic void\ncreate_exception_catchall(debug_context_t *debug_context)\n{\n    rb_iseq_t *iseq;\n    struct iseq_catch_table_entry *entry;\n    rb_control_frame_t *cfp = GET_THREAD()->cfp;\n    while (cfp->iseq == NULL)\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n\n    debug_context->start_cfp = cfp;\n    if (catchall == Qfalse)\n        return;\n\n    iseq = cfp->iseq;\n    iseq->catch_table_size++;\n    entry = ALLOC_N(struct iseq_catch_table_entry, iseq->catch_table_size);\n\n    if (iseq->catch_table_size > 1)\n    {\n        MEMCPY(entry, iseq->catch_table, struct iseq_catch_table_entry, iseq->catch_table_size-1);\n        xfree(iseq->catch_table);\n        iseq->catch_table = entry;\n        entry += iseq->catch_table_size-1;\n    }\n    else\n        iseq->catch_table = entry;\n\n    memset(&debug_context->catch_iseq, 0, sizeof(struct rb_iseq_struct));\n    memset(&debug_context->catch_cref_stack, 0, sizeof(struct RNode));\n    debug_context->catch_rdata.basic.flags = RUBY_T_DATA;\n    debug_context->catch_rdata.basic.klass = 0;\n    debug_context->catch_rdata.dmark = NULL;\n    debug_context->catch_rdata.dfree = NULL;\n    debug_context->catch_rdata.data = &debug_context->catch_iseq;\n    debug_context->catch_iseq.type = ISEQ_TYPE_RESCUE;\n    debug_context->catch_iseq.name = rb_str_new_cstr("(exception catcher)");\n    debug_context->catch_iseq.filename = rb_str_new_cstr("(exception catcher)");\n    debug_context->catch_iseq.iseq = debug_context->iseq_insn;\n    debug_context->catch_iseq.iseq_encoded = NULL;\n    debug_context->catch_iseq.iseq[0] = BIN(opt_call_c_function);\n    debug_context->catch_iseq.iseq[1] = (VALUE)do_catchall;\n    debug_context->catch_iseq.iseq[2] = BIN(getdynamic);\n    debug_context->catch_iseq.iseq[3] = 1; /* #$!, only value in local_table */\n    debug_context->catch_iseq.iseq[4] = 0;\n    debug_context->catch_iseq.iseq[5] = BIN(throw);\n    debug_context->catch_iseq.iseq[6] = 0;\n    debug_context->catch_iseq.iseq_size = sizeof(debug_context->iseq_insn) / sizeof(VALUE);\n    debug_context->catch_iseq.mark_ary = rb_ary_new();\n    debug_context->catch_iseq.line_info_table = &debug_context->catch_info_entry;\n    debug_context->catch_iseq.line_info_size = 1;\n    debug_context->catch_iseq.local_size = 1;\n    debug_context->catch_iseq.local_table = &debug_context->local_table;\n    debug_context->catch_iseq.local_table[0] = rb_intern("#$!");\n    debug_context->catch_iseq.arg_simple = 1;\n    debug_context->catch_iseq.arg_rest = -1;\n    debug_context->catch_iseq.arg_block = -1;\n    debug_context->catch_iseq.stack_max = 1;\n    debug_context->catch_iseq.local_iseq = &debug_context->catch_iseq;\n    debug_context->catch_iseq.self = (VALUE)&debug_context->catch_rdata;\n    debug_context->catch_iseq.cref_stack = &debug_context->catch_cref_stack;\n    debug_context->catch_info_entry.position = 0;\n    debug_context->catch_info_entry.line_no = 1;\n#if defined HAVE_TYPE_STRUCT_ISEQ_INSN_INFO_ENTRY\n    debug_context->catch_info_entry.sp = 0;\n#endif\n    debug_context->catch_cref_stack.flags = 1052;\n\n    entry->type = CATCH_TYPE_RESCUE;\n    entry->iseq = debug_context->catch_iseq.self;\n    entry->start = 0;\n    entry->end = ULONG_MAX;\n    entry->cont = 0;\n    entry->sp = 0;\n\n    rb_iseq_translate_threaded_code(&debug_context->catch_iseq);\n}\n\n/*\n *   call-seq:\n *      Debugger.started? -> bool\n *\n *   Returns +true+ the debugger is started.\n *   Deprecated.\n */\nstatic VALUE\ndebug_is_started(VALUE self)\n{\n    return Qtrue;\n}\n\nstatic void\ndebug_context_mark(void *data)\n{\n    debug_context_t *debug_context = (debug_context_t *)data;\n    rb_gc_mark(debug_context->breakpoint);\n}\n\nstatic void\ndebug_context_free(void *data)\n{\n    debug_context_t *debug_context = (debug_context_t *)data;\n}\n\nstatic VALUE\ndebug_context_create(VALUE thread)\n{\n    debug_context_t *debug_context;\n\n    debug_context = ALLOC(debug_context_t);\n    debug_context-> thnum = ++thnum_max;\n\n    debug_context->last_file = NULL;\n    debug_context->last_line = 0;\n    debug_context->flags = 0;\n\n    debug_context->stop_next = -1;\n    debug_context->dest_frame = -1;\n    debug_context->stop_line = -1;\n    debug_context->stop_frame = -1;\n    debug_context->stop_reason = CTX_STOP_NONE;\n    debug_context->thread_id = ref2id(thread);\n    debug_context->breakpoint = Qnil;\n    debug_context->jump_pc = NULL;\n    debug_context->jump_cfp = NULL;\n    debug_context->old_iseq_catch = NULL;\n    debug_context->last_exception = Qnil;\n    debug_context->thread_pause = 0;\n    debug_context->cfp_count = 0;\n    debug_context->start_cfp = NULL;\n    debug_context->cur_cfp = NULL;\n    debug_context->top_cfp = NULL;\n    debug_context->catch_cfp = NULL;\n    debug_context->saved_frames = NULL;\n    debug_context->saved_cfp = NULL;\n    debug_context->saved_cfp_count = 0;\n    debug_context->cfp = NULL;\n    if(rb_obj_class(thread) == cDebugThread)\n        CTX_FL_SET(debug_context, CTX_FL_IGNORE);\n    return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, debug_context);\n}\n\nstatic void\nthread_context_lookup(VALUE thread, VALUE *context, debug_context_t **debug_context, int create)\n{\n    threads_table_t *threads_table;\n    VALUE thread_id;\n    debug_context_t *l_debug_context;\n\n    if(last_thread == thread && last_context != Qnil)\n    {\n        *context = last_context;\n        if(debug_context)\n            *debug_context = last_debug_context;\n        return;\n    }\n    thread_id = ref2id(thread);\n    Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);\n    if(!st_lookup(threads_table->tbl, thread_id, context) || !*context)\n    {\n        if (create)\n        {\n            *context = debug_context_create(thread);\n            st_insert(threads_table->tbl, thread_id, *context);\n        }\n        else\n        {\n            *context = 0;\n            if (debug_context)\n                *debug_context = NULL;\n            return;\n        }\n    }\n\n    Data_Get_Struct(*context, debug_context_t, l_debug_context);\n    if (debug_context)\n        *debug_context = l_debug_context;\n\n    last_thread = thread;\n    last_context = *context;\n    last_debug_context = l_debug_context;\n}\n\nstatic VALUE\ncall_at_line_unprotected(VALUE args)\n{\n    VALUE context;\n    context = *RARRAY_PTR(args);\n    return rb_funcall2(context, idAtLine, RARRAY_LEN(args) - 1, RARRAY_PTR(args) + 1);\n}\n\nstatic VALUE\ncall_at_line(VALUE context, debug_context_t *debug_context, VALUE file, VALUE line)\n{\n    VALUE args;\n\n    last_debugged_thnum = debug_context->thnum;\n    save_current_position(debug_context);\n\n    args = rb_ary_new3(3, context, file, line);\n    return(rb_protect(call_at_line_unprotected, args, 0));\n}\n\n#if defined DOSISH\n#define isdirsep(x) ((x) == \'/\' || (x) == \'\\\\\')\n#else\n#define isdirsep(x) ((x) == \'/\')\n#endif\n\nint\nfilename_cmp(VALUE source, const char *file)\n{\n    const char *source_ptr, *file_ptr;\n    int s_len, f_len, min_len;\n    int s,f;\n    int dirsep_flag = 0;\n\n    s_len = RSTRING_LEN(source);\n    f_len = strlen(file);\n    min_len = min(s_len, f_len);\n\n    source_ptr = RSTRING_PTR(source);\n    file_ptr   = file;\n\n    for( s = s_len - 1, f = f_len - 1; s >= s_len - min_len && f >= f_len - min_len; s--, f-- )\n    {\n        if((source_ptr[s] == \'.\' || file_ptr[f] == \'.\') && dirsep_flag)\n            return 1;\n        if(isdirsep(source_ptr[s]) && isdirsep(file_ptr[f]))\n            dirsep_flag = 1;\n#ifdef DOSISH_DRIVE_LETTER\n        else if (s == 0)\n            return(toupper(source_ptr[s]) == toupper(file_ptr[f]));\n#endif\n        else if(source_ptr[s] != file_ptr[f])\n            return 0;\n    }\n    return 1;\n}\n\nstatic VALUE\nbinding_alloc(VALUE klass)\n{\n    VALUE obj;\n    rb_binding_t *bind;\n    static const rb_data_type_t *binding_data_type;\n    if (!binding_data_type) {\n        VALUE b = rb_binding_new();\n        binding_data_type = RTYPEDDATA_TYPE(b);\n    }\n    obj = TypedData_Make_Struct(klass, rb_binding_t, binding_data_type, bind);\n    return obj;\n}\n\nstatic void\nsave_current_position(debug_context_t *debug_context)\n{\n    CTX_FL_UNSET(debug_context, CTX_FL_ENABLE_BKPT);\n    CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);\n    CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);\n}\n\ninline static int\nc_call_new_frame_p(VALUE klass, ID mid)\n{\n    klass = real_class(klass);\n    if ((klass == rb_mKernel) && (strcmp(rb_id2name(mid), "raise") == 0)) return 0;\n    if(rb_block_given_p()) return 1;\n    if(klass == rb_cProc || klass == rb_mKernel || klass == rb_cModule) return 1;\n    return 0;\n}\n\nstatic void\ncall_at_line_check(VALUE self, debug_context_t *debug_context, VALUE breakpoint, VALUE context, const char *file, int line)\n{\n    debug_context->stop_reason = CTX_STOP_STEP;\n\n    /* check breakpoint expression */\n    if(breakpoint != Qnil)\n    {\n        if(!check_breakpoint_expression(breakpoint, rb_binding_new()))\n            return;\n        if(!check_breakpoint_hit_condition(breakpoint))\n            return;\n        if(breakpoint != debug_context->breakpoint)\n        {\n            debug_context->stop_reason = CTX_STOP_BREAKPOINT;\n            rb_funcall(context, idAtBreakpoint, 1, breakpoint);\n        }\n        else\n            debug_context->breakpoint = Qnil;\n    }\n\n    reset_stepping_stop_points(debug_context);\n    call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));\n}\n\nstatic int\nset_thread_event_flag_i(st_data_t key, st_data_t val, st_data_t flag)\n{\n    VALUE thval = (VALUE)key;\n    rb_thread_t *th;\n    GetThreadPtr(thval, th);\n    th->event_flags |= RUBY_EVENT_VM;\n\n    return(ST_CONTINUE);\n}\n\nstatic int\nfind_prev_line_start(rb_control_frame_t *cfp)\n{\n#if defined HAVE_TYPE_STRUCT_ISEQ_LINE_INFO_ENTRY\n    const rb_iseq_t *iseq = cfp->iseq;\n    return rb_iseq_line_no(iseq, cfp->pc - iseq->iseq_encoded - 1);\n#else\n    int i, pos, line_no;\n    pos = cfp->pc - cfp->iseq->iseq_encoded;\n    for (i = 0; i < cfp->iseq->insn_info_size; i++)\n    {\n        if (cfp->iseq->insn_info_table[i].position != pos)\n            continue;\n\n        if (i == 0) return(0); // TODO\n\n        pos = -1;\n        line_no = cfp->iseq->insn_info_table[--i].line_no;\n        do {\n            if (cfp->iseq->insn_info_table[i].line_no == line_no)\n                pos = cfp->iseq->insn_info_table[i].position;\n            else\n                break;\n            i--;\n        } while (i >= 0);\n\n        return(pos);\n    }\n    return(-1);\n#endif\n}\n\nstatic rb_control_frame_t *\nFUNC_FASTCALL(do_catch)(rb_thread_t *th, rb_control_frame_t *cfp)\n{\n    VALUE context;\n    debug_context_t *debug_context;\n\n    thread_context_lookup(th->self, &context, &debug_context, 0);\n    if (debug_context == NULL)\n        debug_runtime_error(NULL, "Lost context in catch");\n    cfp->pc[-2] = debug_context->saved_jump_ins[0];\n    cfp->pc[-1] = debug_context->saved_jump_ins[1];\n\n    cfp->pc = debug_context->jump_pc;\n\n    debug_context->jump_pc = NULL;\n    debug_context->jump_cfp = NULL;\n\n    /* restore the proper catch table */\n    cfp->iseq->catch_table_size = debug_context->catch_table.old_catch_table_size;\n    cfp->iseq->catch_table = debug_context->catch_table.old_catch_table;\n    CTX_FL_SET(debug_context, CTX_FL_CATCHING);\n    th->cfp->sp--;\n\n    return(cfp);\n}\n\nstatic int\ncatch_exception(debug_context_t *debug_context, VALUE mod_name)\n{\n    int prev_line_start;\n    rb_control_frame_t *cfp = GET_THREAD()->cfp;\n    while (cfp->pc == 0 || cfp->iseq == 0)\n\tcfp++;\n\n    prev_line_start = find_prev_line_start(cfp);\n    if (prev_line_start < 0)\n        return(0);\n\n    /* save the current catch table */\n    debug_context->catch_table.old_catch_table_size = cfp->iseq->catch_table_size;\n    debug_context->catch_table.old_catch_table = cfp->iseq->catch_table;\n    debug_context->catch_table.mod_name = mod_name;\n    debug_context->catch_table.errinfo = rb_errinfo();\n\n    /* create a new catch table to catch this exception, and put it in the current iseq */\n    cfp->iseq->catch_table_size = 1;\n    cfp->iseq->catch_table = create_catch_table(debug_context, 0);\n\n    /* hijack the currently running code so that we can change the frame PC */\n    debug_context->saved_jump_ins[0] = cfp->iseq->iseq_encoded[0];\n    debug_context->saved_jump_ins[1] = cfp->iseq->iseq_encoded[1];\n    cfp->iseq->iseq_encoded[0] = bin_opt_call_c_function;\n    cfp->iseq->iseq_encoded[1] = (VALUE)do_catch;\n    debug_context->jump_pc = cfp->iseq->iseq_encoded + prev_line_start;\n    debug_context->jump_cfp = NULL;\n    return(1);\n}\n\nstatic void\nset_cfp(debug_context_t *debug_context)\n{\n    int cfp_count = 0;\n    int has_changed = 0;\n    rb_control_frame_t *cfp = debug_context->cur_cfp;\n    while (cfp <= debug_context->start_cfp)\n    {\n        if (cfp->iseq != NULL)\n        {\n            if (cfp->pc != NULL)\n            {\n                if ((cfp_count < debug_context->cfp_count) && (debug_context->cfp[cfp_count] != cfp))\n                    has_changed = 1;\n                cfp_count++;\n            }\n        }\n        else if (RUBYVM_CFUNC_FRAME_P(cfp))\n        {\n            if ((cfp_count < debug_context->cfp_count) && (debug_context->cfp[cfp_count] != cfp))\n                has_changed = 1;\n            cfp_count++;\n        }\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n    }\n\n    //if (cfp_count == debug_context->cfp_count && !has_changed)\n      //  return;\n\n    ZFREE(debug_context->cfp);\n    debug_context->cfp = (rb_control_frame_t**)malloc(sizeof(rb_control_frame_t) * cfp_count);\n    debug_context->cfp_count = 0;\n\n    cfp = debug_context->cur_cfp;\n    while (cfp <= debug_context->start_cfp)\n    {\n        if (cfp->iseq != NULL)\n            if (cfp->pc != NULL) debug_context->cfp[debug_context->cfp_count++] = cfp;\n        else if (RUBYVM_CFUNC_FRAME_P(cfp))\n            debug_context->cfp[debug_context->cfp_count++] = cfp;\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n    }\n}\n\nstatic void\nsave_frames(debug_context_t *debug_context)\n{\n    int size;\n    VALUE thread;\n    rb_thread_t *th;\n\n    if (debug_context->cfp_count == 0)\n        return;\n\n    /* save the entire call frame state */\n    thread = rb_thread_current();\n    GetThreadPtr(thread, th);\n    for (size = 0; size < debug_context->cfp_count; size++)\n    {\n        rb_binding_t *bind;\n        VALUE bindval = binding_alloc(rb_cBinding);\n        GetBindingPtr(bindval, bind);\n        bind->env = rb_vm_make_env_object(th, debug_context->cfp[size]);\n    }\n\n    debug_context->catch_table.mod_name = rb_obj_class(rb_errinfo());\n    debug_context->catch_table.errinfo = rb_errinfo();\n\n    debug_context->catch_cfp = GET_THREAD()->cfp;\n    ZFREE(debug_context->saved_frames);\n\n    size = sizeof(rb_control_frame_t) *\n        ((debug_context->cfp[debug_context->cfp_count-1] - debug_context->cfp[0]) + 1);\n    debug_context->saved_frames = (rb_control_frame_t*)malloc(size);\n    memcpy(debug_context->saved_frames, debug_context->cfp[0], size);\n\n    ZFREE(debug_context->saved_cfp);\n    size = sizeof(rb_control_frame_t*) * debug_context->cfp_count;\n    debug_context->saved_cfp = (rb_control_frame_t**)malloc(size);\n    memcpy(debug_context->saved_cfp, debug_context->cfp, size);\n    debug_context->saved_cfp_count = debug_context->cfp_count;\n\n    CTX_FL_SET(debug_context, CTX_FL_EXCEPTION_TEST);\n}\n\nstatic int\ntry_thread_lock(rb_thread_t *th, debug_context_t *debug_context)\n{\n    while(1)\n    {\n        /* halt execution of the current thread if the debugger\n           is activated in another\n         */\n        while(locker != Qnil && locker != th->self)\n        {\n            add_to_locked(th->self);\n            rb_thread_stop();\n        }\n\n        /* stop the current thread if it\'s marked as suspended */\n        if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND) && locker != th->self)\n        {\n            CTX_FL_SET(debug_context, CTX_FL_WAS_RUNNING);\n            rb_thread_stop();\n        }\n        else break;\n    }\n\n    /* return if the current thread is the locker */\n    if (locker != Qnil) return(0);\n\n    /* only the current thread can proceed */\n    locker = th->self;\n    return(1);\n}\n\nstatic int\nhandle_raise_event(rb_thread_t *th, debug_context_t *debug_context)\n{\n    VALUE ancestors;\n    VALUE aclass;\n    int i;\n\n    ZFREE(debug_context->saved_frames);\n    if (CTX_FL_TEST(debug_context, CTX_FL_RETHROW))\n    {\n        CTX_FL_UNSET(debug_context, CTX_FL_RETHROW);\n        return(0);\n    }\n    if (debug_context->cfp_count == 0 || !try_thread_lock(th, debug_context))\n        return(0);\n\n    if (skip_next_exception == Qtrue)\n    {\n        skip_next_exception = Qfalse;\n        debug_context->last_exception = rb_errinfo();\n        return(1);\n    }\n\n    if (rb_errinfo() == debug_context->last_exception)\n        return(1);\n\n    debug_context->last_exception = Qnil;\n\n    if (rdebug_catchpoints == Qnil ||\n        (debug_context->cfp_count == 0) ||\n        CTX_FL_TEST(debug_context, CTX_FL_CATCHING) ||\n#ifdef _ST_NEW_\n        st_get_num_entries(RHASH_TBL(rdebug_catchpoints)) == 0\n#else\n        (RHASH_TBL(rdebug_catchpoints)->num_entries) == 0\n#endif\n\t)\n    {\n        if (catchall == Qfalse) return(1);\n    }\n\n    ancestors = rb_mod_ancestors(rb_obj_class(rb_errinfo()));\n    for(i = 0; i < RARRAY_LEN(ancestors); i++)\n    {\n        VALUE mod_name;\n        VALUE hit_count;\n\n        aclass    = rb_ary_entry(ancestors, i);\n        mod_name  = rb_mod_name(aclass);\n        hit_count = rb_hash_aref(rdebug_catchpoints, mod_name);\n        if (hit_count != Qnil)\n        {\n            if (!catch_exception(debug_context, mod_name))\n                debug_runtime_error(debug_context, "Could not catch exception");\n            return(1);\n        }\n    }\n\n    if (catchall == Qtrue)\n        save_frames(debug_context);\n    return(1);\n}\n\nstatic void\ndebug_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)\n{\n    VALUE context;\n    VALUE breakpoint = Qnil;\n    debug_context_t *debug_context;\n    int moved = 0;\n    rb_thread_t *th;\n    struct rb_iseq_struct *iseq;\n    const char *file;\n    int line = 0;\n\n    if (hook_off == Qtrue)\n        return;\n\n    th   = GET_THREAD();\n    iseq = th->cfp->iseq;\n    hook_count++;\n    thread_context_lookup(th->self, &context, &debug_context, 1);\n\n    /* return if thread is marked as \'ignored\'.\n       debugger\'s threads are marked this way\n     */\n    if(CTX_FL_TEST(debug_context, CTX_FL_IGNORE)) return;\n\n    if (debug_context->top_cfp && th->cfp >= debug_context->top_cfp)\n        return;\n    debug_context->catch_cfp = NULL;\n\n    if (event == RUBY_EVENT_RAISE)\n    {\n        if (handle_raise_event(th, debug_context))\n            goto cleanup;\n        return;\n    }\n\n    if (iseq == NULL || th->cfp->pc == NULL)\n        return;\n\n    if (event == RUBY_EVENT_LINE && CTX_FL_TEST(debug_context, CTX_FL_EXCEPTION_TEST))\n    {\n        if (iseq->type == ISEQ_TYPE_ENSURE)\n        {\n            /* don\'t allow "ensure" blocks to execute yet: jump out of here by re-throwing exception */\n            VALUE *jump_pc =\n                iseq->iseq_encoded + iseq->iseq_size - insn_len(BIN(getdynamic)) - insn_len(BIN(throw));\n\n            if (jump_pc[0] != bin_getdynamic || jump_pc[insn_len(BIN(getdynamic))] != bin_throw)\n                debug_runtime_error(debug_context, "Unexpected instructions in ENSURE block");\n\n            debug_context->jump_cfp = th->cfp;\n            debug_context->jump_pc = jump_pc;\n            debug_context->saved_jump_ins[0] = th->cfp->pc[0];\n            debug_context->saved_jump_ins[1] = th->cfp->pc[1];\n            th->cfp->pc[0] = bin_opt_call_c_function;\n            th->cfp->pc[1] = (VALUE)do_jump;\n            CTX_FL_SET(debug_context, CTX_FL_ENSURE_SKIPPED);\n            return;\n        }\n        else if (iseq->type == ISEQ_TYPE_RESCUE)\n        {\n            CTX_FL_UNSET(debug_context, CTX_FL_EXCEPTION_TEST);\n            if (CTX_FL_TEST(debug_context, CTX_FL_ENSURE_SKIPPED))\n            {\n                /* exception was caught by the code; need to start the whole thing over */\n                debug_context->saved_jump_ins[0] = th->cfp->pc[0];\n                debug_context->saved_jump_ins[1] = th->cfp->pc[1];\n                th->cfp->pc[0] = bin_opt_call_c_function;\n                th->cfp->pc[1] = (VALUE)do_catchall;\n                CTX_FL_UNSET(debug_context, CTX_FL_ENSURE_SKIPPED);\n                CTX_FL_SET(debug_context, CTX_FL_RETHROW);\n                return;\n            }\n        }\n    }\n\n    if (iseq->defined_method_id == id_binding_n &&\n        (self == rb_mKernel || self == mDebugger || klass == cContext || klass == 0))\n        return;\n    if (iseq->defined_method_id == id_frame_binding && (klass == cContext || klass == 0))\n        return;\n\n    if (event == RUBY_EVENT_LINE || event == RUBY_EVENT_CALL)\n    {\n        mid = iseq->defined_method_id;\n        klass = iseq->klass;\n    }\n\n    if (mid == ID_ALLOCATOR) return;\n\n    if (!try_thread_lock(th, debug_context))\n        return;\n\n    if (iseq->type != ISEQ_TYPE_RESCUE && iseq->type != ISEQ_TYPE_ENSURE)\n    {\n        if (debug_context->start_cfp == NULL || th->cfp > debug_context->start_cfp)\n            create_exception_catchall(debug_context);\n    }\n\n    /* restore catch tables removed for jump */\n    if (debug_context->old_iseq_catch != NULL)\n    {\n        int i = 0;\n        while (debug_context->old_iseq_catch[i].iseq != NULL)\n        {\n            debug_context->old_iseq_catch[i].iseq->catch_table = debug_context->old_iseq_catch[i].catch_table;\n            debug_context->old_iseq_catch[i].iseq->catch_table_size = debug_context->old_iseq_catch[i].catch_table_size;\n            i++;\n        }\n        ZFREE(debug_context->old_iseq_catch);\n    }\n\n    /* make sure all threads have event flag set so we\'ll get its events */\n    st_foreach(th->vm->living_threads, set_thread_event_flag_i, 0);\n\n    if (debug_context->thread_pause)\n    {\n        debug_context->thread_pause = 0;\n        debug_context->stop_next = 1;\n        debug_context->dest_frame = -1;\n        moved = 1;\n    }\n\n    /* ignore a skipped section of code */\n    if (CTX_FL_TEST(debug_context, CTX_FL_SKIPPED))\n        goto cleanup;\n\n    debug_context->cur_cfp = th->cfp;\n    if (iseq->type != ISEQ_TYPE_RESCUE && iseq->type != ISEQ_TYPE_ENSURE)\n        set_cfp(debug_context);\n\n    /* There can be many event calls per line, but we only want\n     *one* breakpoint per line. */\n    line = rb_sourceline();\n    file = RSTRING_PTR(iseq->filename);\n    if(debug_context->last_line != line || debug_context->last_file == NULL ||\n       strcmp(debug_context->last_file, file) != 0)\n    {\n        CTX_FL_SET(debug_context, CTX_FL_ENABLE_BKPT);\n        moved = 1;\n    }\n\n    if(event != RUBY_EVENT_LINE)\n        CTX_FL_SET(debug_context, CTX_FL_STEPPED);\n\n    switch(event)\n    {\n    case RUBY_EVENT_LINE:\n    {\n        if (CTX_FL_TEST(debug_context, CTX_FL_CATCHING))\n        {\n            int hit_count;\n\n            /* send catchpoint notification */\n            hit_count = INT2FIX(FIX2INT(rb_hash_aref(rdebug_catchpoints,\n                debug_context->catch_table.mod_name)+1));\n            rb_hash_aset(rdebug_catchpoints, debug_context->catch_table.mod_name, hit_count);\n            debug_context->stop_reason = CTX_STOP_CATCHPOINT;\n            rb_funcall(context, idAtCatchpoint, 1, debug_context->catch_table.errinfo);\n            call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));\n\n            /* now allow the next exception to be caught */\n            CTX_FL_UNSET(debug_context, CTX_FL_CATCHING);\n            break;\n        }\n\n        if(RTEST(tracing) || CTX_FL_TEST(debug_context, CTX_FL_TRACING))\n            rb_funcall(context, idAtTracing, 2, rb_str_new2(file), INT2FIX(line));\n\n        if(debug_context->dest_frame == -1 ||\n            debug_context->cfp_count == debug_context->dest_frame)\n        {\n            if(moved || !CTX_FL_TEST(debug_context, CTX_FL_FORCE_MOVE))\n                debug_context->stop_next--;\n            if(debug_context->stop_next < 0)\n                debug_context->stop_next = -1;\n            if(moved || (CTX_FL_TEST(debug_context, CTX_FL_STEPPED) &&\n                        !CTX_FL_TEST(debug_context, CTX_FL_FORCE_MOVE)))\n            {\n                debug_context->stop_line--;\n                CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);\n            }\n        }\n        else if(debug_context->cfp_count < debug_context->dest_frame)\n        {\n            debug_context->stop_next = 0;\n        }\n\n        if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||\n            (breakpoint = check_breakpoints_by_pos(debug_context, file, line)) != Qnil)\n        {\n            call_at_line_check(self, debug_context, breakpoint, context, file, line);\n        }\n        break;\n    }\n    case RUBY_EVENT_CALL:\n    {\n        breakpoint = check_breakpoints_by_method(debug_context, klass, mid, self);\n        if(breakpoint != Qnil)\n        {\n            if(!check_breakpoint_expression(breakpoint, rb_binding_new()))\n                break;\n            if(!check_breakpoint_hit_condition(breakpoint))\n                break;\n            if(breakpoint != debug_context->breakpoint)\n            {\n                debug_context->stop_reason = CTX_STOP_BREAKPOINT;\n                rb_funcall(context, idAtBreakpoint, 1, breakpoint);\n            }\n            else\n                debug_context->breakpoint = Qnil;\n            call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));\n            break;\n        }\n        breakpoint = check_breakpoints_by_pos(debug_context, file, line);\n        if (breakpoint != Qnil)\n            call_at_line_check(self, debug_context, breakpoint, context, file, line);\n        break;\n    }\n    case RUBY_EVENT_C_RETURN:\n    {\n        /* note if a block is given we fall through! */\n        if(!rb_method_boundp(klass, mid, 0) || !c_call_new_frame_p(klass, mid))\n            break;\n    }\n    case RUBY_EVENT_RETURN:\n    case RUBY_EVENT_END:\n    {\n        if(debug_context->cfp_count == debug_context->stop_frame)\n        {\n            debug_context->stop_next = 1;\n            debug_context->stop_frame = 0;\n            /* NOTE: can\'t use call_at_line function here to trigger a debugger event.\n               this can lead to segfault. We should only unroll the stack on this event.\n             */\n        }\n        CTX_FL_SET(debug_context, CTX_FL_ENABLE_BKPT);\n\n        break;\n    }\n    }\n\ncleanup:\n\n    debug_context->stop_reason = CTX_STOP_NONE;\n\n    /* check that all contexts point to alive threads */\n    if(hook_count - last_check > 3000)\n    {\n        check_thread_contexts();\n        last_check = hook_count;\n    }\n\n    /* release a lock */\n    locker = Qnil;\n\n    /* let the next thread to run */\n    {\n        VALUE next_thread = remove_from_locked();\n        if(next_thread != Qnil)\n            rb_thread_run(next_thread);\n    }\n}\n\n/*\n *   call-seq:\n *      Debugger.start_ -> bool\n *      Debugger.start_ { ... } -> bool\n *\n *   Deprecated.\n *\n */\nstatic VALUE\ndebug_start(VALUE self)\n{\n    hook_off = Qfalse;\n    return(Qtrue);\n}\n\n/*\n *   call-seq:\n *      Debugger.stop -> bool\n *\n *   Deprecated.\n */\nstatic VALUE\ndebug_stop(VALUE self)\n{\n    hook_off = Qtrue;\n    return Qtrue;\n}\n\nstatic int\nfind_last_context_func(st_data_t key, st_data_t value, st_data_t result_arg)\n{\n    debug_context_t *debug_context;\n    VALUE *result = (VALUE *)result_arg;\n    if(!value) {\n        return ST_CONTINUE;\n    }\n    Data_Get_Struct((VALUE)value, debug_context_t, debug_context);\n    if(debug_context->thnum == last_debugged_thnum)\n    {\n        *result = value;\n        return ST_STOP;\n    }\n    return ST_CONTINUE;\n}\n\n/*\n *   call-seq:\n *      Debugger.last_interrupted -> context\n *\n *   Returns last debugged context.\n */\nstatic VALUE\ndebug_last_interrupted(VALUE self)\n{\n    VALUE result = Qnil;\n    threads_table_t *threads_table;\n\n    Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);\n\n    st_foreach(threads_table->tbl, find_last_context_func, (st_data_t)&result);\n    return result;\n}\n\n/*\n *   call-seq:\n *      Debugger.current_context -> context\n *\n *   Returns current context.\n *   <i>Note:</i> Debugger.current_context.thread == Thread.current\n */\nstatic VALUE\ndebug_current_context(VALUE self)\n{\n    VALUE thread, context;\n\n    thread = rb_thread_current();\n    thread_context_lookup(thread, &context, NULL, 1);\n\n    return context;\n}\n\n/*\n *   call-seq:\n *      Debugger.thread_context(thread) -> context\n *\n *   Returns context of the thread passed as an argument.\n */\nstatic VALUE\ndebug_thread_context(VALUE self, VALUE thread)\n{\n    VALUE context;\n\n    thread_context_lookup(thread, &context, NULL, 1);\n    return context;\n}\n\n/*\n *   call-seq:\n *      Debugger.contexts -> array\n *\n *   Returns an array of all contexts.\n */\nstatic VALUE\ndebug_contexts(VALUE self)\n{\n    volatile VALUE list;\n    volatile VALUE new_list;\n    VALUE thread, context;\n    threads_table_t *threads_table;\n    debug_context_t *debug_context;\n    int i;\n\n    new_list = rb_ary_new();\n    list = rb_funcall(rb_cThread, idList, 0);\n    for(i = 0; i < RARRAY_LEN(list); i++)\n    {\n        thread = rb_ary_entry(list, i);\n        thread_context_lookup(thread, &context, NULL, 1);\n        rb_ary_push(new_list, context);\n    }\n    threads_table_clear(rdebug_threads_tbl);\n    Data_Get_Struct(rdebug_threads_tbl, threads_table_t, threads_table);\n    for(i = 0; i < RARRAY_LEN(new_list); i++)\n    {\n        context = rb_ary_entry(new_list, i);\n        Data_Get_Struct(context, debug_context_t, debug_context);\n        st_insert(threads_table->tbl, debug_context->thread_id, context);\n    }\n\n    return new_list;\n}\n\n/*\n *   call-seq:\n *      Debugger.suspend -> Debugger\n *\n *   Suspends all contexts.\n */\nstatic VALUE\ndebug_suspend(VALUE self)\n{\n    VALUE current, context;\n    VALUE context_list;\n    debug_context_t *debug_context;\n    int i;\n\n    context_list = debug_contexts(self);\n    thread_context_lookup(rb_thread_current(), &current, NULL, 1);\n\n    for(i = 0; i < RARRAY_LEN(context_list); i++)\n    {\n        context = rb_ary_entry(context_list, i);\n        if(current == context)\n            continue;\n        Data_Get_Struct(context, debug_context_t, debug_context);\n        context_suspend_0(debug_context);\n    }\n\n    return self;\n}\n\n/*\n *   call-seq:\n *      Debugger.resume -> Debugger\n *\n *   Resumes all contexts.\n */\nstatic VALUE\ndebug_resume(VALUE self)\n{\n    VALUE current, context;\n    VALUE context_list;\n    debug_context_t *debug_context;\n    int i;\n\n    context_list = debug_contexts(self);\n\n    thread_context_lookup(rb_thread_current(), &current, NULL, 1);\n    for(i = 0; i < RARRAY_LEN(context_list); i++)\n    {\n        context = rb_ary_entry(context_list, i);\n        if(current == context)\n            continue;\n        Data_Get_Struct(context, debug_context_t, debug_context);\n        context_resume_0(debug_context);\n    }\n\n    rb_thread_schedule();\n\n    return self;\n}\n\n/*\n *   call-seq:\n *      Debugger.tracing -> bool\n *\n *   Returns +true+ if the global tracing is activated.\n */\nstatic VALUE\ndebug_tracing(VALUE self)\n{\n    return tracing;\n}\n\n/*\n *   call-seq:\n *      Debugger.tracing = bool\n *\n *   Sets the global tracing flag.\n */\nstatic VALUE\ndebug_set_tracing(VALUE self, VALUE value)\n{\n    tracing = RTEST(value) ? Qtrue : Qfalse;\n    return value;\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_debug(VALUE self)\n{\n    return debug_flag;\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_set_debug(VALUE self, VALUE value)\n{\n    debug_flag = RTEST(value) ? Qtrue : Qfalse;\n    return value;\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_catchall(VALUE self)\n{\n    return catchall;\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_set_catchall(VALUE self, VALUE value)\n{\n    catchall = RTEST(value) ? Qtrue : Qfalse;\n    return value;\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_skip_next_exception(VALUE self)\n{\n    skip_next_exception = Qtrue;\n    return(Qtrue);\n}\n\n/* :nodoc: */\nstatic VALUE\ndebug_thread_inherited(VALUE klass)\n{\n    rb_raise(rb_eRuntimeError, "Can\'t inherit Debugger::DebugThread class");\n}\n\n/*\n *   call-seq:\n *      Debugger.debug_load(file, stop = false, increment_start = false) -> nil\n *\n *   Same as Kernel#load but resets current context\'s frames.\n *   +stop+ parameter forces the debugger to stop at the first line of code in the +file+\n *   +increment_start+ determines if start_count should be incremented. When\n *    control threads are used, they have to be set up before loading the\n *    debugger; so here +increment_start+ will be false.\n *   FOR INTERNAL USE ONLY.\n */\nstatic VALUE\ndebug_debug_load(int argc, VALUE *argv, VALUE self)\n{\n    VALUE file, stop, context, increment_start;\n    debug_context_t *debug_context;\n    int state = 0;\n\n    if(rb_scan_args(argc, argv, "12", &file, &stop, &increment_start) == 1)\n    {\n        stop = Qfalse;\n        increment_start = Qtrue;\n    }\n\n    context = debug_current_context(self);\n    Data_Get_Struct(context, debug_context_t, debug_context);\n    debug_context->start_cfp = NULL;\n    debug_context->top_cfp = GET_THREAD()->cfp;\n    if(RTEST(stop))\n        debug_context->stop_next = 1;\n    /* Initializing $0 to the script\'s path */\n    ruby_script(RSTRING_PTR(file));\n    rb_load_protect(file, 0, &state);\n    if (0 != state)\n    {\n        VALUE errinfo = rb_errinfo();\n        debug_suspend(self);\n        reset_stepping_stop_points(debug_context);\n        rb_set_errinfo(Qnil);\n        return errinfo;\n    }\n\n    /* We should run all at_exit handler\'s in order to provide,\n     * for instance, a chance to run all defined test cases */\n    rb_exec_end_proc();\n\n    return Qnil;\n}\n\nstatic VALUE\nset_current_skipped_status(VALUE status)\n{\n    VALUE context;\n    debug_context_t *debug_context;\n\n    context = debug_current_context(Qnil);\n    Data_Get_Struct(context, debug_context_t, debug_context);\n    if(status)\n        CTX_FL_SET(debug_context, CTX_FL_SKIPPED);\n    else\n        CTX_FL_UNSET(debug_context, CTX_FL_SKIPPED);\n    return Qnil;\n}\n\n/*\n *   call-seq:\n *      Debugger.skip { block } -> obj or nil\n *\n *   The code inside of the block is escaped from the debugger.\n */\nstatic VALUE\ndebug_skip(VALUE self)\n{\n    if (!rb_block_given_p()) {\n        rb_raise(rb_eArgError, "called without a block");\n    }\n    set_current_skipped_status(Qtrue);\n    return rb_ensure(rb_yield, Qnil, set_current_skipped_status, Qfalse);\n}\n\nstatic VALUE\ndebug_at_exit_c(VALUE proc)\n{\n    return rb_funcall(proc, rb_intern("call"), 0);\n}\n\nstatic void\ndebug_at_exit_i(VALUE proc)\n{\n    set_current_skipped_status(Qtrue);\n    rb_ensure(debug_at_exit_c, proc, set_current_skipped_status, Qfalse);\n}\n\n/*\n *   call-seq:\n *      Debugger.debug_at_exit { block } -> proc\n *\n *   Register <tt>at_exit</tt> hook which is escaped from the debugger.\n *   FOR INTERNAL USE ONLY.\n */\nstatic VALUE\ndebug_at_exit(VALUE self)\n{\n    VALUE proc;\n    if (!rb_block_given_p())\n        rb_raise(rb_eArgError, "called without a block");\n    proc = rb_block_proc();\n    rb_set_end_proc(debug_at_exit_i, proc);\n    return proc;\n}\n\n/*\n *   call-seq:\n *      context.step(steps, force = false)\n *\n *   Stops the current context after a number of +steps+ are made.\n *   +force+ parameter (if true) ensures that the cursor moves from the current line.\n */\nstatic VALUE\ncontext_stop_next(int argc, VALUE *argv, VALUE self)\n{\n    VALUE steps, force;\n    debug_context_t *debug_context;\n\n    rb_scan_args(argc, argv, "11", &steps, &force);\n    if(FIX2INT(steps) < 0)\n        rb_raise(rb_eRuntimeError, "Steps argument can\'t be negative.");\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    debug_context->stop_next = FIX2INT(steps);\n    if(RTEST(force))\n        CTX_FL_SET(debug_context, CTX_FL_FORCE_MOVE);\n    else\n        CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);\n\n    return steps;\n}\n\n/*\n *   call-seq:\n *      context.step_over(steps, frame = nil, force = false)\n *\n *   Steps over a +steps+ number of times.\n *   Make step over operation on +frame+, by default the current frame.\n *   +force+ parameter (if true) ensures that the cursor moves from the current line.\n */\nstatic VALUE\ncontext_step_over(int argc, VALUE *argv, VALUE self)\n{\n    VALUE lines, frame, force;\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if(debug_context->cfp_count == 0)\n        rb_raise(rb_eRuntimeError, "No frames collected.");\n\n    rb_scan_args(argc, argv, "12", &lines, &frame, &force);\n    debug_context->stop_line = FIX2INT(lines);\n    CTX_FL_UNSET(debug_context, CTX_FL_STEPPED);\n    if(frame == Qnil)\n    {\n        debug_context->dest_frame = debug_context->cfp_count;\n    }\n    else\n    {\n        if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->cfp_count)\n            rb_raise(rb_eRuntimeError, "Destination frame is out of range.");\n        debug_context->dest_frame = debug_context->cfp_count - FIX2INT(frame);\n    }\n    if(RTEST(force))\n        CTX_FL_SET(debug_context, CTX_FL_FORCE_MOVE);\n    else\n        CTX_FL_UNSET(debug_context, CTX_FL_FORCE_MOVE);\n\n    return Qnil;\n}\n\n/*\n *   call-seq:\n *      context.stop_frame(frame)\n *\n *   Stops when a frame with number +frame+ is activated. Implements +finish+ and +next+ commands.\n */\nstatic VALUE\ncontext_stop_frame(VALUE self, VALUE frame)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if(FIX2INT(frame) < 0 && FIX2INT(frame) >= debug_context->cfp_count)\n        rb_raise(rb_eRuntimeError, "Stop frame is out of range.");\n    debug_context->stop_frame = debug_context->cfp_count - FIX2INT(frame);\n\n    return frame;\n}\n\ninline static int\ncheck_frame_number(debug_context_t *debug_context, VALUE frame)\n{\n    int frame_n;\n\n    frame_n = FIX2INT(frame);\n    if(frame_n < 0 || frame_n >= debug_context->cfp_count)\n    rb_raise(rb_eArgError, "Invalid frame number %d, stack (0...%d)",\n        frame_n, debug_context->cfp_count - 1);\n    return frame_n;\n}\n\nstatic int\noptional_frame_position(int argc, VALUE *argv)\n{\n  unsigned int i_scanned;\n  VALUE level;\n\n  if ((argc > 1) || (argc < 0))\n    rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 or 1)", argc);\n  i_scanned = rb_scan_args(argc, argv, "01", &level);\n  if (0 == i_scanned) {\n    level = INT2FIX(0);\n  }\n  return level;\n}\n\n/*\n *   call-seq:\n *      context.frame_binding(frame_position=0) -> binding\n *\n *   Returns frame\'s binding.\n */\nstatic VALUE\ncontext_frame_binding(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n    rb_thread_t *th;\n    VALUE bindval;\n    rb_binding_t *bind;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    GetThreadPtr(context_thread_0(debug_context), th);\n    cfp = GET_CFP;\n\n    bindval = binding_alloc(rb_cBinding);\n    GetBindingPtr(bindval, bind);\n    bind->env = rb_vm_make_env_object(th, cfp);\n    return bindval;\n}\n\n/*\n *   call-seq:\n *      context.frame_method(frame_position=0) -> sym\n *\n *   Returns the sym of the called method.\n */\nstatic VALUE\ncontext_frame_id(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    cfp = GET_CFP;\n    if (cfp->iseq == NULL || cfp->iseq->defined_method_id == 0) return(Qnil);\n\n#if defined HAVE_RB_CONTROL_FRAME_T_METHOD_ID\n    return(RUBYVM_CFUNC_FRAME_P(cfp) ? ID2SYM(cfp->method_id) : ID2SYM(cfp->iseq->defined_method_id));\n#elif defined HAVE_RB_METHOD_ENTRY_T_CALLED_ID\n    return(RUBYVM_CFUNC_FRAME_P(cfp) ? ID2SYM(cfp->me->called_id) : ID2SYM(cfp->iseq->defined_method_id));\n#endif\n}\n\n/*\n *   call-seq:\n *      context.frame_line(frame_position) -> int\n *\n *   Returns the line number in the file.\n */\nstatic VALUE\ncontext_frame_line(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    cfp = GET_CFP;\n    return(INT2FIX(rb_vm_get_sourceline(cfp)));\n}\n\n/*\n *   call-seq:\n *      context.frame_file(frame_position) -> string\n *\n *   Returns the name of the file.\n */\nstatic VALUE\ncontext_frame_file(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    cfp = GET_CFP;\n    while (cfp <= debug_context->start_cfp)\n    {\n        if (cfp->iseq != NULL)\n            return(cfp->iseq->filename);\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n    }\n    return(Qnil);\n}\n\n/*\n *   call-seq:\n *      context.frame_locals(frame) -> hash\n *\n *   Returns frame\'s local variables.\n */\nstatic VALUE\ncontext_frame_locals(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    int i;\n    rb_control_frame_t *cfp;\n    rb_iseq_t *iseq;\n    VALUE hash;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    cfp = GET_CFP;\n    iseq = cfp->iseq;\n    hash = rb_hash_new();\n\n    if (iseq != NULL && iseq->local_table != NULL)\n    {\n        /* Note rb_iseq_disasm() is instructive in coming up with this code */\n        for (i = 0; i < iseq->local_table_size; i++)\n        {\n            VALUE str = rb_id2str(iseq->local_table[i]);\n            if (str != 0)\n                rb_hash_aset(hash, str, *(cfp->dfp - iseq->local_size + i));\n        }\n    }\n\n    return(hash);\n}\n\n/*\n *   call-seq:\n *      context.frame_args(frame_position=0) -> list\n *\n *   Returns frame\'s argument parameters\n */\nstatic VALUE\ncontext_frame_args(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n\n    cfp = GET_CFP;\n    if (cfp && cfp->iseq && cfp->iseq->local_table && cfp->iseq->argc)\n    {\n        int i;\n        VALUE list;\n\n        list = rb_ary_new2(cfp->iseq->argc);\n        for (i = 0; i < cfp->iseq->argc; i++)\n        {\n            if (!rb_is_local_id(cfp->iseq->local_table[i])) continue; /* skip flip states */\n            rb_ary_push(list, rb_id2str(cfp->iseq->local_table[i]));\n        }\n\n        return(list);\n    }\n    return(rb_ary_new2(0));\n}\n\n/*\n *   call-seq:\n *      context.frame_self(frame_postion=0) -> obj\n *\n *   Returns self object of the frame.\n */\nstatic VALUE\ncontext_frame_self(int argc, VALUE *argv, VALUE self)\n{\n    VALUE frame;\n    debug_context_t *debug_context;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n\n    return(GET_CFP->self);\n}\n\n/*\n *   call-seq:\n *      context.frame_class(frame_position) -> obj\n *\n *   Returns the real class of the frame.\n *   It could be different than context.frame_self(frame).class\n */\nstatic VALUE\ncontext_frame_class(int argc, VALUE *argv, VALUE self)\n{\n    VALUE klass;\n    VALUE frame;\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n\n    frame = optional_frame_position(argc, argv);\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    cfp = GET_CFP;\n\n    if (cfp->iseq == NULL) return(Qnil);\n    klass = real_class(cfp->iseq->klass);\n    if(TYPE(klass) == T_CLASS || TYPE(klass) == T_MODULE)\n        return klass;\n    return Qnil;\n}\n\n\n/*\n *   call-seq:\n *      context.stack_size-> int\n *\n *   Returns the size of the context stack.\n */\nstatic VALUE\ncontext_stack_size(VALUE self)\n{\n    debug_context_t *debug_context;\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return(INT2FIX(debug_context->cfp_count));\n}\n\n/*\n *   call-seq:\n *      context.stack_inc-> bool\n *\n *   Increments the top of the call stack to the previous valid frame, if possible.\n */\nstatic VALUE\ncontext_stack_inc(VALUE self)\n{\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n    rb_thread_t *th;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    GetThreadPtr(context_thread_0(debug_context), th);\n    cfp = debug_context->start_cfp;\n\n    while (cfp < RUBY_VM_END_CONTROL_FRAME(th))\n    {\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n        if (cfp->iseq != NULL && cfp->pc != NULL)\n        {\n            debug_context->start_cfp = cfp;\n            if (GET_THREAD() == th)\n                set_cfp(debug_context);\n            return(Qtrue);\n        }\n    }\n    return(Qfalse);\n}\n\n/*\n *   call-seq:\n *      context.stack_dec-> bool\n *\n *   Decrements the top of the call stack to the next valid frame, if possible.\n */\nstatic VALUE\ncontext_stack_dec(VALUE self)\n{\n    debug_context_t *debug_context;\n    rb_control_frame_t *cfp;\n    rb_thread_t *th;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    GetThreadPtr(context_thread_0(debug_context), th);\n    cfp = debug_context->start_cfp;\n\n    while (cfp >= debug_context->cur_cfp)\n    {\n        cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp);\n        if (cfp->iseq != NULL && cfp->pc != NULL)\n        {\n            debug_context->start_cfp = cfp;\n            if (GET_THREAD() == th)\n                set_cfp(debug_context);\n            return(Qtrue);\n        }\n    }\n    return(Qfalse);\n}\n\n/*\n *   call-seq:\n *      context.thread -> thread\n *\n *   Returns a thread this context is associated with.\n */\nstatic VALUE\ncontext_thread(VALUE self)\n{\n    debug_context_t *debug_context;\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return(id2ref(debug_context->thread_id));\n}\n\n/*\n *   call-seq:\n *      context.thnum -> int\n *\n *   Returns the context\'s number.\n */\nstatic VALUE\ncontext_thnum(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n\n    return INT2FIX(debug_context->thnum);\n}\n\nstatic void\ncontext_suspend_0(debug_context_t *debug_context)\n{\n    VALUE status;\n\n    status = rb_funcall(context_thread_0(debug_context), rb_intern("status"), 0);\n    if(rb_str_cmp(status, rb_str_new2("run")) == 0)\n      CTX_FL_SET(debug_context, CTX_FL_WAS_RUNNING);\n    else if(rb_str_cmp(status, rb_str_new2("sleep")) == 0)\n      CTX_FL_UNSET(debug_context, CTX_FL_WAS_RUNNING);\n    else\n      return;\n    CTX_FL_SET(debug_context, CTX_FL_SUSPEND);\n}\n\nstatic void\ncontext_resume_0(debug_context_t *debug_context)\n{\n    if(!CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))\n      return;\n    CTX_FL_UNSET(debug_context, CTX_FL_SUSPEND);\n    if(CTX_FL_TEST(debug_context, CTX_FL_WAS_RUNNING))\n      rb_thread_wakeup(context_thread_0(debug_context));\n}\n\n/*\n *   call-seq:\n *      context.suspend -> nil\n *\n *   Suspends the thread when it is running.\n */\nstatic VALUE\ncontext_suspend(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))\n        rb_raise(rb_eRuntimeError, "Already suspended.");\n    context_suspend_0(debug_context);\n    return Qnil;\n}\n\n/*\n *   call-seq:\n *      context.suspended? -> bool\n *\n *   Returns +true+ if the thread is suspended by debugger.\n */\nstatic VALUE\ncontext_is_suspended(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return CTX_FL_TEST(debug_context, CTX_FL_SUSPEND) ? Qtrue : Qfalse;\n}\n\n/*\n *   call-seq:\n *      context.resume -> nil\n *\n *   Resumes the thread from the suspended mode.\n */\nstatic VALUE\ncontext_resume(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if(!CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))\n        rb_raise(rb_eRuntimeError, "Thread is not suspended.");\n    context_resume_0(debug_context);\n    return Qnil;\n}\n\n/*\n *   call-seq:\n *      context.tracing -> bool\n *\n *   Returns the tracing flag for the current context.\n */\nstatic VALUE\ncontext_tracing(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return CTX_FL_TEST(debug_context, CTX_FL_TRACING) ? Qtrue : Qfalse;\n}\n\n/*\n *   call-seq:\n *      context.tracing = bool\n *\n *   Controls the tracing for this context.\n */\nstatic VALUE\ncontext_set_tracing(VALUE self, VALUE value)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if(RTEST(value))\n        CTX_FL_SET(debug_context, CTX_FL_TRACING);\n    else\n        CTX_FL_UNSET(debug_context, CTX_FL_TRACING);\n    return value;\n}\n\n/*\n *   call-seq:\n *      context.ignored? -> bool\n *\n *   Returns the ignore flag for the current context.\n */\nstatic VALUE\ncontext_ignored(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return CTX_FL_TEST(debug_context, CTX_FL_IGNORE) ? Qtrue : Qfalse;\n}\n\n/*\n *   call-seq:\n *      context.dead? -> bool\n *\n *   Returns +true+ if context doesn\'t represent a live context and is created\n *   during post-mortem exception handling.\n */\nstatic VALUE\ncontext_dead(VALUE self)\n{\n    debug_context_t *debug_context;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    return CTX_FL_TEST(debug_context, CTX_FL_DEAD) ? Qtrue : Qfalse;\n}\n\n/*\n *   call-seq:\n *      context.stop_reason -> sym\n *\n *   Returns the reason for the stop. It maybe of the following values:\n *   :initial, :step, :breakpoint, :catchpoint, :post-mortem\n */\nstatic VALUE\ncontext_stop_reason(VALUE self)\n{\n    debug_context_t *debug_context;\n    const char * sym_name;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n\n    switch(debug_context->stop_reason)\n    {\n        case CTX_STOP_STEP:\n            sym_name = "step";\n            break;\n        case CTX_STOP_BREAKPOINT:\n            sym_name = "breakpoint";\n            break;\n        case CTX_STOP_CATCHPOINT:\n            sym_name = "catchpoint";\n            break;\n        case CTX_STOP_NONE:\n        default:\n            sym_name = "none";\n    }\n    if(CTX_FL_TEST(debug_context, CTX_FL_DEAD))\n        sym_name = "post-mortem";\n\n    return ID2SYM(rb_intern(sym_name));\n}\n\n/*\n *   call-seq:\n *      context.jump(line, file) -> bool\n *\n *   Returns +true+ if jump to +line+ in filename +file+ was successful.\n */\nstatic VALUE\ncontext_jump(VALUE self, VALUE line, VALUE file)\n{\n    debug_context_t *debug_context;\n    int i;\n    rb_thread_t *th;\n    rb_control_frame_t *cfp;\n    rb_control_frame_t *cfp_end;\n    rb_control_frame_t *cfp_start = NULL;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    GetThreadPtr(context_thread_0(debug_context), th);\n    line = FIX2INT(line);\n\n    cfp_start = debug_context->cur_cfp;\n    cfp_end = RUBY_VM_END_CONTROL_FRAME(th);\n    cfp = cfp_start;\n    if (cfp_start == NULL)\n        return(INT2FIX(2)); /* couldn\'t find frame; should never happen */\n    if ((cfp->pc - cfp->iseq->iseq_encoded) >= (cfp->iseq->iseq_size - 1))\n        return(INT2FIX(1)); /* no space for opt_call_c_function hijack */\n\n    /* find target frame to jump to */\n    while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, cfp_end))\n    {\n        if ((cfp->iseq != NULL) && (rb_str_cmp(file, cfp->iseq->filename) == 0))\n        {\n            for (i = 0; i < cfp->iseq->line_info_size; i++)\n            {\n                if (cfp->iseq->line_info_table[i].line_no != line)\n                    continue;\n\n                /* hijack the currently running code so that we can change the frame PC */\n                debug_context->saved_jump_ins[0] = cfp_start->pc[0];\n                debug_context->saved_jump_ins[1] = cfp_start->pc[1];\n                cfp_start->pc[0] = bin_opt_call_c_function;\n                cfp_start->pc[1] = (VALUE)do_jump;\n\n                debug_context->jump_cfp = cfp;\n                debug_context->jump_pc =\n                    cfp->iseq->iseq_encoded + cfp->iseq->line_info_table[i].position;\n\n                return(INT2FIX(0)); /* success */\n            }\n        }\n\n        cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n    }\n\n    return(INT2FIX(3)); /* couldn\'t find a line and file frame match */\n}\n\n/*\n *   call-seq:\n *      context.break -> bool\n *\n *   Returns +true+ if context is currently running and set flag to break it at next line\n */\nstatic VALUE\ncontext_pause(VALUE self)\n{\n    debug_context_t *debug_context;\n    rb_thread_t *th;\n\n    Data_Get_Struct(self, debug_context_t, debug_context);\n    if (CTX_FL_TEST(debug_context, CTX_FL_DEAD))\n        return(Qfalse);\n\n    GetThreadPtr(context_thread_0(debug_context), th);\n    if (th == GET_THREAD())\n        return(Qfalse);\n\n    debug_context->thread_pause = 1;\n    return(Qtrue);\n}\n\n\n/*\n *   Document-class: Context\n *\n *   == Summary\n *\n *   Debugger keeps a single instance of this class for each Ruby thread.\n */\nstatic void\nInit_context(void)\n{\n    cContext = rb_define_class_under(mDebugger, "Context", rb_cObject);\n    rb_define_method(cContext, "stop_next=", context_stop_next, -1);\n    rb_define_method(cContext, "step", context_stop_next, -1);\n    rb_define_method(cContext, "step_over", context_step_over, -1);\n    rb_define_method(cContext, "stop_frame=", context_stop_frame, 1);\n    rb_define_method(cContext, "thread", context_thread, 0);\n    rb_define_method(cContext, "thnum", context_thnum, 0);\n    rb_define_method(cContext, "stop_reason", context_stop_reason, 0);\n    rb_define_method(cContext, "suspend", context_suspend, 0);\n    rb_define_method(cContext, "suspended?", context_is_suspended, 0);\n    rb_define_method(cContext, "resume", context_resume, 0);\n    rb_define_method(cContext, "tracing", context_tracing, 0);\n    rb_define_method(cContext, "tracing=", context_set_tracing, 1);\n    rb_define_method(cContext, "ignored?", context_ignored, 0);\n    rb_define_method(cContext, "frame_args", context_frame_args, -1);\n    rb_define_method(cContext, "frame_binding", context_frame_binding, -1);\n    rb_define_method(cContext, "frame_class", context_frame_class, -1);\n    rb_define_method(cContext, "frame_file", context_frame_file, -1);\n    rb_define_method(cContext, "frame_id", context_frame_id, -1);\n    rb_define_method(cContext, "frame_line", context_frame_line, -1);\n    rb_define_method(cContext, "frame_locals", context_frame_locals, -1);\n    rb_define_method(cContext, "frame_method", context_frame_id, -1);\n    rb_define_method(cContext, "frame_self", context_frame_self, -1);\n    rb_define_method(cContext, "stack_size", context_stack_size, 0);\n    rb_define_method(cContext, "stack_inc", context_stack_inc, 0);\n    rb_define_method(cContext, "stack_dec", context_stack_dec, 0);\n    rb_define_method(cContext, "dead?", context_dead, 0);\n    rb_define_method(cContext, "breakpoint",\n             context_breakpoint, 0);      /* in breakpoint.c */\n    rb_define_method(cContext, "set_breakpoint",\n             context_set_breakpoint, -1); /* in breakpoint.c */\n    rb_define_method(cContext, "jump", context_jump, 2);\n    rb_define_method(cContext, "pause", context_pause, 0);\n}\n\n/*\n *   call-seq:\n *      Debugger.breakpoints -> array\n *\n *   Returns an array of breakpoints.\n */\nstatic VALUE\ndebug_breakpoints(VALUE self)\n{\n    return rdebug_breakpoints;\n}\n\n/*\n *   call-seq:\n *      Debugger.add_breakpoint(source, pos, condition = nil) -> breakpoint\n *\n *   Adds a new breakpoint.\n *   <i>source</i> is a name of a file or a class.\n *   <i>pos</i> is a line number or a method name if <i>source</i> is a class name.\n *   <i>condition</i> is a string which is evaluated to +true+ when this breakpoint\n *   is activated.\n */\nstatic VALUE\ndebug_add_breakpoint(int argc, VALUE *argv, VALUE self)\n{\n    VALUE result;\n\n    result = create_breakpoint_from_args(argc, argv, ++bkp_count);\n    rb_ary_push(rdebug_breakpoints, result);\n    return result;\n}\n\nVALUE translate_insns(VALUE bin)\n{\n    rb_iseq_t iseq;\n    iseq.iseq = &bin;\n    iseq.iseq_size = 1;\n    iseq.iseq_encoded = NULL;\n\n    rb_iseq_translate_threaded_code(&iseq);\n    if (iseq.iseq_encoded != iseq.iseq)\n    {\n        bin = iseq.iseq_encoded[0];\n        xfree(iseq.iseq_encoded);\n    }\n    return(bin);\n}\n\n#define TRANSLATE_INSNS(op) bin_##op = translate_insns(BIN(op))\n\n/*\n *   Document-class: Debugger\n *\n *   == Summary\n *\n *   This is a singleton class allows controlling the debugger. Use it to start/stop debugger,\n *   set/remove breakpoints, etc.\n */\nvoid\nInit_ruby_debug(void)\n{\n    TRANSLATE_INSNS(opt_call_c_function);\n    TRANSLATE_INSNS(getdynamic);\n    TRANSLATE_INSNS(throw);\n\n    mDebugger = rb_define_module("Debugger");\n    rb_define_const(mDebugger, "VERSION", rb_str_new2(DEBUG_VERSION));\n    rb_define_module_function(mDebugger, "start_", debug_start, 0);\n    rb_define_module_function(mDebugger, "stop", debug_stop, 0);\n    rb_define_module_function(mDebugger, "started?", debug_is_started, 0);\n    rb_define_module_function(mDebugger, "breakpoints", debug_breakpoints, 0);\n    rb_define_module_function(mDebugger, "add_breakpoint", debug_add_breakpoint, -1);\n    rb_define_module_function(mDebugger, "remove_breakpoint",\n                  rdebug_remove_breakpoint,\n                  1);                        /* in breakpoint.c */\n    rb_define_module_function(mDebugger, "add_catchpoint",\n                  rdebug_add_catchpoint, 1); /* in breakpoint.c */\n    rb_define_module_function(mDebugger, "catchpoints",\n                  debug_catchpoints, 0);     /* in breakpoint.c */\n    rb_define_module_function(mDebugger, "last_context", debug_last_interrupted, 0);\n    rb_define_module_function(mDebugger, "contexts", debug_contexts, 0);\n    rb_define_module_function(mDebugger, "current_context", debug_current_context, 0);\n    rb_define_module_function(mDebugger, "thread_context", debug_thread_context, 1);\n    rb_define_module_function(mDebugger, "suspend", debug_suspend, 0);\n    rb_define_module_function(mDebugger, "resume", debug_resume, 0);\n    rb_define_module_function(mDebugger, "tracing", debug_tracing, 0);\n    rb_define_module_function(mDebugger, "tracing=", debug_set_tracing, 1);\n    rb_define_module_function(mDebugger, "debug_load", debug_debug_load, -1);\n    rb_define_module_function(mDebugger, "skip", debug_skip, 0);\n    rb_define_module_function(mDebugger, "debug_at_exit", debug_at_exit, 0);\n    rb_define_module_function(mDebugger, "debug", debug_debug, 0);\n    rb_define_module_function(mDebugger, "debug=", debug_set_debug, 1);\n    rb_define_module_function(mDebugger, "catchall", debug_catchall, 0);\n    rb_define_module_function(mDebugger, "catchall=", debug_set_catchall, 1);\n    rb_define_module_function(mDebugger, "skip_next_exception", debug_skip_next_exception, 0);\n\n    cThreadsTable = rb_define_class_under(mDebugger, "ThreadsTable", rb_cObject);\n\n    cDebugThread  = rb_define_class_under(mDebugger, "DebugThread", rb_cThread);\n    rb_define_singleton_method(cDebugThread, "inherited",\n                   debug_thread_inherited, 1);\n\n    Init_context();\n    Init_breakpoint();\n\n    idAtBreakpoint = rb_intern("at_breakpoint");\n    idAtCatchpoint = rb_intern("at_catchpoint");\n    idAtLine       = rb_intern("at_line");\n    idAtReturn     = rb_intern("at_return");\n    idAtTracing    = rb_intern("at_tracing");\n    idList         = rb_intern("list");\n\n    rb_mObjectSpace = rb_const_get(rb_mKernel, rb_intern("ObjectSpace"));\n\n    rb_global_variable(&last_context);\n    rb_global_variable(&last_thread);\n    rb_global_variable(&locker);\n    rb_global_variable(&rdebug_breakpoints);\n    rb_global_variable(&rdebug_catchpoints);\n    rb_global_variable(&rdebug_threads_tbl);\n\n    /* start the debugger hook */\n    id_binding_n       = rb_intern("binding_n");\n    id_frame_binding   = rb_intern("frame_binding");\n    hook_off           = Qfalse;\n    locker             = Qnil;\n    rdebug_breakpoints = rb_ary_new();\n    rdebug_catchpoints = rb_hash_new();\n    rdebug_threads_tbl = threads_table_create();\n    rb_add_event_hook(debug_event_hook, RUBY_EVENT_ALL, Qnil);\n}\n', 'message': 'store Thread itself in debug_context_t, not object ID.\n', 'author_name': 'Nobuyoshi Nakada', 'lang': 'c'}