require 'ruby_debug.so' require 'pp' SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ module Debugger class Mutex def initialize @locker = nil @waiting = [] @locked = false; end def locked? @locked end 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 @stdout = STDOUT @display = [] @trace_proc = nil class Context private 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 stdout Debugger.stdout end def at_breakpoint(breakpoint, method_name) n = Debugger.breakpoints.index(breakpoint) + 1 stdout.printf "Breakpoint %d, %s at %s:%s\n", n, method_name, breakpoint.source, breakpoint.pos end def at_catchpoint frames = Debugger.current_context.frames stdout.printf "%s:%d: `%s' (%s)\n", frames[0].file, frames[0].line, $!, $!.class fs = frames.size tb = caller(0)[-fs..-1] if tb for i in tb stdout.printf "\tfrom %s\n", i end end end def at_tracing(file, line) if Debugger.trace_proc Debugger.trace_proc.call(self.thnum, file, line) else stdout.puts sprintf("Tracing(%d):%s:%s", self.thnum, file, line) end end def debug_command(file, line, id, binding) MUTEX.lock frame_pos = 0 binding_file = file binding_line = line previous_line = nil stdout.printf "%s:%d:%s", binding_file, binding_line, line_at(binding_file, binding_line) display_expressions(binding) prompt = true while prompt and input = readline("(rdb:%d) " % Debugger.thnum, true) catch(:debug_error) do next if input == '' case input when /^\s*s(?:tep)?(?:\s+(\d+))?$/ self.stop_next = $1 ? $1.to_i : 1 prompt = false when /^\s*c(?:ont)?$|^\s*r(?:un)?$/ prompt = false when /^\s*v(?:ar)?\s+/ debug_variable_info($', binding) 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*n(?:ext)?(?:\s+(\d+))?$/ steps = $1 ? $1.to_i : 1 self.step_over steps, self.frames.size - frame_pos prompt = false when /^\s*up(?:\s+(\d+))?$/ previous_line = nil frame_pos += $1 ? $1.to_i : 1 if frame_pos >= self.frames.size frame_pos = self.frames.size - 1 stdout.puts "At toplevel" end frame = self.frames[frame_pos] binding, binding_file, binding_line = frame.binding, frame.file, frame.line stdout.print format_frame(frame, frame_pos) when /^\s*down(?:\s+(\d+))?$/ previous_line = nil frame_pos -= $1 ? $1.to_i : 1 if frame_pos < 0 frame_pos = 0 stdout.print "At stack bottom\n" end frame = self.frames[frame_pos] binding, binding_file, binding_line = frame.binding, frame.file, frame.line stdout.print format_frame(frame, frame_pos) when /^\s*fin(?:ish)?$/ if frame_pos == self.frames.size stdout.print "\"finish\" not meaningful in the outermost frame.\n" else self.stop_frame = self.frames.size - frame_pos frame_pos = 0 prompt = false end when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/ pos = $2 if $1 klass = debug_silent_eval($1, binding) file = $1 end if pos =~ /^\d+$/ pname = pos pos = pos.to_i else pname = pos = pos.intern.id2name end Debugger.add_breakpoint klass || file, pos stdout.printf "Set breakpoint %d at %s:%s\n", Debugger.breakpoints.size, klass || file, pname when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/ pos = $2.intern.id2name klass = debug_eval($1, binding) Debugger.add_breakpoint klass, pos stdout.printf "Set breakpoint %d at %s.%s\n", Debugger.breakpoints.size, klass, pos when /^\s*b(?:reak)?$/ unless Debugger.breakpoints.empty? stdout.print "Breakpoints:\n" Debugger.breakpoints.each_with_index do |b, n| stdout.printf " %d %s:%s\n", n+1, b.source, b.pos end stdout.puts else stdout.print "No breakpoints\n" end when /^\s*del(?:ete)?(?:\s+(\d+))?$/ pos = $1 unless pos input = readline("Clear all breakpoints? (y/n) ", false) if input == "y" Debugger.breakpoints.clear end else pos = pos.to_i unless Debugger.breakpoints.delete_at(pos-1) stdout.printf "Breakpoint %d is not defined\n", pos end end when /^\s*th(?:read)?\s+/ if Debugger.debug_thread_info($') == :cont prompt = false end when /^\s*m(?:ethod)?\s+/ debug_method_info($', binding) when /^\s*pp\s+/ PP.pp(debug_eval($', binding), stdout) when /^\s*p\s+/ stdout.printf "%s\n", debug_eval($', binding).inspect when /^\s*h(?:elp)?$/ debug_print_help() 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*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*cat(?:ch)?(?:\s+(.+))?$/ if $1 excn = $1 if excn == 'off' Debugger.catchpoint = nil stdout.print "Clear catchpoint.\n" else Debugger.catchpoint = excn stdout.printf "Set catchpoint %s.\n", excn end else if Debugger.catchpoint stdout.printf "Catchpoint %s.\n", Debugger.catchpoint else stdout.print "No catchpoint.\n" end end when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/ if defined?( $2 ) Debugger.tracing = $1 == 'on' elsif defined?( $1 ) self.tracing = $1 == 'on' end if Debugger.tracing || self.tracing stdout.print "Trace on.\n" else stdout.print "Trace off.\n" end else v = debug_eval(input, binding) stdout.printf "%s\n", v.inspect end end end MUTEX.unlock Debugger.resume end def debug_print_help stdout.print < b[reak] [class.] set breakpoint to some position cat[ch] set catchpoint to an exception disp[lay] add expression into display expression list undisp[lay][ nnn] delete one particular or all display expressions b[reak] list breakpoints cat[ch] show catchpoint del[ete][ nnn] delete some or all breakpoints 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 q[uit] exit from debugger v[ar] g[lobal] show global variables v[ar] l[ocal] show local variables v[ar] i[nstance] show instance variables of object v[ar] c[onst] show constants of object m[ethod] i[nstance] show methods of object m[ethod] 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]] switch thread context to nnn th[read] stop stop thread nnn th[read] resume resume thread nnn p expression evaluate expression and print its value pp expression evaluate expression and print its value h[elp] print this help 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 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 end end def debug_silent_eval(str, binding) begin eval(str, binding) rescue StandardError, ScriptError nil 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 display_frames(pos) self.frames.each_with_index do |frame, idx| if idx == pos stdout.print "--> " else stdout.print " " end stdout.print format_frame(frame, idx) end end def format_frame(frame, pos) bind, file, line, id = frame.binding, frame.file, frame.line, frame.id sprintf "#%d %s:%s%s\n", pos + 1, file, line, (id ? ":in `#{id.id2name}'" : "") end def var_list(ary, binding) ary.sort! for v in ary stdout.printf " %s => %s\n", v, eval(v, binding).inspect end 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 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 end return "\n" end def display Debugger.display end end class << self def stdout @stdout end def stdout=(v) @stdout = v end def set_trace_proc &block @trace_proc = block end def trace_proc @trace_proc end def display @display 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 display_context(c) if c.thread == Thread.current stdout.print "+" else stdout.print " " end stdout.printf "%d ", c.thnum stdout.print c.thread.inspect, "\t" last_frame = c.frames.first if last_frame @stdout.print last_frame.file,":",last_frame.line end @stdout.print "\n" end def display_all_contexts threads = contexts.sort_by{|c| c.thnum}.each do |c| display_context(c) end end def get_context(thnum) contexts.find{|c| c.thnum = thnum} end def debug_thread_info(input) case input when /^l(?:ist)?/ display_all_contexts when /^c(?:ur(?:rent)?)?$/ display_context(current_context) when /^(?:sw(?:itch)?\s+)?(\d+)/ c = get_context($1.to_i) if c == current_context stdout.print "It's the current thread.\n" else display_context(c) c.stop_next = 1 c.thread.run return :cont end when /^stop\s+(\d+)/ c = get_context($1.to_i) if c == current_context stdout.print "It's the current thread.\n" elsif c.thread.stop? stdout.print "Already stopped.\n" else display_context(c) c.set_suspend end when /^resume\s+(\d+)/ c = get_context($1.to_i) if c == current_context stdout.print "It's the current thread.\n" elsif !c.thread.stop? stdout.print "Already running." else display_context(c) c.thread.run end end end end end module Kernel def debugger(steps = 1) Debugger.current_context.stop_next = steps end end Debugger.start