From: Brian Candler Date: 2004-10-16T00:20:29+09:00 Subject: [BUG] segfault in ruby-1.8.2p2 --lrZ03NoBR/3+SXJZ Content-Type: text/plain; charset=us-ascii Content-Disposition: inline I can reliably get ruby-1.8.2p2 to segfault on my system, which is: $ uname -a FreeBSD vaio.linnet.org 4.10-STABLE FreeBSD 4.10-STABLE #0: Sun Sep 26 17:08:21 BST 2004 root@localhost:/usr/obj/usr/src/sys/VAIO i386 $ ruby -v ruby 1.8.2 (2004-07-29) [i386-freebsd4] The steps which I use to replicate this are as follows. Put the attached four Ruby scripts into the current directory. $ ruby test.rb & [1] 72319 $ ruby breakpoint-client.rb Executing break point "Person#initialize" at test.rb:7 in `initialize' irb(#):001:0> exit Executing break point "Person#name" at test.rb:13 in `name' irb(#):001:0> name At this point, the system hangs. Now press Ctrl-C ^CIRB::Abort: abort then interrupt!! << There is a 3-second delay>> ... long backtrace snipped ... from /usr/local/lib/ruby/1.8/drb/drb.rb:1324:in `run' from /usr/local/lib/ruby/1.8/drb/drb.rb:1249:in `initialize' from /usr/local/lib/ruby/1.8/drb/drb.rb:1505:in `new' from /usr/local/lib/ruby/1.8/drb/drb.rb:1505:in `start_service' from breakpoint-client.rb:16irb(#):002:0> Now type "exit" and hit Enter. I get: Name: Random Person ./breakpoint.rb:148: [BUG] Segmentation fault ruby 1.8.2 (2004-07-29) [i386-freebsd4] No connection to breakpoint service at druby://localhost:42531: (connection closed) Reconnecting in 10 seconds... At this point, I try to get out using Ctrl-C, but it doesn't work, so I use Ctrl-Z to suspend, and then kill %2 to kill it. ^Z[1] Abort trap (core dumped) ruby test.rb [2]+ Stopped ruby breakpoint-client.rb $ kill %2 [2]+ Terminated ruby breakpoint-client.rb $ ls -l ruby.core -rw------- 1 brian brian 2744320 Oct 15 16:11 ruby.core $ gdb -c ruby.core ruby GNU gdb 4.18 (FreeBSD) Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-unknown-freebsd"... (no debugging symbols found)... Core was generated by `ruby'. Program terminated with signal 6, Abort trap. Reading symbols from /usr/local/lib/libruby18.so.18... (no debugging symbols found)...done. Reading symbols from /usr/lib/libcrypt.so.2...(no debugging symbols found)... done. Reading symbols from /usr/lib/libm.so.2...(no debugging symbols found)...done. Reading symbols from /usr/lib/libc.so.4...(no debugging symbols found)...done. Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/readline.so... (no debugging symbols found)...done. Reading symbols from /usr/lib/libreadline.so.4... (no debugging symbols found)...done. Reading symbols from /usr/lib/libncurses.so.5...(no debugging symbols found)... done. Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/socket.so... (no debugging symbols found)...done. Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/fcntl.so... (no debugging symbols found)...done. Reading symbols from /usr/libexec/ld-elf.so.1...(no debugging symbols found)... done. #0 0x28190084 in kill () from /usr/lib/libc.so.4 (gdb) bt #0 0x28190084 in kill () from /usr/lib/libc.so.4 #1 0x281d197a in abort () from /usr/lib/libc.so.4 #2 0x280827d1 in rb_bug () from /usr/local/lib/libruby18.so.18 #3 0x280e6e82 in sigsegv () from /usr/local/lib/libruby18.so.18 #4 0xbfbfffac in ?? () #5 0x2809127f in eval () from /usr/local/lib/libruby18.so.18 #6 0x2809166e in rb_f_eval () from /usr/local/lib/libruby18.so.18 #7 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18 #8 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #9 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18 #10 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #11 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #12 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18 #13 0x2808b54e in rb_eval () from /usr/local/lib/libruby18.so.18 #14 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #15 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #16 0x28090778 in rb_f_send () from /usr/local/lib/libruby18.so.18 #17 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18 #18 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #19 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #20 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #21 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #22 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18 #23 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18 #24 0x28089fd6 in rb_eval () from /usr/local/lib/libruby18.so.18 #25 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #26 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #27 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #28 0x28089de1 in rb_eval () from /usr/local/lib/libruby18.so.18 ---Type to continue, or q to quit--- #29 0x2808b504 in rb_eval () from /usr/local/lib/libruby18.so.18 #30 0x2808a11c in rb_eval () from /usr/local/lib/libruby18.so.18 #31 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18 #32 0x2808e11e in rb_f_loop () from /usr/local/lib/libruby18.so.18 #33 0x2808fb51 in rb_call0 () from /usr/local/lib/libruby18.so.18 #34 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #35 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18 #36 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18 #37 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18 #38 0x2809a129 in rb_thread_yield () from /usr/local/lib/libruby18.so.18 #39 0x28099e47 in rb_thread_start_0 () from /usr/local/lib/libruby18.so.18 #40 0x2809a252 in rb_thread_start () from /usr/local/lib/libruby18.so.18 #41 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18 #42 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #43 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #44 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18 #45 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #46 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #47 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18 #48 0x28089666 in rb_eval () from /usr/local/lib/libruby18.so.18 #49 0x2808a11c in rb_eval () from /usr/local/lib/libruby18.so.18 #50 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18 #51 0x2809a129 in rb_thread_yield () from /usr/local/lib/libruby18.so.18 #52 0x28099e47 in rb_thread_start_0 () from /usr/local/lib/libruby18.so.18 #53 0x2809a252 in rb_thread_start () from /usr/local/lib/libruby18.so.18 #54 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18 #55 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #56 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #57 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18 ---Type to continue, or q to quit--- #58 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #59 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #60 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18 #61 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18 #62 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #63 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #64 0x280908d5 in rb_funcall2 () from /usr/local/lib/libruby18.so.18 #65 0x2809309c in rb_obj_call_init () from /usr/local/lib/libruby18.so.18 #66 0x280b850b in rb_class_new_instance () from /usr/local/lib/libruby18.so.18 #67 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18 #68 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #69 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #70 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18 #71 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #72 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #73 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #74 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18 #75 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18 #76 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18 #77 0x28086168 in eval_node () from /usr/local/lib/libruby18.so.18 #78 0x280866fe in ruby_exec () from /usr/local/lib/libruby18.so.18 #79 0x28086771 in ruby_run () from /usr/local/lib/libruby18.so.18 #80 0x80485df in main () #81 0x8048516 in _start () (gdb) q I hope you can dig something useful out of that! Regards, Brian. --lrZ03NoBR/3+SXJZ Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="binding_of_caller.rb" begin require 'simplecc' rescue LoadError def Continuation.create(*args, &block) cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} result ||= args return *[cc, *result] end end # This method returns the binding of the method that called your # method. It will raise an Exception when you're not inside a method. # # It's used like this: # def inc_counter(amount = 1) # Binding.of_caller do |binding| # # Create a lambda that will increase the variable 'counter' # # in the caller of this method when called. # inc = eval("lambda { |arg| counter += arg }", binding) # # We can refer to amount from inside this block safely. # inc.call(amount) # end # # No other statements can go here. Put them inside the block. # end # counter = 0 # 2.times { inc_counter } # counter # => 2 # # Binding.of_caller must be the last statement in the method. # This means that you will have to put everything you want to # do after the call to Binding.of_caller into the block of it. # This should be no problem however, because Ruby has closures. # If you don't do this an Exception will be raised. Because of # the way that Binding.of_caller is implemented it has to be # done this way. def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error, extra_data = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, context, extra_data = args[0], args[4], args if type == "return" count += 1 # First this method and then calling one will return -- # the trace event of the second event gets the context # of the method which called the method that called this # method. if count == 2 # It would be nice if we could restore the trace_func # that was set before we swapped in our own one, but # this is impossible without overloading set_trace_func # in current Ruby. set_trace_func(nil) cc.call(eval("binding", context), nil, extra_data) end elsif type != "line" set_trace_func(nil) error_msg = "Binding.of_caller used in non-method context or " + "trailing statements of method using it aren't in the block." cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical case block.arity when 1 then yield(result) else yield(result, extra_data) end end end --lrZ03NoBR/3+SXJZ Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="breakpoint-client.rb" require 'breakpoint' if ARGV[0] == "--help" then puts "This tool lets you connect to a breakpoint service ", "which was started via Breakpoint.activate_drb.", "", "Usage: ruby #{$0} [drb uri]", "", "The drb uri defaults to druby://localhost:42531" exit end uri = ARGV[0] || 'druby://localhost:42531' DRb.start_service begin service = DRbObject.new(nil, uri) loop do begin service.handle_breakpoint do |(workspace, message)| puts message IRB.start(nil, nil, workspace) end rescue Breakpoint::DRbService::FinishedException end end rescue Exception => error puts "No connection to breakpoint service at #{uri}:", " (#{error})", " Reconnecting in 10 seconds..." sleep 10 retry end --lrZ03NoBR/3+SXJZ Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="breakpoint.rb" require 'irb' require 'binding_of_caller' require 'drb' module Breakpoint extend self # This will pop up an interactive ruby session at a # pre-defined break point in a Ruby application. In # this session you can examine the environment of # the break point. # # You can get a list of variables in the context using # local_variables via +local_variables+. You can then # examine their values by typing their names. # # You can have a look at the call stack via +caller+. # # breakpoints can also return a value. They will execute # a supplied block for getting a default return value. # A custom value can be returned from the session by doing # +throw(:debug_return, value)+. # # You can also give names to break points which will be # used in the message that is displayed upon execution # of them. # # Here's a sample of how breakpoints should be placed: # # class Person # def initialize(name, age) # @name, @age = name, age # breakpoint("Person#initialize") # end # # attr_reader :age # def name # breakpoint("Person#name") { @name } # end # end # # person = Person.new("Random Person", 23) # puts "Name: #{person.name}" # # And here is a sample debug session: # # Executing break point "Person#initialize" at file.rb:4 in `initialize' # irb(#):001:0> local_variables # => ["name", "age", "_", "__"] # irb(#):002:0> [name, age] # => ["Random Person", 23] # irb(#):003:0> [@name, @age] # => ["Random Person", 23] # irb(#):004:0> self # => # # irb(#):005:0> @age += 1; self # => # # irb(#):006:0> exit # Executing break point "Person#name" at file.rb:9 in `name' # irb(#):001:0> throw(:debug_return, "Overriden name") # Name: Overriden name def breakpoint(id = nil, context = nil, &block) callstack = caller callstack.slice!(0, 3) if callstack.first["breakpoint"] file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures message = "Executing break point " + (id ? "#{id.inspect} " : "") + "at #{file}:#{line}" + (method ? " in `#{method}'" : "") if context then return handle_breakpoint(context, message, &block) end Binding.of_caller do |binding_context| handle_breakpoint(binding_context, message, &block) end end def handle_breakpoint(context, message, &block) # :nodoc: catch(:debug_return) do |value| if not use_drb? then puts message IRB.start(nil, IRB::WorkSpace.new(context)) else @drb_service.add_breakpoint(context, message) end block.call if block end end private :handle_breakpoint # This asserts that the block evaluates to true. # If it doesn't evaluate to true a breakpoint will # automatically be created at that execution point. # # You can disable assert checking by setting # Breakpoint.optimize_asserts to true before # loading the breakpoint.rb library. (It will still # be enabled when Ruby is run via the -d argument.) # # Example: # person_name = "Foobar" # assert { not person_name.nil? } def assert(context = nil, &condition) return if Breakpoint.optimize_asserts and not $DEBUG return if yield callstack = caller callstack.slice!(0, 3) if callstack.first["assert"] file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}. " + "Executing implicit breakpoint." if context then return handle_breakpoint(context, message) end Binding.of_caller do |context| handle_breakpoint(context, message) end end # Whether asserts should be ignored if not in debug mode. # Debug mode can be enabled by running ruby with the -d # switch or by setting $DEBUG to true. attr_accessor :optimize_asserts self.optimize_asserts = false class DRbService class FinishedException < Exception; end include DRbUndumped def initialize @breakpoint = @result = nil @has_breakpoint = @is_done = false end def add_breakpoint(context, message) workspace = IRB::WorkSpace.new(context) workspace.extend(DRbUndumped) @breakpoint = [workspace, message] @has_breakpoint = true until @is_done; end @is_done = false end def handle_breakpoint(&block) until @has_breakpoint; end @has_breakpoint = false #begin yield(@breakpoint) #rescue FinishedException #end @is_done = true end end # Will run Breakpoint in DRb mode. This will spawn a server # that can be attached to via the [ TODO: command ] command # whenever a breakpoint is executed. This is useful when you # are debugging CGI applications or other applications where # you can't access debug sessions via the standard input and # output of your application. # # You can specify an URI where the DRb server will run at. # This way you can specify the port the server runs on. The # default URI is druby://localhost:42531. # # Please note that breakpoints will be skipped silently in # case the DRb server can not spawned. (This can happen if # the port is already used by another instance of your # application on CGI or another application.) def activate_drb(uri = 'druby://localhost:42531') @use_drb = true @drb_service = DRbService.new DRb.start_service(uri, @drb_service) end def use_drb? @use_drb == true end end module IRB def IRB.start(ap_path = nil, main_context = nil, workspace = nil) $0 = File::basename(ap_path, ".rb") if ap_path # suppress some warnings about redefined constants old_verbose, $VERBOSE = $VERBOSE, nil IRB.setup(ap_path) $VERBOSE = old_verbose if @CONF[:SCRIPT] then irb = Irb.new(main_context, @CONF[:SCRIPT]) else irb = Irb.new(main_context) end if workspace then irb.context.workspace = workspace end @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @CONF[:MAIN_CONTEXT] = irb.context trap("SIGINT") do irb.signal_handle end catch(:IRB_EXIT) do irb.eval_input end end class << self; alias :old_CurrentContext :CurrentContext; end def IRB.CurrentContext if old_CurrentContext.nil? and Breakpoint.use_drb? then result = Object.new def result.last_value; end return result else old_CurrentContext end end class Context alias :old_evaluate :evaluate def evaluate(line, line_no) if line.chomp == "exit" then exit else old_evaluate(line, line_no) end end end class WorkSpace alias :old_evaluate :evaluate def evaluate(*args) if Breakpoint.use_drb? then result = old_evaluate(*args) result.extend(DRbUndumped) rescue nil return result else old_evaluate(*args) end end end end class DRb::DRbObject undef :inspect end def breakpoint(id = nil, &block) Binding.of_caller do |context| Breakpoint.breakpoint(id, context, &block) end end def assert(&block) Binding.of_caller do |context| Breakpoint.assert(context, &block) end end --lrZ03NoBR/3+SXJZ Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="test.rb" require 'breakpoint' Breakpoint.activate_drb class Person def initialize(name, age) @name, @age = name, age breakpoint("Person#initialize") end attr_reader :age def name x = 8 breakpoint("Person#name") @name end end person = Person.new("Random Person", 23) puts "Name: #{person.name}" --lrZ03NoBR/3+SXJZ--