summaryrefslogtreecommitdiff
path: root/lib/debug.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/debug.rb')
-rw-r--r--lib/debug.rb1730
1 files changed, 947 insertions, 783 deletions
diff --git a/lib/debug.rb b/lib/debug.rb
index 9ae119f8fb..34d7d27406 100644
--- a/lib/debug.rb
+++ b/lib/debug.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
# Copyright (C) 2000 Information-technology Promotion Agency, Japan
# Copyright (C) 2000-2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org>
+require 'continuation'
+
if $SAFE > 0
STDERR.print "-r debug.rb is not available in safe mode\n"
exit 1
@@ -10,541 +13,663 @@ end
require 'tracer'
require 'pp'
-class Tracer
+class Tracer # :nodoc:
def Tracer.trace_func(*vars)
Single.trace_func(*vars)
end
end
-SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
+SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ # :nodoc:
+
+##
+# This library provides debugging functionality to Ruby.
+#
+# To add a debugger to your code, start by requiring +debug+ in your
+# program:
+#
+# def say(word)
+# require 'debug'
+# puts word
+# end
+#
+# This will cause Ruby to interrupt execution and show a prompt when the +say+
+# method is run.
+#
+# Once you're inside the prompt, you can start debugging your program.
+#
+# (rdb:1) p word
+# "hello"
+#
+# == Getting help
+#
+# You can get help at any time by pressing +h+.
+#
+# (rdb:1) h
+# Debugger help v.-0.002b
+# Commands
+# b[reak] [file:|class:]<line|method>
+# b[reak] [class.]<line|method>
+# set breakpoint to some position
+# wat[ch] <expression> set watchpoint to some expression
+# cat[ch] (<exception>|off) set catchpoint to an exception
+# b[reak] list breakpoints
+# cat[ch] show catchpoint
+# del[ete][ nnn] delete some or all breakpoints
+# disp[lay] <expression> add expression into display expression list
+# undisp[lay][ nnn] delete one particular or all display expressions
+# c[ont] run until program ends or hit breakpoint
+# s[tep][ nnn] step (into methods) one line or till line nnn
+# n[ext][ nnn] go over one line or till line nnn
+# w[here] display frames
+# f[rame] alias for where
+# l[ist][ (-|nn-mm)] list program, - lists backwards
+# nn-mm lists given lines
+# up[ nn] move to higher frame
+# down[ nn] move to lower frame
+# fin[ish] return to outer frame
+# tr[ace] (on|off) set trace mode of current thread
+# tr[ace] (on|off) all set trace mode of all threads
+# q[uit] exit from debugger
+# v[ar] g[lobal] show global variables
+# v[ar] l[ocal] show local variables
+# v[ar] i[nstance] <object> show instance variables of object
+# v[ar] c[onst] <object> show constants of object
+# m[ethod] i[nstance] <obj> show methods of object
+# m[ethod] <class|module> show instance methods of class or module
+# th[read] l[ist] list all threads
+# th[read] c[ur[rent]] show current thread
+# th[read] [sw[itch]] <nnn> switch thread context to nnn
+# th[read] stop <nnn> stop thread nnn
+# th[read] resume <nnn> resume thread nnn
+# p expression evaluate expression and print its value
+# h[elp] print this help
+# <everything else> evaluate
+#
+# == Usage
+#
+# The following is a list of common functionalities that the debugger
+# provides.
+#
+# === Navigating through your code
+#
+# In general, a debugger is used to find bugs in your program, which
+# often means pausing execution and inspecting variables at some point
+# in time.
+#
+# Let's look at an example:
+#
+# def my_method(foo)
+# require 'debug'
+# foo = get_foo if foo.nil?
+# raise if foo.nil?
+# end
+#
+# When you run this program, the debugger will kick in just before the
+# +foo+ assignment.
+#
+# (rdb:1) p foo
+# nil
+#
+# In this example, it'd be interesting to move to the next line and
+# inspect the value of +foo+ again. You can do that by pressing +n+:
+#
+# (rdb:1) n # goes to next line
+# (rdb:1) p foo
+# nil
+#
+# You now know that the original value of +foo+ was nil, and that it
+# still was nil after calling +get_foo+.
+#
+# Other useful commands for navigating through your code are:
+#
+# +c+::
+# Runs the program until it either exists or encounters another breakpoint.
+# You usually press +c+ when you are finished debugging your program and
+# want to resume its execution.
+# +s+::
+# Steps into method definition. In the previous example, +s+ would take you
+# inside the method definition of +get_foo+.
+# +r+::
+# Restart the program.
+# +q+::
+# Quit the program.
+#
+# === Inspecting variables
+#
+# You can use the debugger to easily inspect both local and global variables.
+# We've seen how to inspect local variables before:
+#
+# (rdb:1) p my_arg
+# 42
+#
+# You can also pretty print the result of variables or expressions:
+#
+# (rdb:1) pp %w{a very long long array containing many words}
+# ["a",
+# "very",
+# "long",
+# ...
+# ]
+#
+# You can list all local variables with +v l+:
+#
+# (rdb:1) v l
+# foo => "hello"
+#
+# Similarly, you can show all global variables with +v g+:
+#
+# (rdb:1) v g
+# all global variables
+#
+# Finally, you can omit +p+ if you simply want to evaluate a variable or
+# expression
+#
+# (rdb:1) 5**2
+# 25
+#
+# === Going beyond basics
+#
+# Ruby Debug provides more advanced functionalities like switching
+# between threads, setting breakpoints and watch expressions, and more.
+# The full list of commands is available at any time by pressing +h+.
+#
+# == Staying out of trouble
+#
+# Make sure you remove every instance of +require 'debug'+ before
+# shipping your code. Failing to do so may result in your program
+# hanging unpredictably.
+#
+# Debug is not available in safe mode.
class DEBUGGER__
-class Mutex
- def initialize
- @locker = nil
- @waiting = []
- @locked = false;
- end
-
- def locked?
- @locked
- end
+ MUTEX = Thread::Mutex.new # :nodoc:
- def lock
- return if Thread.critical
- return if @locker == Thread.current
- while (Thread.critical = true; @locked)
- @waiting.push Thread.current
- Thread.stop
- end
- @locked = true
- @locker = Thread.current
- Thread.critical = false
- self
- end
-
- def unlock
- return if Thread.critical
- return unless @locked
- unless @locker == Thread.current
- raise RuntimeError, "unlocked by other"
- end
- Thread.critical = true
- t = @waiting.shift
- @locked = false
- @locker = nil
- Thread.critical = false
- t.run if t
- self
- end
-end
-MUTEX = Mutex.new
-
-class Context
- DEBUG_LAST_CMD = []
-
- begin
- require 'readline'
- def readline(prompt, hist)
- Readline::readline(prompt, hist)
- end
- rescue LoadError
- def readline(prompt, hist)
- STDOUT.print prompt
- STDOUT.flush
- line = STDIN.gets
- exit unless line
- line.chomp!
- line
- end
- USE_READLINE = false
- end
-
- def initialize
- if Thread.current == Thread.main
- @stop_next = 1
- else
- @stop_next = 0
- end
- @last_file = nil
- @file = nil
- @line = nil
- @no_step = nil
- @frames = []
- @finish_pos = 0
- @trace = false
- @catch = "StandardError"
- @suspend_next = false
- end
-
- def stop_next(n=1)
- @stop_next = n
- end
+ class Context # :nodoc:
+ DEBUG_LAST_CMD = []
- def set_suspend
- @suspend_next = true
- end
+ begin
+ require 'readline'
+ def readline(prompt, hist)
+ Readline::readline(prompt, hist)
+ end
+ rescue LoadError
+ def readline(prompt, hist)
+ STDOUT.print prompt
+ STDOUT.flush
+ line = STDIN.gets
+ exit unless line
+ line.chomp!
+ line
+ end
+ USE_READLINE = false
+ end
- def clear_suspend
- @suspend_next = false
- end
+ def initialize
+ if Thread.current == Thread.main
+ @stop_next = 1
+ else
+ @stop_next = 0
+ end
+ @last_file = nil
+ @file = nil
+ @line = nil
+ @no_step = nil
+ @frames = []
+ @finish_pos = 0
+ @trace = false
+ @catch = "StandardError"
+ @suspend_next = false
+ end
- def suspend_all
- DEBUGGER__.suspend
- end
+ def stop_next(n=1)
+ @stop_next = n
+ end
- def resume_all
- DEBUGGER__.resume
- end
+ def set_suspend
+ @suspend_next = true
+ end
- def check_suspend
- return if Thread.critical
- while (Thread.critical = true; @suspend_next)
- DEBUGGER__.waiting.push Thread.current
+ def clear_suspend
@suspend_next = false
- Thread.stop
end
- Thread.critical = false
- end
- def trace?
- @trace
- end
+ def suspend_all
+ DEBUGGER__.suspend
+ end
- def set_trace(arg)
- @trace = arg
- end
+ def resume_all
+ DEBUGGER__.resume
+ end
- def stdout
- DEBUGGER__.stdout
- end
+ def check_suspend
+ while MUTEX.synchronize {
+ if @suspend_next
+ DEBUGGER__.waiting.push Thread.current
+ @suspend_next = false
+ true
+ end
+ }
+ end
+ end
- def break_points
- DEBUGGER__.break_points
- end
+ def trace?
+ @trace
+ end
- def display
- DEBUGGER__.display
- end
+ def set_trace(arg)
+ @trace = arg
+ end
- def context(th)
- DEBUGGER__.context(th)
- end
+ def stdout
+ DEBUGGER__.stdout
+ end
- def set_trace_all(arg)
- DEBUGGER__.set_trace(arg)
- end
+ def break_points
+ DEBUGGER__.break_points
+ end
- def set_last_thread(th)
- DEBUGGER__.set_last_thread(th)
- end
+ def display
+ DEBUGGER__.display
+ end
- def debug_eval(str, binding)
- begin
- val = eval(str, binding)
- rescue StandardError, ScriptError => e
- at = eval("caller(1)", binding)
- stdout.printf "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')
- for i in at
- stdout.printf "\tfrom %s\n", i
- end
- throw :debug_error
+ def context(th)
+ DEBUGGER__.context(th)
end
- end
- def debug_silent_eval(str, binding)
- begin
- eval(str, binding)
- rescue StandardError, ScriptError
- nil
+ def set_trace_all(arg)
+ DEBUGGER__.set_trace(arg)
end
- end
- def var_list(ary, binding)
- ary.sort!
- for v in ary
- stdout.printf " %s => %s\n", v, eval(v, binding).inspect
+ def set_last_thread(th)
+ DEBUGGER__.set_last_thread(th)
end
- end
- def debug_variable_info(input, binding)
- case input
- when /^\s*g(?:lobal)?\s*$/
- var_list(global_variables, binding)
+ def debug_eval(str, binding)
+ begin
+ eval(str, binding)
+ rescue StandardError, ScriptError => e
+ at = eval("caller(1)", binding)
+ stdout.printf "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')
+ for i in at
+ stdout.printf "\tfrom %s\n", i
+ end
+ throw :debug_error
+ end
+ end
- when /^\s*l(?:ocal)?\s*$/
- var_list(eval("local_variables", binding), binding)
+ def debug_silent_eval(str, binding)
+ begin
+ eval(str, binding)
+ rescue StandardError, ScriptError
+ nil
+ end
+ end
- when /^\s*i(?:nstance)?\s+/
- obj = debug_eval($', binding)
- var_list(obj.instance_variables, obj.instance_eval{binding()})
+ def var_list(ary, binding)
+ ary.sort!
+ for v in ary
+ stdout.printf " %s => %s\n", v, eval(v.to_s, binding).inspect
+ end
+ end
- when /^\s*c(?:onst(?:ant)?)?\s+/
- obj = debug_eval($', binding)
- unless obj.kind_of? Module
- stdout.print "Should be Class/Module: ", $', "\n"
- else
- var_list(obj.constants, obj.module_eval{binding()})
+ def debug_variable_info(input, binding)
+ case input
+ when /^\s*g(?:lobal)?\s*$/
+ var_list(global_variables, binding)
+
+ when /^\s*l(?:ocal)?\s*$/
+ var_list(eval("local_variables", binding), binding)
+
+ when /^\s*i(?:nstance)?\s+/
+ obj = debug_eval($', binding)
+ var_list(obj.instance_variables, obj.instance_eval{binding()})
+
+ when /^\s*c(?:onst(?:ant)?)?\s+/
+ obj = debug_eval($', binding)
+ unless obj.kind_of? Module
+ stdout.print "Should be Class/Module: ", $', "\n"
+ else
+ var_list(obj.constants, obj.module_eval{binding()})
+ end
end
end
- end
- def debug_method_info(input, binding)
- case input
- when /^i(:?nstance)?\s+/
- obj = debug_eval($', binding)
-
- len = 0
- for v in obj.methods.sort
- len += v.size + 1
- if len > 70
- len = v.size + 1
- stdout.print "\n"
- end
- stdout.print v, " "
- end
- stdout.print "\n"
-
- else
- obj = debug_eval(input, binding)
- unless obj.kind_of? Module
- stdout.print "Should be Class/Module: ", input, "\n"
+ def debug_method_info(input, binding)
+ case input
+ when /^i(:?nstance)?\s+/
+ obj = debug_eval($', binding)
+
+ len = 0
+ for v in obj.methods.sort
+ len += v.size + 1
+ if len > 70
+ len = v.size + 1
+ stdout.print "\n"
+ end
+ stdout.print v, " "
+ end
+ stdout.print "\n"
+
else
- len = 0
- for v in obj.instance_methods(false).sort
- len += v.size + 1
- if len > 70
- len = v.size + 1
- stdout.print "\n"
- end
- stdout.print v, " "
- end
- stdout.print "\n"
+ obj = debug_eval(input, binding)
+ unless obj.kind_of? Module
+ stdout.print "Should be Class/Module: ", input, "\n"
+ else
+ len = 0
+ for v in obj.instance_methods(false).sort
+ len += v.size + 1
+ if len > 70
+ len = v.size + 1
+ stdout.print "\n"
+ end
+ stdout.print v, " "
+ end
+ stdout.print "\n"
+ end
end
end
- end
- def thnum
- num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
- unless num
- DEBUGGER__.make_thread_list
+ def thnum
num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
+ unless num
+ DEBUGGER__.make_thread_list
+ num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
+ end
+ num
end
- num
- end
- def debug_command(file, line, id, binding)
- MUTEX.lock
- unless defined?($debugger_restart) and $debugger_restart
- callcc{|c| $debugger_restart = c}
- end
- set_last_thread(Thread.current)
- frame_pos = 0
- binding_file = file
- binding_line = line
- previous_line = nil
- if ENV['EMACS']
- stdout.printf "\032\032%s:%d:\n", binding_file, binding_line
- else
- stdout.printf "%s:%d:%s", binding_file, binding_line,
- line_at(binding_file, binding_line)
- end
- @frames[0] = [binding, file, line, id]
- display_expressions(binding)
- prompt = true
- while prompt and input = readline("(rdb:%d) "%thnum(), true)
- catch(:debug_error) do
- if input == ""
- next unless DEBUG_LAST_CMD[0]
- input = DEBUG_LAST_CMD[0]
- stdout.print input, "\n"
- else
- DEBUG_LAST_CMD[0] = input
- end
-
- case input
- when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/
- if defined?( $2 )
- if $1 == 'on'
- set_trace_all true
+ def debug_command(file, line, id, binding)
+ MUTEX.lock
+ unless defined?($debugger_restart) and $debugger_restart
+ callcc{|c| $debugger_restart = c}
+ end
+ set_last_thread(Thread.current)
+ frame_pos = 0
+ binding_file = file
+ binding_line = line
+ previous_line = nil
+ if ENV['EMACS']
+ stdout.printf "\032\032%s:%d:\n", binding_file, binding_line
+ else
+ stdout.printf "%s:%d:%s", binding_file, binding_line,
+ line_at(binding_file, binding_line)
+ end
+ @frames[0] = [binding, file, line, id]
+ display_expressions(binding)
+ prompt = true
+ while prompt and input = readline("(rdb:%d) "%thnum(), true)
+ catch(:debug_error) do
+ if input == ""
+ next unless DEBUG_LAST_CMD[0]
+ input = DEBUG_LAST_CMD[0]
+ stdout.print input, "\n"
+ else
+ DEBUG_LAST_CMD[0] = input
+ end
+
+ case input
+ when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/
+ if defined?( $2 )
+ if $1 == 'on'
+ set_trace_all true
+ else
+ set_trace_all false
+ end
+ elsif defined?( $1 )
+ if $1 == 'on'
+ set_trace true
+ else
+ set_trace false
+ end
+ end
+ if trace?
+ stdout.print "Trace on.\n"
else
- set_trace_all false
+ stdout.print "Trace off.\n"
+ end
+
+ when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/
+ pos = $2
+ if $1
+ klass = debug_silent_eval($1, binding)
+ file = $1
end
- elsif defined?( $1 )
- if $1 == 'on'
- set_trace true
+ if pos =~ /^\d+$/
+ pname = pos
+ pos = pos.to_i
else
- set_trace false
+ pname = pos = pos.intern.id2name
+ end
+ break_points.push [true, 0, klass || file, pos]
+ stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, klass || file, pname
+
+ when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/
+ pos = $2.intern.id2name
+ klass = debug_eval($1, binding)
+ break_points.push [true, 0, klass, pos]
+ stdout.printf "Set breakpoint %d at %s.%s\n", break_points.size, klass, pos
+
+ when /^\s*wat(?:ch)?\s+(.+)$/
+ exp = $1
+ break_points.push [true, 1, exp]
+ stdout.printf "Set watchpoint %d:%s\n", break_points.size, exp
+
+ when /^\s*b(?:reak)?$/
+ if break_points.find{|b| b[1] == 0}
+ n = 1
+ stdout.print "Breakpoints:\n"
+ break_points.each do |b|
+ if b[0] and b[1] == 0
+ stdout.printf " %d %s:%s\n", n, b[2], b[3]
+ end
+ n += 1
+ end
+ end
+ if break_points.find{|b| b[1] == 1}
+ n = 1
+ stdout.print "\n"
+ stdout.print "Watchpoints:\n"
+ for b in break_points
+ if b[0] and b[1] == 1
+ stdout.printf " %d %s\n", n, b[2]
+ end
+ n += 1
+ end
+ end
+ if break_points.size == 0
+ stdout.print "No breakpoints\n"
+ else
+ stdout.print "\n"
end
- end
- if trace?
- stdout.print "Trace on.\n"
- else
- stdout.print "Trace off.\n"
- end
- when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/
- pos = $2
- if $1
- klass = debug_silent_eval($1, binding)
- file = $1
+ when /^\s*del(?:ete)?(?:\s+(\d+))?$/
+ pos = $1
+ unless pos
+ input = readline("Clear all breakpoints? (y/n) ", false)
+ if input == "y"
+ for b in break_points
+ b[0] = false
+ end
+ end
+ else
+ pos = pos.to_i
+ if break_points[pos-1]
+ break_points[pos-1][0] = false
+ else
+ stdout.printf "Breakpoint %d is not defined\n", pos
+ end
+ end
+
+ when /^\s*disp(?:lay)?\s+(.+)$/
+ exp = $1
+ display.push [true, exp]
+ stdout.printf "%d: ", display.size
+ display_expression(exp, binding)
+
+ when /^\s*disp(?:lay)?$/
+ display_expressions(binding)
+
+ when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/
+ pos = $1
+ unless pos
+ input = readline("Clear all expressions? (y/n) ", false)
+ if input == "y"
+ for d in display
+ d[0] = false
+ end
+ end
+ else
+ pos = pos.to_i
+ if display[pos-1]
+ display[pos-1][0] = false
+ else
+ stdout.printf "Display expression %d is not defined\n", pos
+ end
+ end
+
+ when /^\s*c(?:ont)?$/
+ prompt = false
+
+ when /^\s*s(?:tep)?(?:\s+(\d+))?$/
+ if $1
+ lev = $1.to_i
+ else
+ lev = 1
+ end
+ @stop_next = lev
+ prompt = false
+
+ when /^\s*n(?:ext)?(?:\s+(\d+))?$/
+ if $1
+ lev = $1.to_i
+ else
+ lev = 1
+ end
+ @stop_next = lev
+ @no_step = @frames.size - frame_pos
+ prompt = false
+
+ when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/
+ display_frames(frame_pos)
+
+ when /^\s*l(?:ist)?(?:\s+(.+))?$/
+ if not $1
+ b = previous_line ? previous_line + 10 : binding_line - 5
+ e = b + 9
+ elsif $1 == '-'
+ b = previous_line ? previous_line - 10 : binding_line - 5
+ e = b + 9
+ else
+ b, e = $1.split(/[-,]/)
+ if e
+ b = b.to_i
+ e = e.to_i
+ else
+ b = b.to_i - 5
+ e = b + 9
+ end
+ end
+ previous_line = b
+ display_list(b, e, binding_file, binding_line)
+
+ when /^\s*up(?:\s+(\d+))?$/
+ previous_line = nil
+ if $1
+ lev = $1.to_i
+ else
+ lev = 1
+ end
+ frame_pos += lev
+ if frame_pos >= @frames.size
+ frame_pos = @frames.size - 1
+ stdout.print "At toplevel\n"
+ end
+ binding, binding_file, binding_line = @frames[frame_pos]
+ stdout.print format_frame(frame_pos)
+
+ when /^\s*down(?:\s+(\d+))?$/
+ previous_line = nil
+ if $1
+ lev = $1.to_i
+ else
+ lev = 1
+ end
+ frame_pos -= lev
+ if frame_pos < 0
+ frame_pos = 0
+ stdout.print "At stack bottom\n"
+ end
+ binding, binding_file, binding_line = @frames[frame_pos]
+ stdout.print format_frame(frame_pos)
+
+ when /^\s*fin(?:ish)?$/
+ if frame_pos == @frames.size
+ stdout.print "\"finish\" not meaningful in the outermost frame.\n"
+ else
+ @finish_pos = @frames.size - frame_pos
+ frame_pos = 0
+ prompt = false
+ end
+
+ when /^\s*cat(?:ch)?(?:\s+(.+))?$/
+ if $1
+ excn = $1
+ if excn == 'off'
+ @catch = nil
+ stdout.print "Clear catchpoint.\n"
+ else
+ @catch = excn
+ stdout.printf "Set catchpoint %s.\n", @catch
+ end
+ else
+ if @catch
+ stdout.printf "Catchpoint %s.\n", @catch
+ else
+ stdout.print "No catchpoint.\n"
+ end
+ end
+
+ when /^\s*q(?:uit)?$/
+ input = readline("Really quit? (y/n) ", false)
+ if input == "y"
+ exit! # exit -> exit!: No graceful way to stop threads...
+ end
+
+ when /^\s*v(?:ar)?\s+/
+ debug_variable_info($', binding)
+
+ when /^\s*m(?:ethod)?\s+/
+ debug_method_info($', binding)
+
+ when /^\s*th(?:read)?\s+/
+ if DEBUGGER__.debug_thread_info($', binding) == :cont
+ prompt = false
+ end
+
+ when /^\s*pp\s+/
+ PP.pp(debug_eval($', binding), stdout)
+
+ when /^\s*p\s+/
+ stdout.printf "%s\n", debug_eval($', binding).inspect
+
+ when /^\s*r(?:estart)?$/
+ $debugger_restart.call
+
+ when /^\s*h(?:elp)?$/
+ debug_print_help()
+
+ else
+ v = debug_eval(input, binding)
+ stdout.printf "%s\n", v.inspect
end
- if pos =~ /^\d+$/
- pname = pos
- pos = pos.to_i
- else
- pname = pos = pos.intern.id2name
- end
- break_points.push [true, 0, klass || file, pos]
- stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, klass || file, pname
-
- when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/
- pos = $2.intern.id2name
- klass = debug_eval($1, binding)
- break_points.push [true, 0, klass, pos]
- stdout.printf "Set breakpoint %d at %s.%s\n", break_points.size, klass, pos
-
- when /^\s*wat(?:ch)?\s+(.+)$/
- exp = $1
- break_points.push [true, 1, exp]
- stdout.printf "Set watchpoint %d:%s\n", break_points.size, exp
-
- when /^\s*b(?:reak)?$/
- if break_points.find{|b| b[1] == 0}
- n = 1
- stdout.print "Breakpoints:\n"
- for b in break_points
- if b[0] and b[1] == 0
- stdout.printf " %d %s:%s\n", n, b[2], b[3]
- end
- n += 1
- end
- end
- if break_points.find{|b| b[1] == 1}
- n = 1
- stdout.print "\n"
- stdout.print "Watchpoints:\n"
- for b in break_points
- if b[0] and b[1] == 1
- stdout.printf " %d %s\n", n, b[2]
- end
- n += 1
- end
- end
- if break_points.size == 0
- stdout.print "No breakpoints\n"
- else
- stdout.print "\n"
- end
-
- when /^\s*del(?:ete)?(?:\s+(\d+))?$/
- pos = $1
- unless pos
- input = readline("Clear all breakpoints? (y/n) ", false)
- if input == "y"
- for b in break_points
- b[0] = false
- end
- end
- else
- pos = pos.to_i
- if break_points[pos-1]
- break_points[pos-1][0] = false
- else
- stdout.printf "Breakpoint %d is not defined\n", pos
- end
- end
-
- when /^\s*disp(?:lay)?\s+(.+)$/
- exp = $1
- display.push [true, exp]
- stdout.printf "%d: ", display.size
- display_expression(exp, binding)
-
- when /^\s*disp(?:lay)?$/
- display_expressions(binding)
-
- when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/
- pos = $1
- unless pos
- input = readline("Clear all expressions? (y/n) ", false)
- if input == "y"
- for d in display
- d[0] = false
- end
- end
- else
- pos = pos.to_i
- if display[pos-1]
- display[pos-1][0] = false
- else
- stdout.printf "Display expression %d is not defined\n", pos
- end
- end
-
- when /^\s*c(?:ont)?$/
- prompt = false
-
- when /^\s*s(?:tep)?(?:\s+(\d+))?$/
- if $1
- lev = $1.to_i
- else
- lev = 1
- end
- @stop_next = lev
- prompt = false
-
- when /^\s*n(?:ext)?(?:\s+(\d+))?$/
- if $1
- lev = $1.to_i
- else
- lev = 1
- end
- @stop_next = lev
- @no_step = @frames.size - frame_pos
- prompt = false
-
- when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/
- display_frames(frame_pos)
-
- when /^\s*l(?:ist)?(?:\s+(.+))?$/
- if not $1
- b = previous_line ? previous_line + 10 : binding_line - 5
- e = b + 9
- elsif $1 == '-'
- b = previous_line ? previous_line - 10 : binding_line - 5
- e = b + 9
- else
- b, e = $1.split(/[-,]/)
- if e
- b = b.to_i
- e = e.to_i
- else
- b = b.to_i - 5
- e = b + 9
- end
- end
- previous_line = b
- display_list(b, e, binding_file, binding_line)
-
- when /^\s*up(?:\s+(\d+))?$/
- previous_line = nil
- if $1
- lev = $1.to_i
- else
- lev = 1
- end
- frame_pos += lev
- if frame_pos >= @frames.size
- frame_pos = @frames.size - 1
- stdout.print "At toplevel\n"
- end
- binding, binding_file, binding_line = @frames[frame_pos]
- stdout.print format_frame(frame_pos)
-
- when /^\s*down(?:\s+(\d+))?$/
- previous_line = nil
- if $1
- lev = $1.to_i
- else
- lev = 1
- end
- frame_pos -= lev
- if frame_pos < 0
- frame_pos = 0
- stdout.print "At stack bottom\n"
- end
- binding, binding_file, binding_line = @frames[frame_pos]
- stdout.print format_frame(frame_pos)
-
- when /^\s*fin(?:ish)?$/
- if frame_pos == @frames.size
- stdout.print "\"finish\" not meaningful in the outermost frame.\n"
- else
- @finish_pos = @frames.size - frame_pos
- frame_pos = 0
- prompt = false
- end
-
- when /^\s*cat(?:ch)?(?:\s+(.+))?$/
- if $1
- excn = $1
- if excn == 'off'
- @catch = nil
- stdout.print "Clear catchpoint.\n"
- else
- @catch = excn
- stdout.printf "Set catchpoint %s.\n", @catch
- end
- else
- if @catch
- stdout.printf "Catchpoint %s.\n", @catch
- else
- stdout.print "No catchpoint.\n"
- end
- end
-
- when /^\s*q(?:uit)?$/
- input = readline("Really quit? (y/n) ", false)
- if input == "y"
- exit! # exit -> exit!: No graceful way to stop threads...
- end
-
- when /^\s*v(?:ar)?\s+/
- debug_variable_info($', binding)
-
- when /^\s*m(?:ethod)?\s+/
- debug_method_info($', binding)
-
- when /^\s*th(?:read)?\s+/
- if DEBUGGER__.debug_thread_info($', binding) == :cont
- prompt = false
- end
-
- when /^\s*pp\s+/
- PP.pp(debug_eval($', binding), stdout)
-
- when /^\s*p\s+/
- stdout.printf "%s\n", debug_eval($', binding).inspect
-
- when /^\s*r(?:estart)?$/
- $debugger_restart.call
-
- when /^\s*h(?:elp)?$/
- debug_print_help()
-
- else
- v = debug_eval(input, binding)
- stdout.printf "%s\n", v.inspect
- end
- end
- end
- MUTEX.unlock
- resume_all
- end
+ end
+ end
+ MUTEX.unlock
+ resume_all
+ end
- def debug_print_help
- stdout.print <<EOHELP
+ def debug_print_help
+ stdout.print <<EOHELP
Debugger help v.-0.002b
Commands
b[reak] [file:|class:]<line|method>
@@ -581,367 +706,406 @@ Commands
th[read] [sw[itch]] <nnn> switch thread context to nnn
th[read] stop <nnn> stop thread nnn
th[read] resume <nnn> resume thread nnn
+ pp expression evaluate expression and pretty_print its value
p expression evaluate expression and print its value
+ r[estart] restart program
h[elp] print this help
<everything else> evaluate
EOHELP
- end
+ end
- def display_expressions(binding)
- n = 1
- for d in display
- if d[0]
- stdout.printf "%d: ", n
- display_expression(d[1], binding)
+ def display_expressions(binding)
+ n = 1
+ for d in display
+ if d[0]
+ stdout.printf "%d: ", n
+ display_expression(d[1], binding)
+ end
+ n += 1
end
- n += 1
end
- end
- def display_expression(exp, binding)
- stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s
- end
-
- def frame_set_pos(file, line)
- if @frames[0]
- @frames[0][1] = file
- @frames[0][2] = line
+ def display_expression(exp, binding)
+ stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s
end
- end
- def display_frames(pos)
- 0.upto(@frames.size - 1) do |n|
- if n == pos
- stdout.print "--> "
- else
- stdout.print " "
+ def frame_set_pos(file, line)
+ if @frames[0]
+ @frames[0][1] = file
+ @frames[0][2] = line
end
- stdout.print format_frame(n)
end
- end
-
- def format_frame(pos)
- bind, file, line, id = @frames[pos]
- sprintf "#%d %s:%s%s\n", pos + 1, file, line,
- (id ? ":in `#{id.id2name}'" : "")
- end
- def display_list(b, e, file, line)
- stdout.printf "[%d, %d] in %s\n", b, e, file
- if lines = SCRIPT_LINES__[file] and lines != true
- n = 0
- b.upto(e) do |n|
- if n > 0 && lines[n-1]
- if n == line
- stdout.printf "=> %d %s\n", n, lines[n-1].chomp
- else
- stdout.printf " %d %s\n", n, lines[n-1].chomp
- end
- end
- end
- else
- stdout.printf "No sourcefile available for %s\n", file
+ def display_frames(pos)
+ 0.upto(@frames.size - 1) do |n|
+ if n == pos
+ stdout.print "--> "
+ else
+ stdout.print " "
+ end
+ stdout.print format_frame(n)
+ end
end
- end
- def line_at(file, line)
- lines = SCRIPT_LINES__[file]
- if lines
- return "\n" if lines == true
- line = lines[line-1]
- return "\n" unless line
- return line
+ def format_frame(pos)
+ _, file, line, id = @frames[pos]
+ sprintf "#%d %s:%s%s\n", pos + 1, file, line,
+ (id ? ":in `#{id.id2name}'" : "")
end
- return "\n"
- end
- def debug_funcname(id)
- if id.nil?
- "toplevel"
- else
- id.id2name
+ def script_lines(file, line)
+ unless (lines = SCRIPT_LINES__[file]) and lines != true
+ Tracer::Single.get_line(file, line) if File.exist?(file)
+ lines = SCRIPT_LINES__[file]
+ lines = nil if lines == true
+ end
+ lines
end
- end
- def check_break_points(file, klass, pos, binding, id)
- return false if break_points.empty?
- n = 1
- for b in break_points
- if b[0] # valid
- if b[1] == 0 # breakpoint
- if (b[2] == file and b[3] == pos) or
- (klass and b[2] == klass and b[3] == pos)
- stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
- return true
- end
- elsif b[1] == 1 # watchpoint
- if debug_silent_eval(b[2], binding)
- stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
- return true
- end
- end
- end
- n += 1
- end
- return false
- end
-
- def excn_handle(file, line, id, binding)
- if $!.class <= SystemExit
- set_trace_func nil
- exit
+ def display_list(b, e, file, line)
+ if lines = script_lines(file, line)
+ stdout.printf "[%d, %d] in %s\n", b, e, file
+ b.upto(e) do |n|
+ if n > 0 && lines[n-1]
+ if n == line
+ stdout.printf "=> %d %s\n", n, lines[n-1].chomp
+ else
+ stdout.printf " %d %s\n", n, lines[n-1].chomp
+ end
+ end
+ end
+ else
+ stdout.printf "No sourcefile available for %s\n", file
+ end
end
- if @catch and ($!.class.ancestors.find { |e| e.to_s == @catch })
- stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.class
- fs = @frames.size
- tb = caller(0)[-fs..-1]
- if tb
- for i in tb
- stdout.printf "\tfrom %s\n", i
- end
+ def line_at(file, line)
+ lines = script_lines(file, line)
+ if lines and line = lines[line-1]
+ return line
end
- suspend_all
- debug_command(file, line, id, binding)
+ return "\n"
end
- end
- def trace_func(event, file, line, id, binding, klass)
- Tracer.trace_func(event, file, line, id, binding, klass) if trace?
- context(Thread.current).check_suspend
- @file = file
- @line = line
- case event
- when 'line'
- frame_set_pos(file, line)
- if !@no_step or @frames.size == @no_step
- @stop_next -= 1
- @stop_next = -1 if @stop_next < 0
- elsif @frames.size < @no_step
- @stop_next = 0 # break here before leaving...
+ def debug_funcname(id)
+ if id.nil?
+ "toplevel"
else
- # nothing to do. skipped.
- end
- if @stop_next == 0 or check_break_points(file, nil, line, binding, id)
- @no_step = nil
- suspend_all
- debug_command(file, line, id, binding)
+ id.id2name
end
+ end
- when 'call'
- @frames.unshift [binding, file, line, id]
- if check_break_points(file, klass, id.id2name, binding, id)
- suspend_all
- debug_command(file, line, id, binding)
+ def check_break_points(file, klass, pos, binding, id)
+ return false if break_points.empty?
+ n = 1
+ for b in break_points
+ if b[0] # valid
+ if b[1] == 0 # breakpoint
+ if (b[2] == file and b[3] == pos) or
+ (klass and b[2] == klass and b[3] == pos)
+ stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
+ return true
+ end
+ elsif b[1] == 1 # watchpoint
+ if debug_silent_eval(b[2], binding)
+ stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
+ return true
+ end
+ end
+ end
+ n += 1
end
+ return false
+ end
- when 'c-call'
- frame_set_pos(file, line)
-
- when 'class'
- @frames.unshift [binding, file, line, id]
-
- when 'return', 'end'
- if @frames.size == @finish_pos
- @stop_next = 1
- @finish_pos = 0
+ def excn_handle(file, line, id, binding)
+ if $!.class <= SystemExit
+ set_trace_func nil
+ exit
end
- @frames.shift
-
- when 'end'
- @frames.shift
-
- when 'raise'
- excn_handle(file, line, id, binding)
+ if @catch and ($!.class.ancestors.find { |e| e.to_s == @catch })
+ stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.class
+ fs = @frames.size
+ tb = caller(0)[-fs..-1]
+ if tb
+ for i in tb
+ stdout.printf "\tfrom %s\n", i
+ end
+ end
+ suspend_all
+ debug_command(file, line, id, binding)
+ end
end
- @last_file = file
- end
-end
-
-trap("INT") { DEBUGGER__.interrupt }
-@last_thread = Thread::main
-@max_thread = 1
-@thread_list = {Thread::main => 1}
-@break_points = []
-@display = []
-@waiting = []
-@stdout = STDOUT
-
-class << DEBUGGER__
- def stdout
- @stdout
- end
-
- def stdout=(s)
- @stdout = s
- end
- def display
- @display
- end
-
- def break_points
- @break_points
- end
+ def trace_func(event, file, line, id, binding, klass)
+ Tracer.trace_func(event, file, line, id, binding, klass) if trace?
+ context(Thread.current).check_suspend
+ @file = file
+ @line = line
+ case event
+ when 'line'
+ frame_set_pos(file, line)
+ if !@no_step or @frames.size == @no_step
+ @stop_next -= 1
+ @stop_next = -1 if @stop_next < 0
+ elsif @frames.size < @no_step
+ @stop_next = 0 # break here before leaving...
+ else
+ # nothing to do. skipped.
+ end
+ if @stop_next == 0 or check_break_points(file, nil, line, binding, id)
+ @no_step = nil
+ suspend_all
+ debug_command(file, line, id, binding)
+ end
+
+ when 'call'
+ @frames.unshift [binding, file, line, id]
+ if check_break_points(file, klass, id.id2name, binding, id)
+ suspend_all
+ debug_command(file, line, id, binding)
+ end
+
+ when 'c-call'
+ frame_set_pos(file, line)
+
+ when 'class'
+ @frames.unshift [binding, file, line, id]
+
+ when 'return', 'end'
+ if @frames.size == @finish_pos
+ @stop_next = 1
+ @finish_pos = 0
+ end
+ @frames.shift
+
+ when 'raise'
+ excn_handle(file, line, id, binding)
- def waiting
- @waiting
- end
-
- def set_trace( arg )
- saved_crit = Thread.critical
- Thread.critical = true
- make_thread_list
- for th, in @thread_list
- context(th).set_trace arg
+ end
+ @last_file = file
end
- Thread.critical = saved_crit
- arg
end
- def set_last_thread(th)
- @last_thread = th
- end
+ trap("INT") { DEBUGGER__.interrupt }
+ @last_thread = Thread::main
+ @max_thread = 1
+ @thread_list = {Thread::main => 1}
+ @break_points = []
+ @display = []
+ @waiting = []
+ @stdout = STDOUT
+
+ class << DEBUGGER__
+ # Returns the IO used as stdout. Defaults to STDOUT
+ def stdout
+ @stdout
+ end
- def suspend
- saved_crit = Thread.critical
- Thread.critical = true
- make_thread_list
- for th, in @thread_list
- next if th == Thread.current
- context(th).set_suspend
- end
- Thread.critical = saved_crit
- # Schedule other threads to suspend as soon as possible.
- Thread.pass unless Thread.critical
- end
+ # Sets the IO used as stdout. Defaults to STDOUT
+ def stdout=(s)
+ @stdout = s
+ end
- def resume
- saved_crit = Thread.critical
- Thread.critical = true
- make_thread_list
- for th, in @thread_list
- next if th == Thread.current
- context(th).clear_suspend
- end
- waiting.each do |th|
- th.run
- end
- waiting.clear
- Thread.critical = saved_crit
- # Schedule other threads to restart as soon as possible.
- Thread.pass
- end
+ # Returns the display expression list
+ #
+ # See DEBUGGER__ for more usage
+ def display
+ @display
+ end
- def context(thread=Thread.current)
- c = thread[:__debugger_data__]
- unless c
- thread[:__debugger_data__] = c = Context.new
+ # Returns the list of break points where execution will be stopped.
+ #
+ # See DEBUGGER__ for more usage
+ def break_points
+ @break_points
end
- c
- end
- def interrupt
- context(@last_thread).stop_next
- end
+ # Returns the list of waiting threads.
+ #
+ # When stepping through the traces of a function, thread gets suspended, to
+ # be resumed later.
+ def waiting
+ @waiting
+ end
- def get_thread(num)
- th = @thread_list.index(num)
- unless th
- @stdout.print "No thread ##{num}\n"
- throw :debug_error
+ def set_trace( arg )
+ MUTEX.synchronize do
+ make_thread_list
+ for th, in @thread_list
+ context(th).set_trace arg
+ end
+ end
+ arg
end
- th
- end
- def thread_list(num)
- th = get_thread(num)
- if th == Thread.current
- @stdout.print "+"
- else
- @stdout.print " "
+ def set_last_thread(th)
+ @last_thread = th
end
- @stdout.printf "%d ", num
- @stdout.print th.inspect, "\t"
- file = context(th).instance_eval{@file}
- if file
- @stdout.print file,":",context(th).instance_eval{@line}
+
+ def suspend
+ MUTEX.synchronize do
+ make_thread_list
+ for th, in @thread_list
+ next if th == Thread.current
+ context(th).set_suspend
+ end
+ end
+ # Schedule other threads to suspend as soon as possible.
+ Thread.pass
end
- @stdout.print "\n"
- end
- def thread_list_all
- for th in @thread_list.values.sort
- thread_list(th)
+ def resume
+ MUTEX.synchronize do
+ make_thread_list
+ @thread_list.each do |th,|
+ next if th == Thread.current
+ context(th).clear_suspend
+ end
+ waiting.each do |th|
+ th.run
+ end
+ waiting.clear
+ end
+ # Schedule other threads to restart as soon as possible.
+ Thread.pass
end
- end
- def make_thread_list
- hash = {}
- for th in Thread::list
- if @thread_list.key? th
- hash[th] = @thread_list[th]
- else
- @max_thread += 1
- hash[th] = @max_thread
+ def context(thread=Thread.current)
+ c = thread[:__debugger_data__]
+ unless c
+ thread[:__debugger_data__] = c = Context.new
end
+ c
end
- @thread_list = hash
- end
- def debug_thread_info(input, binding)
- case input
- when /^l(?:ist)?/
- make_thread_list
- thread_list_all
+ def interrupt
+ context(@last_thread).stop_next
+ end
- when /^c(?:ur(?:rent)?)?$/
- make_thread_list
- thread_list(@thread_list[Thread.current])
+ def get_thread(num)
+ th = @thread_list.key(num)
+ unless th
+ @stdout.print "No thread ##{num}\n"
+ throw :debug_error
+ end
+ th
+ end
- when /^(?:sw(?:itch)?\s+)?(\d+)/
- make_thread_list
- th = get_thread($1.to_i)
+ def thread_list(num)
+ th = get_thread(num)
if th == Thread.current
- @stdout.print "It's the current thread.\n"
+ @stdout.print "+"
else
- thread_list(@thread_list[th])
- context(th).stop_next
- th.run
- return :cont
+ @stdout.print " "
+ end
+ @stdout.printf "%d ", num
+ @stdout.print th.inspect, "\t"
+ file = context(th).instance_eval{@file}
+ if file
+ @stdout.print file,":",context(th).instance_eval{@line}
end
+ @stdout.print "\n"
+ end
- when /^stop\s+(\d+)/
- make_thread_list
- th = get_thread($1.to_i)
- if th == Thread.current
- @stdout.print "It's the current thread.\n"
- elsif th.stop?
- @stdout.print "Already stopped.\n"
- else
- thread_list(@thread_list[th])
- context(th).suspend
+ # Prints all threads in @thread_list to @stdout. Returns a sorted array of
+ # values from the @thread_list hash.
+ #
+ # While in the debugger you can list all of
+ # the threads with: <b>DEBUGGER__.thread_list_all</b>
+ #
+ # (rdb:1) DEBUGGER__.thread_list_all
+ # +1 #<Thread:0x007fb2320c03f0 run> debug_me.rb.rb:3
+ # 2 #<Thread:0x007fb23218a538@debug_me.rb.rb:3 sleep>
+ # 3 #<Thread:0x007fb23218b0f0@debug_me.rb.rb:3 sleep>
+ # [1, 2, 3]
+ #
+ # Your current thread is indicated by a <b>+</b>
+ #
+ # Additionally you can list all threads with <b>th l</b>
+ #
+ # (rdb:1) th l
+ # +1 #<Thread:0x007f99328c0410 run> debug_me.rb:3
+ # 2 #<Thread:0x007f9932938230@debug_me.rb:3 sleep> debug_me.rb:3
+ # 3 #<Thread:0x007f9932938e10@debug_me.rb:3 sleep> debug_me.rb:3
+ #
+ # See DEBUGGER__ for more usage.
+
+ def thread_list_all
+ for th in @thread_list.values.sort
+ thread_list(th)
+ end
+ end
+
+ def make_thread_list
+ hash = {}
+ for th in Thread::list
+ if @thread_list.key? th
+ hash[th] = @thread_list[th]
+ else
+ @max_thread += 1
+ hash[th] = @max_thread
+ end
end
+ @thread_list = hash
+ end
- when /^resume\s+(\d+)/
- make_thread_list
- th = get_thread($1.to_i)
- if th == Thread.current
- @stdout.print "It's the current thread.\n"
- elsif !th.stop?
- @stdout.print "Already running."
- else
- thread_list(@thread_list[th])
- th.run
+ def debug_thread_info(input, binding)
+ case input
+ when /^l(?:ist)?/
+ make_thread_list
+ thread_list_all
+
+ when /^c(?:ur(?:rent)?)?$/
+ make_thread_list
+ thread_list(@thread_list[Thread.current])
+
+ when /^(?:sw(?:itch)?\s+)?(\d+)/
+ make_thread_list
+ th = get_thread($1.to_i)
+ if th == Thread.current
+ @stdout.print "It's the current thread.\n"
+ else
+ thread_list(@thread_list[th])
+ context(th).stop_next
+ th.run
+ return :cont
+ end
+
+ when /^stop\s+(\d+)/
+ make_thread_list
+ th = get_thread($1.to_i)
+ if th == Thread.current
+ @stdout.print "It's the current thread.\n"
+ elsif th.stop?
+ @stdout.print "Already stopped.\n"
+ else
+ thread_list(@thread_list[th])
+ context(th).suspend
+ end
+
+ when /^resume\s+(\d+)/
+ make_thread_list
+ th = get_thread($1.to_i)
+ if th == Thread.current
+ @stdout.print "It's the current thread.\n"
+ elsif !th.stop?
+ @stdout.print "Already running."
+ else
+ thread_list(@thread_list[th])
+ th.run
+ end
end
end
end
-end
-stdout.printf "Debug.rb\n"
-stdout.printf "Emacs support available.\n\n"
-set_trace_func proc { |event, file, line, id, binding, klass, *rest|
- DEBUGGER__.context.trace_func event, file, line, id, binding, klass
-}
+ stdout.printf "Debug.rb\n"
+ stdout.printf "Emacs support available.\n\n"
+ RubyVM::InstructionSequence.compile_option = {
+ trace_instruction: true
+ }
+ set_trace_func proc { |event, file, line, id, binding, klass, *rest|
+ DEBUGGER__.context.trace_func event, file, line, id, binding, klass
+ }
end