我想更深入的了解Ruby内部的实现,出发点或许过于天真,
我想了解下这门语言的实现,从中或许可以学习到某些思路,
比如:
- 如果我们要设计另外一种动态语言该如何去下手,
- 如何将其他语言的特性融合进Ruby或者我们要设计的语言,
- 特定领域的特定语言该如何设计(不要一门又广又全的语言,但又不是DSL)。
题目是《从main.c开始走进Ruby》,那我们需要以下的准备工作
- Ruby的源码,我采用的是ruby-1.9.2,
- gdb(神器)
- 一个IDE,eclipse(可选)
得到源码之后,首先要对其进行编译,不能采用默认的编译方式,
要在执行./configure的时候加上如下参数
- CFLAGS="-ggdb -g3 -gdwarf-2"
或者在Makefile中添加如下标志参数:
- optflags = -O
- debugflags = -ggdb -g3 -gdwarf-2
这样编译的Ruby包含了充足的调试信息,同时还保留了Ruby源码中大量使用的宏信息,
比如我们在gdb中显示如下的宏:
(gdb) info macro RTEST Defined at ./include/ruby/ruby.h:349 included at /home/zheng.cuizh/ruby-1.9.2-rc2/ruby.c:18 #define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)
如果编译参数没有添加,那么编译出来的代码是没办法看到macro信息的。
在编译好Ruby后,需要使用make install安装,安装好后就可以登上调试Ruby之旅了。
调试Ruby有几种方法,只要能在你知道的地方下个断点,并且Ruby解析器能够执行到该断点即可,
我想到以下两种方式:
- ruby -e "h=Hash.new"
- irb
对于第一种方式,我们直接使用gdb ruby启动即可,然后在gdb的console中设置属性及断点:
- gdb ruby
- set args -e "h=Hash.new"
- b main
对于第二种方式,我们先执行irb,然后再开启一个终端,通过ps ax|grep irb|grep -v grep|awk '{print $1}' 找到irb的进程id,然后用gdb attach到该进程即可:
- irb
- gdb - <irb_pid>
- b rb_hash_initialize
- c
- h=Hash.new#irb中输入
这样的话就会在初始化Hash对象的时候被断到,然后就可以继续调试了。
个人推荐采用第二种方式来调试Ruby解析器。
这里我要多说一下,第一种方式的最后一步b main的意思是对main()这个位于main.c中的入口函数。
(gdb) list main
18
19 RUBY_GLOBAL_SETUP
20
21 int
22 main(int argc, char **argv)
23 {
24 #省略
30
31 ruby_sysinit(&argc, &argv);
32 {
33 RUBY_INIT_STACK;
34 ruby_init();
35 return ruby_run_node(ruby_options(argc, argv));
36 }
37 }
(gdb)
Line number 38 out of range; main.c has 37 lines.
Ruby是一种解释型语言,它的代码都是没编译过的字符串,
- 在Ruby解析器执行起来后,
vm_exec(th);
- 通过读入Ruby代码文件,动态解析
PREPARE_PARSE_MAIN({
tree = load_file(parser, opt->script, 1, opt);
});
从而完成边解析边执行的过程。第一种方法的main.c函数就是ruby解析器的入口,当我们在那里下断点后,必定能将Ruby断下来。
在main.c35行这句话:return ruby_run_node(ruby_options(argc, argv));
我们需要在gdb里面通过键入s跟进两次,ruby_run_node和ruby_options这两个函数都需要步入,他们都很重要。其中ruby_run_node会调用vm_exec来执行语法树。
至此,我们的准备工作就完成了。很简单的步骤,却很重要。
之后的工作就是不断的跟踪Ruby各种类型的实现机制,简单的描述就是在irb里面不停尝试各种类型的new操作,同时在gdb里面不断的对不同类型rb_object_initialize下断点,然后步入每一个想知道的方法中,对某些变量设置观察点,打印出结构体,再步进。
真感谢Ruby有个irb这么好用的工具,也是由于动态语言的缘故,代码可以通过eval来执行。
有了irb,我们就可以交互式调试没段代码了。
这是走进Ruby的开始,我也刚刚做好准备工作,很多Ruby内部的实现我还没有读懂,我边读边写,不过我要在我读懂了之后再写出来,由于悟性的问题,我可能写的很慢或者有错误的地方,而且思路可能还会很跳跃^-^,希望高手能指点一下。
Ruby有好多预定以变量,多的让我不可能记住,这里有个网页,记录了和Ruby有关的一些关键字:
http://www.tutorialspoint.com/ruby/ruby_predefined_constants.htm
我们在使用Ruby的argv参数数组时,知道$0是当前进程的程序名称,
比如我们在启动的irb中执行$0,就会返回"irb",
这个$0是在这里被保存的:
rb_argv0 = rb_str_new4(rb_progname);
rb_progname是什么,debug跟踪进去可以看到如下定义:
#define rb_progname (GET_VM()->progname) #define GET_VM() ruby_current_vm
这俩段宏可以得到当前Ruby解析器的名字,这个名字是保存在下面这个结构体中的,
下面这个是ruby vm_core.h中的解析器结构体,
typedef struct rb_vm_struct { VALUE self; rb_thread_lock_t global_vm_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; unsigned long trace_flag; volatile int sleeper; /* object management */ VALUE mark_object_ary; VALUE special_exceptions[ruby_special_error_count]; /* load */ VALUE top_self; VALUE load_path; VALUE loaded_features; struct st_table *loading_table; /* signal */ struct { VALUE cmd; int safe; } trap_list[RUBY_NSIG]; /* hook */ rb_event_hook_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 } rb_vm_t;
和python等其他脚本语言类似,脚本语言无法真实的实现native thread来做并行运算,可以通过下面这个成员看出端倪:
全局解释器锁
rb_thread_lock_t global_vm_lock;
在调试过程中要善用bt这个命令,通过bt我们可以看到某个函数的调用栈,配合eclipse等IDE(没有IDE的话就用layout查看代码),可以很好的分析出代码的走向,一个实例创建的过程。
比如在以irb方式调试的时候,刚刚断入irb的进程,执行bt后看如下输出:
(gdb) bt
#0 0x000000304360d5cb in read () from /lib64/libpthread.so.0
#1 0x0000000000527803 in rb_thread_blocking_region (func=0x42b0e0 <internal_read_func>, data1=0x7fff42460860, ubf=0x527960 <ubf_select>, data2=0xb58bf0) at thread.c:1117
#2 0x000000000042bae8 in io_fillbuf (fptr=0xe88a40) at io.c:587
#3 0x0000000000437004 in rb_io_getbyte (io=<value optimized out>) at io.c:3101
#4 0x0000000000517ee5 in vm_call0 (th=0xb58bf0, recv=15222400, id=1928, argc=0, argv=0x0, me=0xc28600) at vm_eval.c:78
#5 0x000000000051b27b in rb_funcall (recv=15222400, mid=0, n=0) at vm_eval.c:234
#6 0x00002aaaaaeb29bd in readline_getc (input=0x3042d516a0) at readline.c:120
#7 0x0000003043e25eba in rl_read_key () from /usr/lib64/libreadline.so.5
#8 0x0000003043e14921 in readline_internal_char () from /usr/lib64/libreadline.so.5
#9 0x0000003043e14d65 in readline () from /usr/lib64/libreadline.so.5
#10 0x000000000041773d in rb_protect (proc=0x2aaaaaeb2940 <readline_get>, data=15701216, state=0x7fff42460e6c) at eval.c:718
#11 0x00002aaaaaeb28ae in readline_readline (argc=2, argv=<value optimized out>, self=<value optimized out>) at readline.c:255
#12 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e16c8, num=2, blockptr=0x1, flag=8, id=0, me=0xe217b0, recv=15222560) at vm_insnhelper.c:401
#13 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#14 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#15 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0xe9d9c0, self=15225000, argc=0, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#16 0x000000000051c77f in rb_vm_invoke_proc (th=0xb58bf0, proc=0xe9d9c0, self=15225000, argc=0, argv=0x2af8e52e2250, blockptr=0x0) at vm.c:603
#17 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e18d8, num=0, blockptr=0x1, flag=0, id=0, me=0xc52000, recv=15307040) at vm_insnhelper.c:401
#18 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#19 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#20 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1c18, self=15213640, argc=0, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#21 0x000000000051c888 in loop_i () at vm.c:587
#22 0x00000000004179a9 in rb_rescue2 (b_proc=0x51c850 <loop_i>, data1=0, r_proc=0, data2=0) at eval.c:646
#23 0x00000000005092e9 in rb_f_loop (self=15213640) at vm_eval.c:816
#24 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1bf0, num=0, blockptr=0x2af8e53e1c19, flag=8, id=0, me=0xbe21a0, recv=15213640) at vm_insnhelper.c:401
#25 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#26 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#27 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1d20, self=15213640, argc=1, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#28 0x000000000051c82e in catch_i (tag=4080910, data=<value optimized out>) at vm.c:587
#29 0x00000000005084fd in rb_catch_obj (tag=4080910, func=0x51c7f0 <catch_i>, data=0) at vm_eval.c:1522
#30 0x0000000000509198 in rb_f_catch (argc=<value optimized out>, argv=<value optimized out>) at vm_eval.c:1498
#31 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1cf8, num=1, blockptr=0x2af8e53e1d21, flag=8, id=0, me=0xbe1e20, recv=15213640) at vm_insnhelper.c:401
#32 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#33 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#34 0x000000000051c3c8 in invoke_block_from_c (th=0xb58bf0, block=0x2af8e53e1ed8, self=12129600, argc=1, argv=0x0, blockptr=0x0, cref=0x0) at vm.c:557
#35 0x000000000051c82e in catch_i (tag=3247374, data=<value optimized out>) at vm.c:587
#36 0x00000000005084fd in rb_catch_obj (tag=3247374, func=0x51c7f0 <catch_i>, data=0) at vm_eval.c:1522
#37 0x0000000000509198 in rb_f_catch (argc=<value optimized out>, argv=<value optimized out>) at vm_eval.c:1498
---Type <return> to continue, or q <return> to quit---
#38 0x000000000050cc95 in vm_call_method (th=0xb58bf0, cfp=0x2af8e53e1eb0, num=1, blockptr=0x2af8e53e1ed9, flag=8, id=0, me=0xbe1e20, recv=12129600) at vm_insnhelper.c:401
#39 0x00000000005103b7 in vm_exec_core (th=0xb58bf0, initial=<value optimized out>) at insns.def:1006
#40 0x0000000000516c99 in vm_exec (th=0x0) at vm.c:1145
#41 0x0000000000516f74 in rb_iseq_eval_main (iseqval=12097560) at vm.c:1386
#42 0x0000000000417bcf in ruby_exec_internal (n=0xb89818) at eval.c:214
#43 0x000000000041a396 in ruby_run_node (n=<value optimized out>) at eval.c:261
#44 0x0000000000416e5d in main (argc=2, argv=0x7fff424632c8) at main.c:35
第一行read函数就是irb和使用者交互的方法,
最后一行的main就是Ruby解析器的入口函数,
从下到上就是Ruby解析器整个执行过程。
不过我还没弄明白loop_i这个函数的作用。
先写到这里,下一章我也不知道会写什么,因为前提是我看懂了什么^-^