mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 ed5d032ea3
			
		
	
	
		ed5d032ea3
		
	
	
	
	
		
			
			When debugging some local code, specifying a breakpoint to a local
filename does not work, i.e.
    break lib/foo.rb:10
Expanding the filename makes it work. FWIW byebug has the same behavior.
		
	
			
		
			
				
	
	
		
			1106 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			1106 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # 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'
 | |
| 
 | |
| require 'tracer'
 | |
| require 'pp'
 | |
| 
 | |
| class Tracer # :nodoc:
 | |
|   def Tracer.trace_func(*vars)
 | |
|     Single.trace_func(*vars)
 | |
|   end
 | |
| end
 | |
| 
 | |
| 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__
 | |
|   MUTEX = Thread::Mutex.new # :nodoc:
 | |
| 
 | |
|   class Context # :nodoc:
 | |
|     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
 | |
| 
 | |
|     def set_suspend
 | |
|       @suspend_next = true
 | |
|     end
 | |
| 
 | |
|     def clear_suspend
 | |
|       @suspend_next = false
 | |
|     end
 | |
| 
 | |
|     def suspend_all
 | |
|       DEBUGGER__.suspend
 | |
|     end
 | |
| 
 | |
|     def resume_all
 | |
|       DEBUGGER__.resume
 | |
|     end
 | |
| 
 | |
|     def check_suspend
 | |
|       while MUTEX.synchronize {
 | |
|           if @suspend_next
 | |
|             DEBUGGER__.waiting.push Thread.current
 | |
|             @suspend_next = false
 | |
|             true
 | |
|           end
 | |
|         }
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def trace?
 | |
|       @trace
 | |
|     end
 | |
| 
 | |
|     def set_trace(arg)
 | |
|       @trace = arg
 | |
|     end
 | |
| 
 | |
|     def stdout
 | |
|       DEBUGGER__.stdout
 | |
|     end
 | |
| 
 | |
|     def break_points
 | |
|       DEBUGGER__.break_points
 | |
|     end
 | |
| 
 | |
|     def display
 | |
|       DEBUGGER__.display
 | |
|     end
 | |
| 
 | |
|     def context(th)
 | |
|       DEBUGGER__.context(th)
 | |
|     end
 | |
| 
 | |
|     def set_trace_all(arg)
 | |
|       DEBUGGER__.set_trace(arg)
 | |
|     end
 | |
| 
 | |
|     def set_last_thread(th)
 | |
|       DEBUGGER__.set_last_thread(th)
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|     def debug_silent_eval(str, binding)
 | |
|       begin
 | |
|         eval(str, binding)
 | |
|       rescue StandardError, ScriptError
 | |
|         nil
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|     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
 | |
| 
 | |
|     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"
 | |
|         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
 | |
| 
 | |
|     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
 | |
| 
 | |
|     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
 | |
|               stdout.print "Trace off.\n"
 | |
|             end
 | |
| 
 | |
|           when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/
 | |
|             pos = $2
 | |
|             if $1
 | |
|               klass = debug_silent_eval($1, binding)
 | |
|               file = File.expand_path($1)
 | |
|             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"
 | |
|               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
 | |
| 
 | |
|           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
 | |
| 
 | |
|     def debug_print_help
 | |
|       stdout.print <<EOHELP
 | |
| 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
 | |
|   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
 | |
| 
 | |
|     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
 | |
|     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
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|     def format_frame(pos)
 | |
|       _, file, line, id = @frames[pos]
 | |
|       sprintf "#%d %s:%s%s\n", pos + 1, file, line,
 | |
|         (id ? ":in `#{id.id2name}'" : "")
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|     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
 | |
| 
 | |
|     def line_at(file, line)
 | |
|       lines = script_lines(file, line)
 | |
|       if lines and line = lines[line-1]
 | |
|         return line
 | |
|       end
 | |
|       return "\n"
 | |
|     end
 | |
| 
 | |
|     def debug_funcname(id)
 | |
|       if id.nil?
 | |
|         "toplevel"
 | |
|       else
 | |
|         id.id2name
 | |
|       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
 | |
|       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
 | |
|         end
 | |
|         suspend_all
 | |
|         debug_command(file, line, id, binding)
 | |
|       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...
 | |
|         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)
 | |
| 
 | |
|       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__
 | |
|     # Returns the IO used as stdout. Defaults to STDOUT
 | |
|     def stdout
 | |
|       @stdout
 | |
|     end
 | |
| 
 | |
|     # Sets the IO used as stdout. Defaults to STDOUT
 | |
|     def stdout=(s)
 | |
|       @stdout = s
 | |
|     end
 | |
| 
 | |
|     # Returns the display expression list
 | |
|     #
 | |
|     # See DEBUGGER__ for more usage
 | |
|     def display
 | |
|       @display
 | |
|     end
 | |
| 
 | |
|     # Returns the list of break points where execution will be stopped.
 | |
|     #
 | |
|     # See DEBUGGER__ for more usage
 | |
|     def break_points
 | |
|       @break_points
 | |
|     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 set_trace( arg )
 | |
|       MUTEX.synchronize do
 | |
|         make_thread_list
 | |
|         for th, in @thread_list
 | |
|           context(th).set_trace arg
 | |
|         end
 | |
|       end
 | |
|       arg
 | |
|     end
 | |
| 
 | |
|     def set_last_thread(th)
 | |
|       @last_thread = th
 | |
|     end
 | |
| 
 | |
|     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
 | |
| 
 | |
|     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
 | |
| 
 | |
|     def context(thread=Thread.current)
 | |
|       c = thread[:__debugger_data__]
 | |
|       unless c
 | |
|         thread[:__debugger_data__] = c = Context.new
 | |
|       end
 | |
|       c
 | |
|     end
 | |
| 
 | |
|     def interrupt
 | |
|       context(@last_thread).stop_next
 | |
|     end
 | |
| 
 | |
|     def get_thread(num)
 | |
|       th = @thread_list.key(num)
 | |
|       unless th
 | |
|         @stdout.print "No thread ##{num}\n"
 | |
|         throw :debug_error
 | |
|       end
 | |
|       th
 | |
|     end
 | |
| 
 | |
|     def thread_list(num)
 | |
|       th = get_thread(num)
 | |
|       if th == Thread.current
 | |
|         @stdout.print "+"
 | |
|       else
 | |
|         @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
 | |
| 
 | |
|     # 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
 | |
| 
 | |
|     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
 | |
| 
 | |
|   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
 |