diff options
author | ko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-08-15 04:39:10 +0000 |
---|---|---|
committer | ko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-08-15 04:39:10 +0000 |
commit | 8eb93103e4651aa9d6ed147208f6d90b211e9bfa (patch) | |
tree | 7c7b466abd35440994a94979182a19ad01a44fcc /vm_trace.c | |
parent | d2e9b54564fedd9ee4fff18c2a7f1df2d6dcdd93 (diff) | |
download | ruby-8eb93103e4651aa9d6ed147208f6d90b211e9bfa.tar.gz |
* vm_trace.c: separate trace_func related functions from
thread.c.
* thread.c: ditto.
* common.mk: add vm_trace.o.
* inits.c: call Init_vm_trace().
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36703 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'vm_trace.c')
-rw-r--r-- | vm_trace.c | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/vm_trace.c b/vm_trace.c new file mode 100644 index 0000000000..f608e1b2c0 --- /dev/null +++ b/vm_trace.c @@ -0,0 +1,596 @@ +/********************************************************************** + + 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: <code>c-call</code> + * (call a C-language routine), <code>c-return</code> (return from a + * C-language routine), <code>call</code> (call a Ruby method), + * <code>class</code> (start a class or module definition), + * <code>end</code> (finish a class or module definition), + * <code>line</code> (execute code on a new line), <code>raise</code> + * (raise an exception), and <code>return</code> (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 <code>Thread#set_trace_func</code> 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); +} + |