`
CharlesCui
  • 浏览: 430947 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

从main.c开始走进Ruby-登上调试Ruby之旅

阅读更多

我想更深入的了解Ruby内部的实现,出发点或许过于天真,

 

我想了解下这门语言的实现,从中或许可以学习到某些思路,
比如:

  • 如果我们要设计另外一种动态语言该如何去下手,
  • 如何将其他语言的特性融合进Ruby或者我们要设计的语言,
  • 特定领域的特定语言该如何设计(不要一门又广又全的语言,但又不是DSL)。


题目是《从main.c开始走进Ruby》,那我们需要以下的准备工作

  1. Ruby的源码,我采用的是ruby-1.9.2,
  2. gdb(神器)
  3. 一个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解析器能够执行到该断点即可,

我想到以下两种方式:

  1. ruby -e "h=Hash.new"
  2. 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这个函数的作用。

 

先写到这里,下一章我也不知道会写什么,因为前提是我看懂了什么^-^

分享到:
评论
14 楼 rubynroll 2010-09-03  
那个恶心之物出自:http://magickcanoe.com/blog/2006/07/31/frogs-water-bugs-and-turtles-on-kemptville-creek/

13 楼 rainchen 2010-08-23  
rubynroll 写道
这个topic很有趣,加油啊~

喜欢捣鼓gdb和窥探ruby内部的朋友,这位大牛的blog千万不能错过:http://timetobleed.com/


他一篇讲BUG的配图太恶心了
12 楼 CharlesCui 2010-08-20  
ray_linn 写道
在windbg里最妙的是可以查看源代码:



gdb也行阿,list就可以看到当前执行到的上下文,layout更可视化,就是个
vim了.
不过可读性没有带UI的调试器好,不过为了可读性,我建议大家装个IDE么,就是为了看代码,跳转来,跳转去.
11 楼 CharlesCui 2010-08-20  
rubynroll 写道
这个topic很有趣,加油啊~

喜欢捣鼓gdb和窥探ruby内部的朋友,这位大牛的blog千万不能错过:http://timetobleed.com/


好,我去偷艺:)
10 楼 rubynroll 2010-08-19  
这个topic很有趣,加油啊~

喜欢捣鼓gdb和窥探ruby内部的朋友,这位大牛的blog千万不能错过:http://timetobleed.com/
9 楼 ray_linn 2010-08-19  
在windbg里最妙的是可以查看源代码:

8 楼 googya 2010-08-19  
通过调试学习真是个不错的方法。同时也能想到困难重重
7 楼 sandect 2010-08-19  
想法和做法都很好,学习
6 楼 ray_linn 2010-08-18  
用windbg配合pdb文件可能更妙一些
5 楼 RednaxelaFX 2010-08-18  
嗯我只是想说解释器和解析器是不一样的……
加油,这系列有潜力~
光一个new能看到的东西就已经相当多了,包括GC检查剩余可用空间/触发收集等也在流程中:
static void *
vm_xmalloc(rb_objspace_t *objspace, size_t size)
{
    void *mem;

    if ((ssize_t)size < 0) {
        negative_size_allocation_error("negative allocation size (or too big)");
    }
    if (size == 0) size = 1;

#if CALC_EXACT_MALLOC_SIZE
    size += sizeof(size_t);
#endif

    if ((ruby_gc_stress && !ruby_disable_gc_stress) ||
        (malloc_increase+size) > malloc_limit) {
        garbage_collect_with_gvl(objspace);
    }
    mem = malloc(size);                           // 尝试分配空间
    if (!mem) {                                   // 如果没分配到空间的话,
        if (garbage_collect_with_gvl(objspace)) { // 就触发一次收集
            mem = malloc(size);                   // 收集过后再试一次分配空间
        }
        if (!mem) {
            ruby_memerror();
        }
    }
    malloc_increase += size;

#if CALC_EXACT_MALLOC_SIZE
    objspace->malloc_params.allocated_size += size;
    objspace->malloc_params.allocations++;
    ((size_t *)mem)[0] = size;
    mem = (size_t *)mem + 1;
#endif

    return mem;
}
4 楼 night_stalker 2010-08-18  
拜 gdb 熟练工 ……
3 楼 CharlesCui 2010-08-18  
dennis_zane 写道
赞,最好能写成一个系列吧。读ruby hacking guide半途而废,1.9的代码比1.8应该有比较大的变动吧。


fireflyman 写道
強烈建議來個系列...


我也很希望能持续的写下去,但途中肯定会遇到一些问题的,到时候我会把问题抛出来,还请大家不要吝啬各自的才华帮我看看问题,借助大家的帮忙估计还是能写几篇的,希望大家不吝赐教啊!
2 楼 fireflyman 2010-08-18  
強烈建議來個系列...
1 楼 dennis_zane 2010-08-18  
赞,最好能写成一个系列吧。读ruby hacking guide半途而废,1.9的代码比1.8应该有比较大的变动吧。

相关推荐

    嵌入式Linux应用开发完全手册

    | | |-- main.c | | |-- Makefile | | |-- nand.c | | |-- s3c24xx.h | | |-- serial.c | | `-- serial.h | |-- hello | | `-- hello.c | |-- i2c | | |-- head.S | | |-- i2c.c | | |-- i2c.h | | |-- i2c.lds | | ...

    ruby-main.zip

    You'll need a recent (2.6+) version of Ruby, but that's it. Minitest ships with the language, so you're all set. Anatomy of an Exercise The files for an exercise live in exercises/&lt;slug&gt;. The slug ...

    DELPHI皮肤控件BusinessSkinForm以及137套精美皮肤

    2004-07-16 13:16 24036 2415 BusinessSkinForm\Ampix\main.bmp 2004-07-09 21:54 3422 91 BusinessSkinForm\Ampix\mask.bmp 2004-07-16 13:17 8912 1183 BusinessSkinForm\Ampix\menuitems.bmp 2004-07-16 13:17 ...

    编译原理 C语言编译器(包括词法/语法/语义分析器等)

    编译原理 C语言编译器(包括词法/语法/语义分析器等) 项目结构如下 -source --lexAnalysis 词法分析器(原创) ---analyse.c 词法分析器 ---text.c 测试用例(被分析的C代码) --lexSynAnalysis 语法分析器(转存...

    Ruby-一个Ruby的例子

    首先,Ruby的面向对象特性是其核心之一。在Ruby中,一切都是对象,包括基本数据类型如数字、字符串和布尔值。例如,当你在Ruby中写下"hello",它实际上是一个String对象,你可以调用方法在它上面操作,如`"hello"....

    gvim常用插件及其配置文件配置(下载解压即可使用)

    main.c Makefile.multi-target.template print_int_array.c.noindent .vim/c-support/doc: ChangeLog c-hotkeys.pdf c-hotkeys.tex .vim/c-support/rc: customization.ctags customization.gvimrc customization....

    DES加密(封装成C++类)

    【rtf文件用windows的写字板打开较好】 des.rar 2009-11-01 07:47 45056 10080 ...2009-11-08 06:41 822 416 des\main.cpp.rtf 2010-09-18 02:23 文件夹 文件夹 des # # 总计 大小 压缩后大小 文件数 # 532147 196928 7

    uCOS-II源代码下载

    The main directory where all μC/OS-II files are located. \SOFTWARE\uCOS-II\EX1_x86L This directory contains the source code for EXAMPLE #1 (see section 1.07, Example #1) which is intended to run ...

    main-menu.xml

    mysqlWorkbench 汉化

    heroku-buildpack-ruby:Heroku的Ruby Buildpack

    用法Ruby用法示例: $ lsGemfile Gemfile.lock$ heroku create --buildpack heroku/ruby$ git push heroku main...-----&gt; Heroku receiving push-----&gt; Fetching custom buildpack-----&gt; Ruby app detected...

    Ruby-Main一个类工厂和DSL用于快速生成命令行程序

    为了简化这一过程,开发者们创造了各种工具,其中之一就是"Main"库。Main是一个Ruby类工厂和领域特定语言(DSL),专门设计用于快速、简洁地生成命令行程序。 "Main"库的核心理念是通过提供一种结构化的方式来定义...

    C语言跑酷小游戏-NJU-CPL-期末项目.zip,捣蛋猫跑酷,捣蛋猫收集金币,避开障碍,含源码+导出exe+试玩视频等等

    gcc common.c main.c menu.c guide.c game.c -o RUN -lSDL2main -lSDL2 -lSDL2_image -lSDL2_ttf 2. 使用Clion编译 请注意文件夹中的```CMakeLists.txt``` 在编译时,请点开**编辑配置**窗口,将**工作目录改为本...

    c语言程序,调用cmd运行当前目录下名为main.pyw或main.py的文件

    c语言程序,调用cmd运行当前目录下名为main.pyw或main.py的文件

    main.c 哈夫曼编码实现_c语言 求WPL -----递归求解

    哈夫曼编码实现_c语言 (最小堆) 求WPL -----递归求解

    第4章习题参考答案1

    这是因为`m1`中的`main`函数实际上从`0x5589`地址开始执行,这是通常的栈帧布局的一部分,其中`%ebp`寄存器的初始值。 理解这些概念对于编写和调试多文件C程序至关重要,特别是在处理全局变量和函数时,以及在模块...

    BleWinrtDll-main.zip

    《PC蓝牙调试工具——BleWinrtDll-main.zip详解》 在现代的计算机技术中,蓝牙功能已经成为设备间通信的重要方式之一。对于开发者而言,理解并掌握蓝牙的调试工具至关重要,尤其是在开发基于Windows平台的蓝牙应用...

    Ruby-Linters-Capstone

    Ruby-Linters 在这个项目中,我...Linters-Capstone文件夹中$ cd Ruby-Linters-Capstone$ ruby bin/main.rb test.rb用您要检查的任何文件更改test.rb好与坏的例子缩进好例子class Test def initialize(name) @name =

    Ruby-将Ruby变成一个多功能命令行实用程序

    在Ruby编程语言中,创建命令行接口(CLI)工具是一种非常实用的方法,它可以使开发者能够快速构建具有交互功能的工具,方便日常任务自动化或者提供特定服务。Ruby的灵活性和强大的库支持使得构建CLI变得简单易行。...

    Linux操作系统下C 语言编程

    ### Linux操作系统下C语言编程知识点详解 #### 一、基础知识 **1.1 源程序编译** 在Linux环境下,使用C语言编程时最常用的编译器是GNU的`gcc`(GNU Compiler Collection)。`gcc`是一个强大的编译器,能够支持...

Global site tag (gtag.js) - Google Analytics