引き続き、Kernel.#set_trace_func
について見ていきましょう。
目標は、イベントに対してフックする仕組みと、コールバックで呼び出す仕組みを理解することです。
CRubyからみたset_trace_func
使われているデータ構造
rb_event_flag_t
rb_event_flag_t
はそのものずばり、イベントを区別するためのフラグのようです。
/* traditional set_trace_func events */
#define RUBY_EVENT_NONE 0x0000
#define RUBY_EVENT_LINE 0x0001
#define RUBY_EVENT_CLASS 0x0002
#define RUBY_EVENT_END 0x0004
#define RUBY_EVENT_CALL 0x0008
#define RUBY_EVENT_RETURN 0x0010
#define RUBY_EVENT_C_CALL 0x0020
#define RUBY_EVENT_C_RETURN 0x0040
#define RUBY_EVENT_RAISE 0x0080
#define RUBY_EVENT_ALL 0x00ff
/* for TracePoint extended events */
#define RUBY_EVENT_B_CALL 0x0100
#define RUBY_EVENT_B_RETURN 0x0200
#define RUBY_EVENT_THREAD_BEGIN 0x0400
#define RUBY_EVENT_THREAD_END 0x0800
#define RUBY_EVENT_TRACEPOINT_ALL 0xFFFF
/* special events */
#define RUBY_EVENT_SPECIFIED_LINE 0x10000
#define RUBY_EVENT_SWITCH 0x20000
#define RUBY_EVENT_COVERAGE 0x40000
typedef unsigned long rb_event_flag_t;
traditional set_trace_func events がline, call, return, c-call, c-return, class, end, raise
のイベントと対応しています(RUBY_EVENT_NONE,RUBY_EVENT_ALLという特殊なイベントもあるようですね)。
rb_event_hook_flag_t
次にrb_event_hook_flag_t
です。これもフラグです(何の?)。
typedef enum {
RUBY_EVENT_HOOK_FLAG_SAFE = 0x01,
RUBY_EVENT_HOOK_FLAG_DELETED = 0x02,
RUBY_EVENT_HOOK_FLAG_RAW_ARG = 0x04
} rb_event_hook_flag_t;
rb_event_hook_func_t
関数の関連でrb_event_hook_func_t
です。
これこそが、set_trace_func
とイベントでやり取りされているコールバック関数(のためのポインタ)なのです(たぶん)。
typedef void (*rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass);
rb_event_hook_t
rb_event_hook_t
です。
/* (1) trace mechanisms */
typedef struct rb_event_hook_struct {
rb_event_hook_flag_t hook_flags; /* RUBY_EVENT_HOOK_FLAG_SAFEなど */
rb_event_flag_t events; /* RUBY_EVENT_ALLなど */
rb_event_hook_func_t func; /* procを呼び出すときに使う関数 */
VALUE data; /* set_trace_func(proc)のproc */
struct rb_event_hook_struct *next; /* 次の要素 */
} rb_event_hook_t;
trace mechanismsって書いてあります。nextから分かるようにリンクですね。
中身はイベントに関する2つのフラグと、コールバック関数、proc、次へのリンクです。
rb_hook_list_t
rb_hook_list_t
です。
ChangeLogに説明があり
(1) add rb_hook_list_t data structure which includes
hooks, events (flag) and `need_clean' flag.
If the last flag is true, then clean the hooks list.
In other words, deleted hooks are contained by `hooks'.
Cleanup process should run before traversing the list.
何のことでしょう。。。
typedef struct rb_hook_list_struct {
struct rb_event_hook_struct *hooks;
rb_event_flag_t events;
int need_clean;
} rb_hook_list_t;
定義を見てみると、rb_event_hook_struct
があり、ここがリンクのスタートになるようです。
ところでevent_flag_t
があるのですが、これはrb_event_hook_struct
にもあるので、どのような関係なのでしょうか。
イベントにコールバック関数を設定する
set_trace_func
の実装はvm_trace.cにあります。
Init_vm_trace
で、Thread
とglobal
に対してメソッドを定義しています。
/* This function is called from inits.c */
void
Init_vm_trace(void)
{
/* trace_func */
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);
...
もともとのset_trace_func
は
static VALUE
set_trace_func(VALUE obj, VALUE trace)
{
...
rb_remove_event_hook(call_trace_func);
...
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;
}
です。trace
はset_trace_func(proc)
のproc
です。RUBY_EVENT_ALL
なので全てのイベントに対して設定されているようです。
call_trace_func
はさきほどみたrb_event_hook_func_t
の実体になります。
typedef void (*rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass);
static void
call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
{
const char *srcfile = rb_sourcefile();
VALUE eventname = rb_str_new2(get_event_name(event));
VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil;
VALUE argv[6];
int line = rb_sourceline();
rb_thread_t *th = GET_THREAD();
if (!klass) {
rb_thread_method_id_and_class(th, &id, &klass);
}
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] = (self && srcfile) ? rb_binding_new() : Qnil;
argv[5] = klass ? klass : Qnil;
rb_proc_call_with_block(proc, 6, argv, Qnil);
}
argv
にevent, file, line, id, binding, klassを詰め込んで、procをcallしていることから、これが最終的にprocを呼び出していると思います。
set_trace_func
からの呼び出しをまとめてみると
- static VALUE
set_trace_func(VALUE obj, VALUE trace)
-- void
rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
--- static rb_event_hook_t *
alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags)
--- static void
connect_event_hook(rb_hook_list_t *list, rb_event_hook_t *hook)
となります。
alloc_event_hook
はrb_event_hook_t
の分のメモリを確保して、引数であるfunc, events, data, hook_flags
をrb_event_hook_t
のメンバにセットして返します。
connect_event_hook
はhook
をlist->hooks
の先頭に挿入します。
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, RUBY_EVENT_HOOK_FLAG_SAFE);
connect_event_hook(&GET_VM()->event_hooks, hook);
}
実装はこのようになっており、VMが管理しているevent_hooksに対して新しいhookを挿入しています。
GET_VM()
は
#define GET_VM() ruby_current_vm
のことで、
rb_vm_t *ruby_current_vm = 0;
となっています。ruby_current_vm
の初期化はvm.c
のInit_VM
やInit_BareVM
で行われています。
このあたりの処理はあとでみるとして、今は構造体をみてみましょう。
typedef struct rb_vm_struct {
VALUE self;
rb_global_vm_lock_t gvl;
rb_thread_lock_t thread_destruct_lock;
struct rb_thread_struct *main_thread;
struct rb_thread_struct *running_thread;
st_table *living_threads;
VALUE thgroup_default;
int running;
int thread_abort_on_exception;
int trace_running;
volatile int sleeper;
/* object management */
VALUE mark_object_ary;
VALUE special_exceptions[ruby_special_error_count];
/* load */
VALUE top_self;
VALUE load_path;
VALUE load_path_snapshot;
VALUE load_path_check_cache;
VALUE expanded_load_path;
VALUE loaded_features;
VALUE loaded_features_snapshot;
struct st_table *loaded_features_index;
struct st_table *loading_table;
/* signal */
struct {
VALUE cmd;
int safe;
} trap_list[RUBY_NSIG];
/* hook */
rb_hook_list_t event_hooks;
int src_encoding_index;
VALUE verbose, debug, progname;
VALUE coverages;
struct unlinked_method_entry_list_entry *unlinked_method_entry_list;
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
struct rb_objspace *objspace;
#endif
/*
* @shyouhei notes that this is not for storing normal Ruby
* objects so do *NOT* mark this when you GC.
*/
struct RArray at_exit;
VALUE *defined_strings;
/* params */
struct { /* size in byte */
size_t thread_vm_stack_size;
size_t thread_machine_stack_size;
size_t fiber_vm_stack_size;
size_t fiber_machine_stack_size;
} default_params;
} rb_vm_t;
このように
VM->event_hooks->hooks->next->next ...
rb_vm_struct->rb_hook_list_struct->rb_event_hook_struct->rb_event_hook_struct ...
という形でprocを管理しています。