You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby脚本执行简单IO.popen循环时偶发SIGBUS错误求助

问题描述

我实现了一个类似system()的工具方法用于调用外部程序,核心代码如下:

File.open(args[:out], 'w') { |outfile|
  IO.popen(cmd) { |cmdout|
    while (line = cmdout.gets)
      outfile.puts line
    end
  }
}

用这个方法调用一个最长运行一周的Perl脚本时,Ruby脚本偶尔会触发SIGBUS错误退出,但Perl脚本仍在后台运行。Ruby返回的退出码为135(135-128=7,对应SIGBUS信号)。

目录中生成了Ruby解释器的核心转储文件,回溯信息显示错误并非来自我的业务代码:

Python Exception exceptions.ImportError No module named traceback:                                                       
#0  0x0000155555545d4c in _Unwind_IteratePhdrCallback () at ../.././libgcc/unwind-dw2-fde-dip.c:406                      
#1  0x0000155553fbdb1c in dl_iterate_phdr () from /lib64/libc.so.6                                                       
#2  0x0000155555546ba1 in _Unwind_Find_FDE (pc=0x15553b5bf2e1, bases=bases@entry=0x706578)                               
    at ../.././libgcc/unwind-dw2-fde-dip.c:469              
#3  0x00001555555433b3 in uw_frame_state_for () at ../.././libgcc/unwind-dw2.c:1257                                      
#4  0x00001555555451f9 in _Unwind_Backtrace (trace=0x155553f95aa0 <backtrace_helper>, trace_argument=0x706780)            
    at ../.././libgcc/unwind.inc:302                        
#5  0x0000155553f95c16 in backtrace () from /lib64/libc.so.6                                                             
#6  0x0000155555019677 in rb_print_backtrace () at ../vm_dump.c:755                                                      
#7  rb_vm_bugreport (ctx=0x706b80) at ../vm_dump.c:941      
#8  0x0000155554e6b533 in rb_bug_for_fatal_signal (default_sighandler=0x0, sig=7, ctx=0x706b80,                          
    fmt=0x15555505a5b3 "Bus Error at %p") at ../error.c:658 
#9  0x0000155554f85d3a in sigbus (sig=7, info=0x706cb0, ctx=0x706b80) at ../signal.c:932                                 
#10 <signal handler called>   
#11 0x000015555533b931 in do_lookup_x () from /lib64/ld-linux-x86-64.so.2                                                
#12 0x000015555533c20f in _dl_lookup_symbol_x () from /lib64/ld-linux-x86-64.so.2                                        
#13 0x0000155555340f1e in _dl_fixup () from /lib64/ld-linux-x86-64.so.2                                                  
#14 0x0000155555348bea in _dl_runtime_resolve_xsavec () from /lib64/ld-linux-x86-64.so.2                                 
#15 0x000015553b5bf2e2 in ?? ()                             
#16 0x0000000000000000 in ?? ()                             

想明确三个问题:

  • 出现该SIGBUS错误的原因是什么?
  • Ruby捕获SIGBUS后的行为是什么?
  • 如何查看出错时Ruby代码的执行状态?

问题解答

1. SIGBUS错误的可能原因

从核心转储的回溯信息来看,错误发生在动态链接器(ld-linux-x86-64.so.2)的符号解析过程中,具体是_dl_runtime_resolve_xsavec阶段触发的SIGBUS。常见原因包括:

  • 内存页访问异常:动态链接器尝试访问的内存页不存在、未映射,或者权限不足。可能是系统内存碎片化严重、OOM Killer干预后残留的无效内存引用,或者磁盘上的共享库文件损坏/被篡改。
  • 共享库加载异常:Perl脚本运行过程中可能加载了新的共享库,而Ruby进程在读取相关符号时,由于内存映射问题触发总线错误。
  • 硬件/内核层面问题:底层硬件故障(如内存模块损坏)、内核bug导致的内存映射错误,或者容器/虚拟化环境中的内存隔离异常。
  • Ruby解释器自身bug:特定版本的Ruby在处理子进程输出的长时间循环中,可能存在内存管理的潜在问题,触发了动态链接器的异常。

2. Ruby捕获SIGBUS后的默认行为

  • Ruby对SIGBUS的默认处理是立即终止进程,并生成核心转储文件(如果系统开启了core dump配置)。
  • 由于SIGBUS是致命信号,Ruby不会执行正常的退出清理流程(比如关闭文件句柄、执行ensure块),这就导致子进程(Perl脚本)不会被终止——因为Ruby进程没来得及向子进程发送终止信号,子进程会脱离Ruby的进程组,成为孤儿进程并被init/systemd接管,继续后台运行。
  • 进程退出码为128 + 信号编号,这里就是128+7=135,符合你看到的现象。

3. 如何查看出错时Ruby代码的执行状态

方法一:利用核心转储文件分析

  1. 确保安装了Ruby的调试符号包(比如ruby-debuginfo或对应发行版的调试包)。
  2. 使用gdb加载核心转储和Ruby二进制文件:
    gdb /path/to/ruby /path/to/core
    
  3. 在gdb中执行bt full命令,查看完整的Ruby调用栈(不仅是C层的回溯)。如果调试符号完整,就能看到出错时正在执行的Ruby代码行号和上下文。
  4. 执行info threads查看所有线程状态,确认是否是主线程在处理子进程输出时出错。

方法二:添加运行时日志

在Ruby代码中添加更详细的日志,记录每次读取子进程输出的状态、文件句柄信息,以及系统资源使用情况:

File.open(args[:out], 'w') { |outfile|
  IO.popen(cmd) { |cmdout|
    line_count = 0
    while (line = cmdout.gets)
      line_count += 1
      # 每1000行记录一次状态,避免日志过多
      if line_count % 1000 == 0
        File.open('/tmp/ruby_script_log', 'a') do |log|
          log.puts "#{Time.now}: Processed #{line_count} lines, cmdout fd: #{cmdout.fileno}, outfile fd: #{outfile.fileno}"
          log.puts "#{Time.now}: Memory usage: #{`ps -o rss= -p #{Process.pid}`.strip} KB"
        end
      end
      outfile.puts line
    end
  }
}

这样可以在下次出错前,获取到Ruby进程的运行状态,辅助定位问题。

方法三:自定义信号处理

临时注册SIGBUS的信号处理函数,在错误发生时打印Ruby调用栈:

Signal.trap('BUS') do
  puts "SIGBUS received at #{Time.now}"
  puts "Ruby call stack:"
  caller.each { |line| puts line }
  # 强制生成核心转储后退出
  Process.kill('ABRT', Process.pid)
end

注意:自定义信号处理可能会干扰Ruby的默认错误处理,仅用于调试场景。


内容的提问来源于stack exchange,提问作者David Ljung Madison Stellar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:37:04