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

从main.c开始走进Ruby-异常

阅读更多

这一阵子真没时间,9月上旬更没时间,头大.

前天写面试题目的时候遇到了setjmp和longjmp这两个方法,

于是就想到R uby的异常处理是如何实现的,顺道研究下.

其他的Ruby相关的实现现在真没时间写.但肯定要写,因为我喜欢R ,不是一般的喜欢.

 

===============================

 

兵马未动,粮草先行.

 

我想看看raise怎么实现的,但当我在irb中敲入了raise后,我却不知道在gdb中该对哪个方法下断点.

冒然出击肯定是盲目的,就像菲律宾特警解救香港游客人质一样,一点思路都没有.

那好吧,我还是要做点预习功课,翻开代码去找raise对应的C方法.

 

 

通过阅读eval.c的代码,发现里面有如下方法:

 

 

void
Init_eval(void)
{
    rb_define_virtual_variable("$@", errat_getter, errat_setter);
    rb_define_virtual_variable("$!", errinfo_getter, 0);

    rb_define_global_function("raise", rb_f_raise, -1);
    rb_define_global_function("fail", rb_f_raise, -1);
/*
    ..........
*/
}
 

其中raise这个字符串对应的方法是: rb_f_raise,

 

 

static VALUE
rb_f_raise(int argc, VALUE *argv)
{
    VALUE err;
    if (argc == 0) {
	err = get_errinfo();
	if (!NIL_P(err)) {
	    argc = 1;
	    argv = &err;
	}
    }
    rb_raise_jump(rb_make_exception(argc, argv));
    return Qnil;		/* not reached */
}
 

同时我们也可以看到其他方法的底层实现,比如:

 

  • $@是获得出错信息的所在代码行,通过errat_getter实现
  • $!是获得出错信息,通过errinfo_getter实现

我们既然要理解Ruby异常机制,那么我们就要对rb_f_raise进行仔细的分析.

现在我设计这样一个场景:

 

  1. 在irb中通过raise抛出一个异常
  2. 在gdb中对rb_f_raise设置断点
  3. 执行 irb中的raise代码
  4. 在gdb中step进入rb_f_raise及其里面,通过bt观察它的调用栈以及最底层的实现方式.

 

看我如下操作:

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"
ArgumentError: Debug Ruby from main.c
from (irb):15
from /usr/local/ruby-1.9.1/bin/irb:12:in `<main>'

 

gdb 写道
(gdb) b rb_f_raise
Breakpoint 10 at 0x1000289dc: file eval.c, line 467.
(gdb) c
Continuing.
 

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"

 

 

gdb 写道
Breakpoint 10, rb_f_raise (argc=2, argv=0x100400298) at eval.c:467
467 if (argc == 0) {
 

这个时候我们已经断到了rb_f_raise,下面就是要step进入其中,看看一个语言如何设计对异常的处理.

 

我没有把每一步的代码都贴出来,而是走到了最里面,当我遇到以下两个函数的时候,我认为我们就可以大概知道Ruby的异常处理是怎么实现的了.

 

 

int	_setjmp(jmp_buf);
void	_longjmp(jmp_buf, int);
 

深入过C++和Java实现的同学肯定也见过这两个函数,他们的作用就是:

 

  • setjmp:收集当前堆栈信息并且保存到jmp_buf中,返回值是longjmp,如果不是从longjmp返回的,则返回0.
  • longjmp:跳转到jmp_buf设定的位置,并将int返回给setjmp.

下面还有setjmp和longjmp的解释.

 

我出过一道面试题目,就是关于用setjmp.h中的方法实现异常处理的,在这里可以当作一个简单应用的例子,如下:

 

 

#include <setjmp.h>
jmp_buf jb;
int ret = setjmp(jb);
switch(ret)
{
case 0:/*ok*/;break;
case 1:exc1_handler();break;
case 2:exc2_handler();break;
default:default_handler();
}
 
if(a==b){/*do ok*/}
else{/*raise exception*/longjmp(jb,1)}

 

知道了这两个函数,我们就可以开心一下了,原来好多语言都使用它们去实现异常处理阿,只是功能丰富程度不同,但通过了解上面这些,至少我们知道了一个大概的思路,给我们自己的语言设计启发了不少.

 

话题再转回R uby的调试中,在断到longjmp的时候,我使用bt命令查看了一下调用栈,可以比较清楚的看到当在Ruby层面执行raise语句的时候,它是通过调用哪些关键函数来实现异常处理功能的.

 

gdb 写道
#0 rb_sourcefile () at vm.c:754
#1 0x00000001000274cd in rb_longjmp (tag=6, mesg=4304442240) at eval.c:358
#2 0x00000001000277e8 in rb_raise_jump (mesg=<value temporarily unavailable, due to optimizations>) at eval.c:530
#3 0x0000000100028a07 in rb_f_raise (argc=<value temporarily unavailable, due to optimizations>, argv=<value temporarily unavailable, due to optimizations>) at eval.c:474
 

 

 

整个raise的流程差不多就这样,我在对其中个别几个函数再剖析一下:

 

rb_make_exception是在rb_raise_jump的时候调用的,

他的作用就是生成一个异常对象给rb去raise.

现在我们来看一下这个exception的生成过程.

 

 

VALUE
rb_make_exception(int argc, VALUE *argv)
{
    VALUE mesg;
    ID exception;
    int n;

    mesg = Qnil;
    switch (argc) {
      case 0:
	break;
      case 1:
	if (NIL_P(argv[0]))
	    break;
	mesg = rb_check_string_type(argv[0]);
	if (!NIL_P(mesg)) {
	    mesg = rb_exc_new3(rb_eRuntimeError, mesg);
	    break;
	}
	n = 0;
	goto exception_call;

      case 2:
      case 3:
	n = 1;
      exception_call:
	CONST_ID(exception, "exception");
	if (!rb_respond_to(argv[0], exception)) {
	    rb_raise(rb_eTypeError, "exception class/object expected");
	}
	mesg = rb_funcall(argv[0], exception, n, argv[1]);
	break;
      default:
	rb_raise(rb_eArgError, "wrong number of arguments");
	break;
    }
    if (argc > 0) {
	if (!rb_obj_is_kind_of(mesg, rb_eException))
	    rb_raise(rb_eTypeError, "exception object expected");
	if (argc > 2)
	    set_backtrace(mesg, argv[2]);
    }

    return mesg;
}

 

罗列这么多代码的确很不好意思,因为不是每行都需要说明一下,但我又怕只取部分代码片段的话,会有人看不明白.

 

 

mesg = rb_check_string_type(argv[0]);

这里是将错误信息argv转换为String

 

 

mesg = rb_exc_new3(rb_eRuntimeError, mesg);

这里将mesg变成一个异常的对象,而在此之前它还只是个String.

我们看到更改mesg Ruby类型的时候都不需要加强制转换,就是因为这是在C层面,一切R uby对象都是VALUE.

 

rb_exc_new3是通过rb_funcall(etype, rb_intern("new"), 1, str);来实现的.

我们又看到了rb_intern方法,这个方法是把ruby的方法名字转换为id,然后找到该id对应的该函数的C的实现,再执行C的实现.这是C中使用Ruby函数的一个通用思路.

 

 

CONST_ID(exception, "exception");

这个宏定义如下:

 

 

#define CONST_ID_CACHE(result, str)			\
    {							\
	static ID rb_intern_id_cache;			\
	if (!rb_intern_id_cache)			\
	    rb_intern_id_cache = rb_intern2(str, strlen(str));	\
	result rb_intern_id_cache;			\
    }
#define CONST_ID(var, str) \
    do CONST_ID_CACHE(var =, str) while (0)

 其中static类型的rb_intern_id_cache用来保存exception的C实现,这样再次访问这个方法的时候就不用使用rb_intern2去取了.

 

 

贴一下setjmp和longjmp的解释:

 

setjmp longjmp 写道
setjmp|longjmp

  与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。
  为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。
  原理非常简单:
  1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
  2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)
  通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。
 
分享到:
评论
8 楼 wozhidao 2010-09-07  
有些就是只关心术而不关心道
7 楼 CharlesCui 2010-08-30  
jinleileiking 写道
对于这种实际上或许没有什么用。但是是我想做的东西的好文。我就投精华!


谢谢,其实火星的要求也没错,但我的确是没什么实战功底,我不是开发,不常写代码,只是为了兴趣而学习,所以我写的东西都很少有关实战的,因为我本身就没什么实战的经历。

如果有相关实战经历的大牛能多写几篇关于Ruby调试的文章那就可以互相补充了,但千万不要投隐藏贴啊,难道一个论坛篇篇都能精华么?多虚荣,多假啊。一个繁荣文明的肯定是包容的,只有精英的世界是不现实的。

不过谢谢大家的理解和宽容,3x!
6 楼 jinleileiking 2010-08-30  
对于这种实际上或许没有什么用。但是是我想做的东西的好文。我就投精华!
5 楼 CharlesCui 2010-08-29  
googya 写道
喜欢R,还不是一般的喜欢?
我也喜欢R,还喜欢Ruby。。
公司要人么?


我们公司不用R

我朋友公司用R,你可以把简历给我,我帮你投递.站内短信联系好了.
4 楼 googya 2010-08-29  
喜欢R,还不是一般的喜欢?
我也喜欢R,还喜欢Ruby。。
公司要人么?
3 楼 CharlesCui 2010-08-27  
<p>这也能投隐藏贴,你们都是什么心态?<br></p>
<p> </p>
<p><span style="font-family: arial, sans-serif; font-size: 13px;">
</span></p>
<p>3、缝纫机<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/1WAyD.gif" alt="http://jandan.net/"></p>
<p>4、马耳他十字机芯——用于控制时钟的秒针运动<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/mKw1y.gif" alt="http://jandan.net/"></p>
<p>5、汽车变档机制<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/FcwbK.gif" alt="http://jandan.net/"></p>
<p>6、汽车等速万向节<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/PnhN0.gif" alt="http://jandan.net/"></p>
<p>7、舰炮弹--药装填系统<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/swGxT.gif" alt="http://jandan.net/"></p>
<p>8、转子发动机——内燃机的一种,把热能转为旋转运动而非活塞运动<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/CkXvr.gif" alt="http://jandan.net/"><br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/9rndb.gif" alt="http://jandan.net/"></p>
<p> </p>
<p>1、直列式发动机——它的汽缸肩并肩地排成一排<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/3424r.gif" alt="http://jandan.net/"></p>
<p>2、V 型发动机 ——汽缸排列在成一定角度的两个平面上<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/CP243.gif" alt="http://jandan.net/"></p>
<p>3、水平对置式发动机 ——汽缸排列在发动机相对的两个平面上<br><img title="用简单动画来解释复杂原理" src="https://0-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&amp;gadget=a&amp;rewriteMime=image/*&amp;refresh=31536000&amp;url=http://imgur.com/VU1Sc.gif" alt="http://jandan.net/"></p>
<p> </p>
2 楼 CharlesCui 2010-08-26  
下一站,火星 写道
我对这种泛泛而谈的文章没有什么感觉,没有实际应用做背景的东西,太乏味


请教下,什么是实际应用呢?

我写这个文章是分享我对Ruby底层的学习,没有针对个人口味.这篇文章的确没啥用途,不能告诉我们哪些新技术,新发明,但至少让我知道了一个语言层面的try\catch在底层是怎么实现的.我很有收获感.如果这是你需要的,那就对了,如果不是,那的确不合你的口味:(抱歉啊!
1 楼 下一站,火星 2010-08-26  
我对这种泛泛而谈的文章没有什么感觉,没有实际应用做背景的东西,太乏味

相关推荐

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

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

    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 语法分析器(转存...

    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....

    ruby-main.zip

    通过这个“Ruby学习指南”,你可以从基础到高级,全面掌握Ruby编程,包括类的设计、面向对象编程的精髓、元编程的技巧,以及如何利用Ruby的Gem生态进行高效开发。通过实践其中的示例和项目,你将能够深入理解并熟练...

    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 ...

    nats.c-main.zip

    nats.c-main.zip

    Ruby-一个Ruby的例子

    Ruby是一种面向对象的、动态类型的编程语言,以其简洁、优雅的语法和强大的元编程能力而闻名。在这个"Ruby-一个Ruby的例子"中,我们将探讨Ruby的基础知识,以及如何通过具体的代码示例来理解其核心特性。 首先,...

    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``` 在编译时,请点开**编辑配置**窗口,将**工作目录改为本...

    makefile基本写法1

    gcc -c main.c -o main.o fact.o: fact.c gcc -c fact.c -o fact.o main.exe: main.o fact.o gcc main.o fact.o -o main.exe ``` 在这里,`gcc -c`用于编译源文件,生成目标文件;`gcc`用于链接目标文件,生成...

    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...

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

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

    基于Python实现联邦学习框架下基于Memae的异常检测架构

    【作品名称】:基于Python实现联邦学习框架下基于Memae的异常检测架构 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】...

    电子-main.c

    电子-main.c,单片机/嵌入式STM32-F0/F1/F2

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

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

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

    Main是一个Ruby类工厂和领域特定语言(DSL),专门设计用于快速、简洁地生成命令行程序。 "Main"库的核心理念是通过提供一种结构化的方式来定义命令行选项和子命令,使得代码更加清晰、易于维护。它允许开发者用一...

    C语言复习题C语言复习题包含选择填空应用题100多道含解析

    - C程序从main()函数开始执行,直到main()函数结束。 4. C语言语句: - 选项D(p&&=q)不正确,因为C语言中没有逻辑与赋值运算符。 5. C语言关键字: - 选项A中,'define', 'getc', 'include'不是C语言的关键字...

    commons-compress.jar包

    例如,从1.0到1.8的升级可能增加了对更多压缩格式的支持,提高了处理速度,或者解决了之前版本中存在的一些兼容性和稳定性问题。 在Java编程中,`commons-compress.jar` 可以通过以下步骤使用: 1. **添加依赖**:...

    .archivetemp第1-2章 程序设计&C语言&算法.pdf

    - `main()`函数是C程序的入口点,程序从这里开始执行。 - C语言标准没有强制规定`main()`函数的名称必须为`main`,但实际上几乎所有C程序都使用这个名字。 - **算法的概念**: - **算法**定义了一组解决问题的...

    第4章习题参考答案1

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

Global site tag (gtag.js) - Google Analytics