从C代码调用Ruby正则表达式时崩溃,如何正确调用避免崩溃?
Ruby C API解析无效正则触发段错误的修复方案
这是此前帖子《Call to ruby regex through C api from C code not working》的续篇。修改代码后,调用rb_reg_regcomp处理正则字符串"*"时触发段错误,gdb捕获的崩溃栈信息如下:
Program received signal SIGSEGV, Segmentation fault. 0x000055555565bef6 in rb_ec_tag_jump (st=st@entry=RUBY_TAG_RAISE, ec=<optimized out>) at ../eval_intern.h:161 161 ec->tag->state = st; (gdb) where #0 0x000055555565bef6 in rb_ec_tag_jump (st=st@entry=RUBY_TAG_RAISE, ec=<optimized out>) at ../eval_intern.h:161 #1 0x0000555555661fe0 in rb_longjmp (ec=ec@entry=0x6160000000d0, tag=tag@entry=6, mesg=<optimized out>, mesg@entry=140737288676920, cause=<optimized out>, cause@entry=36) at ../eval.c:658 #2 0x000055555566231d in rb_exc_exception (mesg=mesg@entry=140737288676920, tag=tag@entry=6, cause=cause@entry=36) at ../vm_core.h:1866 #3 0x0000555555668628 in rb_exc_raise (mesg=mesg@entry=140737288676920) at ../eval.c:684 #4 0x00005555559387a5 in rb_reg_raise_str (err=<optimized out>, options=0, str=140737288677040) at ../re.c:3300 #5 rb_reg_init_str (options=0, s=140737288677040, re=140737288677000) at ../re.c:3300 #6 rb_reg_new_str (options=0, s=140737288677040) at ../re.c:3291 #7 rb_reg_regcomp (str=140737288677040) at ../re.c:3373 #8 0x000055555565aca1 in main () at ../eval.c:856
相关代码如下:
#include <ruby.h> #include "ruby/re.h" #define MAX_INPUT_SIZE 1000 int main(int argc, char** argv) { VALUE x; char string[MAX_INPUT_SIZE]; int result; result = ruby_setup(); ruby_init(); ruby_init_loadpath(); memset(string, 0, MAX_INPUT_SIZE); fgets(string, MAX_INPUT_SIZE, stdin); if (string[MAX_INPUT_SIZE-2]) { return 0; } x = rb_str_new_cstr("*"); rb_reg_regcomp(x); return 0; }
该代码用于模糊测试Ruby正则解析器,需优雅处理无效正则而非崩溃。补充说明:通过源码编译Ruby库,使用commit a8e7fee80129b0ba360c2671582117c8e18a6464;已知"*"是无效正则,但需程序正常处理。
崩溃原因
Ruby解析无效正则时会抛出异常,但当前代码未设置Ruby的异常保护机制。Ruby的C API通过longjmp实现异常抛出,若未提前设置tag保护,异常触发时会跳转到未初始化的上下文,直接引发段错误。
修复方案
使用Ruby C API的rb_protect函数包裹正则编译操作,捕获并处理异常,同时修正Ruby初始化顺序:
#include <ruby.h> #include "ruby/re.h" #define MAX_INPUT_SIZE 1000 // 包装rb_reg_regcomp,适配rb_protect的函数签名 static VALUE regcomp_wrapper(VALUE arg) { return rb_reg_regcomp(arg); } int main(int argc, char** argv) { VALUE x; char string[MAX_INPUT_SIZE]; int setup_result; int exc_state; // 存储异常状态,0表示无异常 // 修正Ruby初始化顺序:先ruby_init,再ruby_setup ruby_init(); setup_result = ruby_setup(); if (setup_result != 0) { return 1; } ruby_init_loadpath(); memset(string, 0, MAX_INPUT_SIZE); fgets(string, MAX_INPUT_SIZE, stdin); if (string[MAX_INPUT_SIZE-2]) { ruby_cleanup(setup_result); return 0; } x = rb_str_new_cstr("*"); // 使用rb_protect捕获正则编译时的异常 VALUE re = rb_protect(regcomp_wrapper, x, &exc_state); if (exc_state != 0) { // 处理异常:获取并打印错误信息 VALUE exc = rb_errinfo(); VALUE err_msg = rb_obj_as_string(exc); fprintf(stderr, "正则编译失败:%s\n", RSTRING_PTR(err_msg)); // 清除错误状态 rb_set_errinfo(Qnil); } else { // 正则编译成功后的逻辑 fprintf(stdout, "正则编译成功\n"); } // 清理Ruby运行时环境 ruby_cleanup(setup_result); return 0; }
关键修改点
- 修正初始化顺序:正确流程是先调用
ruby_init初始化Ruby核心,再调用ruby_setup启动运行时,原代码顺序颠倒可能导致上下文初始化不完整。 - 添加异常保护:
rb_protect会捕获Ruby层面的异常,将状态存入exc_state,避免直接触发段错误。 - 异常处理逻辑:通过
rb_errinfo()获取异常对象,转换为字符串输出错误信息,最后清除错误状态保证程序继续运行。 - 资源清理:退出前调用
ruby_cleanup释放Ruby运行时资源,避免泄漏。
内容的提问来源于stack exchange,提问作者Some nerd who does not have a
相关产品推荐
相关产品推荐

