/********************************************************************** vm_trace.c - $Author: ko1 $ created at: Tue Aug 14 19:37:09 2012 Copyright (C) 1993-2012 Yukihiro Matsumoto **********************************************************************/ /* * This file incldue two parts: * * (1) set_trace_func internal mechanisms * and C level API * * (2) Ruby level API * (2-1) set_trace_func API * (2-2) TracePoint API (not yet) * */ #include "ruby/ruby.h" #include "ruby/encoding.h" #include "internal.h" #include "vm_core.h" #include "eval_intern.h" /* (1) trace mechanisms */ #define RUBY_EVENT_REMOVED 0x1000000 enum { EVENT_RUNNING_NOTHING, EVENT_RUNNING_TRACE = 1, EVENT_RUNNING_THREAD = 2, EVENT_RUNNING_VM = 4, EVENT_RUNNING_EVENT_MASK = EVENT_RUNNING_VM|EVENT_RUNNING_THREAD }; static VALUE thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always); struct event_call_args { rb_thread_t *th; VALUE klass; VALUE self; VALUE proc; ID id; rb_event_flag_t event; }; static rb_event_hook_t * alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) { rb_event_hook_t *hook = ALLOC(rb_event_hook_t); hook->func = func; hook->flag = events; hook->data = data; return hook; } static void thread_reset_event_flags(rb_thread_t *th) { rb_event_hook_t *hook = th->event_hooks; rb_event_flag_t flag = th->event_flags & RUBY_EVENT_VM; while (hook) { if (!(flag & RUBY_EVENT_REMOVED)) flag |= hook->flag; hook = hook->next; } th->event_flags = flag; } static void rb_threadptr_add_event_hook(rb_thread_t *th, rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) { rb_event_hook_t *hook = alloc_event_hook(func, events, data); hook->next = th->event_hooks; th->event_hooks = hook; thread_reset_event_flags(th); } static rb_thread_t * thval2thread_t(VALUE thval) { rb_thread_t *th; GetThreadPtr(thval, th); return th; } void rb_thread_add_event_hook(VALUE thval, rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) { rb_threadptr_add_event_hook(thval2thread_t(thval), func, events, data); } static int set_threads_event_flags_i(st_data_t key, st_data_t val, st_data_t flag) { VALUE thval = key; rb_thread_t *th; GetThreadPtr(thval, th); if (flag) { th->event_flags |= RUBY_EVENT_VM; } else { th->event_flags &= (~RUBY_EVENT_VM); } return ST_CONTINUE; } static void set_threads_event_flags(int flag) { st_foreach(GET_VM()->living_threads, set_threads_event_flags_i, (st_data_t) flag); } static inline int exec_event_hooks(const rb_event_hook_t *hook, rb_event_flag_t flag, VALUE self, ID id, VALUE klass) { int removed = 0; for (; hook; hook = hook->next) { if (hook->flag & RUBY_EVENT_REMOVED) { removed++; continue; } if (flag & hook->flag) { (*hook->func)(flag, hook->data, self, id, klass); } } return removed; } static int remove_defered_event_hook(rb_event_hook_t **root); static VALUE thread_exec_event_hooks(VALUE args, int running) { struct event_call_args *argp = (struct event_call_args *)args; rb_thread_t *th = argp->th; rb_event_flag_t flag = argp->event; VALUE self = argp->self; ID id = argp->id; VALUE klass = argp->klass; const rb_event_flag_t wait_event = th->event_flags; int removed; if (self == rb_mRubyVMFrozenCore) return 0; if ((wait_event & flag) && !(running & EVENT_RUNNING_THREAD)) { th->tracing |= EVENT_RUNNING_THREAD; removed = exec_event_hooks(th->event_hooks, flag, self, id, klass); th->tracing &= ~EVENT_RUNNING_THREAD; if (removed) { remove_defered_event_hook(&th->event_hooks); } } if (wait_event & RUBY_EVENT_VM) { if (th->vm->event_hooks == NULL) { th->event_flags &= (~RUBY_EVENT_VM); } else if (!(running & EVENT_RUNNING_VM)) { th->tracing |= EVENT_RUNNING_VM; removed = exec_event_hooks(th->vm->event_hooks, flag, self, id, klass); th->tracing &= ~EVENT_RUNNING_VM; if (removed) { remove_defered_event_hook(&th->vm->event_hooks); } } } return 0; } void rb_threadptr_exec_event_hooks(rb_thread_t *th, rb_event_flag_t flag, VALUE self, ID id, VALUE klass) { const VALUE errinfo = th->errinfo; struct event_call_args args; args.th = th; args.event = flag; args.self = self; args.id = id; args.klass = klass; args.proc = 0; thread_suppress_tracing(th, EVENT_RUNNING_EVENT_MASK, thread_exec_event_hooks, (VALUE)&args, FALSE); th->errinfo = errinfo; } void rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) { rb_event_hook_t *hook = alloc_event_hook(func, events, data); rb_vm_t *vm = GET_VM(); hook->next = vm->event_hooks; vm->event_hooks = hook; set_threads_event_flags(1); } static int defer_remove_event_hook(rb_event_hook_t *hook, rb_event_hook_func_t func) { while (hook) { if (func == 0 || hook->func == func) { hook->flag |= RUBY_EVENT_REMOVED; } hook = hook->next; } return -1; } static int remove_event_hook(rb_event_hook_t **root, rb_event_hook_func_t func) { rb_event_hook_t *hook = *root, *next; while (hook) { next = hook->next; if (func == 0 || hook->func == func || (hook->flag & RUBY_EVENT_REMOVED)) { *root = next; xfree(hook); } else { root = &hook->next; } hook = next; } return -1; } static int remove_defered_event_hook(rb_event_hook_t **root) { rb_event_hook_t *hook = *root, *next; while (hook) { next = hook->next; if (hook->flag & RUBY_EVENT_REMOVED) { *root = next; xfree(hook); } else { root = &hook->next; } hook = next; } return -1; } static int rb_threadptr_remove_event_hook(rb_thread_t *th, rb_event_hook_func_t func) { int ret; if (th->tracing & EVENT_RUNNING_THREAD) { ret = defer_remove_event_hook(th->event_hooks, func); } else { ret = remove_event_hook(&th->event_hooks, func); } thread_reset_event_flags(th); return ret; } int rb_thread_remove_event_hook(VALUE thval, rb_event_hook_func_t func) { return rb_threadptr_remove_event_hook(thval2thread_t(thval), func); } static rb_event_hook_t * search_live_hook(rb_event_hook_t *hook) { while (hook) { if (!(hook->flag & RUBY_EVENT_REMOVED)) return hook; hook = hook->next; } return NULL; } static int running_vm_event_hooks(st_data_t key, st_data_t val, st_data_t data) { rb_thread_t *th = thval2thread_t((VALUE)key); if (!(th->tracing & EVENT_RUNNING_VM)) return ST_CONTINUE; *(rb_thread_t **)data = th; return ST_STOP; } static rb_thread_t * vm_event_hooks_running_thread(rb_vm_t *vm) { rb_thread_t *found = NULL; st_foreach(vm->living_threads, running_vm_event_hooks, (st_data_t)&found); return found; } int rb_remove_event_hook(rb_event_hook_func_t func) { rb_vm_t *vm = GET_VM(); rb_event_hook_t *hook = search_live_hook(vm->event_hooks); int ret; if (vm_event_hooks_running_thread(vm)) { ret = defer_remove_event_hook(vm->event_hooks, func); } else { ret = remove_event_hook(&vm->event_hooks, func); } if (hook && !search_live_hook(vm->event_hooks)) { set_threads_event_flags(0); } return ret; } static int clear_trace_func_i(st_data_t key, st_data_t val, st_data_t flag) { rb_thread_t *th; GetThreadPtr((VALUE)key, th); rb_threadptr_remove_event_hook(th, 0); return ST_CONTINUE; } void rb_clear_trace_func(void) { st_foreach(GET_VM()->living_threads, clear_trace_func_i, (st_data_t) 0); rb_remove_event_hook(0); } static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass); /* (2-1) set_trace_func (old API) */ /* * call-seq: * set_trace_func(proc) -> proc * set_trace_func(nil) -> nil * * Establishes _proc_ as the handler for tracing, or disables * tracing if the parameter is +nil+. _proc_ takes up * to six parameters: an event name, a filename, a line number, an * object id, a binding, and the name of a class. _proc_ is * invoked whenever an event occurs. Events are: c-call * (call a C-language routine), c-return (return from a * C-language routine), call (call a Ruby method), * class (start a class or module definition), * end (finish a class or module definition), * line (execute code on a new line), raise * (raise an exception), and return (return from a Ruby * method). Tracing is disabled within the context of _proc_. * * class Test * def test * a = 1 * b = 2 * end * end * * set_trace_func proc { |event, file, line, id, binding, classname| * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname * } * t = Test.new * t.test * * line prog.rb:11 false * c-call prog.rb:11 new Class * c-call prog.rb:11 initialize Object * c-return prog.rb:11 initialize Object * c-return prog.rb:11 new Class * line prog.rb:12 false * call prog.rb:2 test Test * line prog.rb:3 test Test * line prog.rb:4 test Test * return prog.rb:4 test Test */ static VALUE set_trace_func(VALUE obj, VALUE trace) { rb_remove_event_hook(call_trace_func); if (NIL_P(trace)) { GET_THREAD()->tracing = EVENT_RUNNING_NOTHING; return Qnil; } if (!rb_obj_is_proc(trace)) { rb_raise(rb_eTypeError, "trace_func needs to be Proc"); } rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace); return trace; } static void thread_add_trace_func(rb_thread_t *th, VALUE trace) { if (!rb_obj_is_proc(trace)) { rb_raise(rb_eTypeError, "trace_func needs to be Proc"); } rb_threadptr_add_event_hook(th, call_trace_func, RUBY_EVENT_ALL, trace); } /* * call-seq: * thr.add_trace_func(proc) -> proc * * Adds _proc_ as a handler for tracing. * See Thread#set_trace_func and +set_trace_func+. */ static VALUE thread_add_trace_func_m(VALUE obj, VALUE trace) { rb_thread_t *th; GetThreadPtr(obj, th); thread_add_trace_func(th, trace); return trace; } /* * call-seq: * thr.set_trace_func(proc) -> proc * thr.set_trace_func(nil) -> nil * * Establishes _proc_ on _thr_ as the handler for tracing, or * disables tracing if the parameter is +nil+. * See +set_trace_func+. */ static VALUE thread_set_trace_func_m(VALUE obj, VALUE trace) { rb_thread_t *th; GetThreadPtr(obj, th); rb_threadptr_remove_event_hook(th, call_trace_func); if (NIL_P(trace)) { th->tracing = EVENT_RUNNING_NOTHING; return Qnil; } thread_add_trace_func(th, trace); return trace; } 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 VALUE call_trace_proc(VALUE args, int tracing) { struct event_call_args *p = (struct event_call_args *)args; const char *srcfile = rb_sourcefile(); VALUE eventname = rb_str_new2(get_event_name(p->event)); VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil; VALUE argv[6]; int line = rb_sourceline(); ID id = 0; VALUE klass = 0; if (p->klass != 0) { id = p->id; klass = p->klass; } else { rb_thread_method_id_and_class(p->th, &id, &klass); } if (id == ID_ALLOCATOR) return Qnil; if (klass) { if (RB_TYPE_P(klass, T_ICLASS)) { klass = RBASIC(klass)->klass; } else if (FL_TEST(klass, FL_SINGLETON)) { klass = rb_iv_get(klass, "__attached__"); } } argv[0] = eventname; argv[1] = filename; argv[2] = INT2FIX(line); argv[3] = id ? ID2SYM(id) : Qnil; argv[4] = (p->self && srcfile) ? rb_binding_new() : Qnil; argv[5] = klass ? klass : Qnil; return rb_proc_call_with_block(p->proc, 6, argv, Qnil); } static void call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass) { struct event_call_args args; args.th = GET_THREAD(); args.event = event; args.proc = proc; args.self = self; args.id = id; args.klass = klass; ruby_suppress_tracing(call_trace_proc, (VALUE)&args, FALSE); } VALUE ruby_suppress_tracing(VALUE (*func)(VALUE, int), VALUE arg, int always) { rb_thread_t *th = GET_THREAD(); return thread_suppress_tracing(th, EVENT_RUNNING_TRACE, func, arg, always); } static VALUE thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always) { int state, tracing = th->tracing, running = tracing & ev; volatile int raised; volatile int outer_state; VALUE result = Qnil; if (running == ev && !always) { return Qnil; } else { th->tracing |= ev; } raised = rb_threadptr_reset_raised(th); outer_state = th->state; th->state = 0; PUSH_TAG(); if ((state = EXEC_TAG()) == 0) { result = (*func)(arg, running); } if (raised) { rb_threadptr_set_raised(th); } POP_TAG(); th->tracing = tracing; if (state) { JUMP_TAG(state); } th->state = outer_state; return result; } /* (2-2) TracePoint API (not yet) */ /* This function is called from inits.c */ void Init_vm_trace(void) { /* trace */ rb_define_global_function("set_trace_func", set_trace_func, 1); rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1); rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1); }