From de7161526014b781468cea5d84411e23be945f79 Mon Sep 17 00:00:00 2001 From: matz Date: Wed, 5 Jan 2000 04:41:21 +0000 Subject: 20000105 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@598 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/debug.rb | 948 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 625 insertions(+), 323 deletions(-) (limited to 'lib/debug.rb') diff --git a/lib/debug.rb b/lib/debug.rb index 244db021e2..d9c4864c16 100644 --- a/lib/debug.rb +++ b/lib/debug.rb @@ -1,399 +1,701 @@ -LINES__ = {} unless defined? LINES__ +if $SAFE > 0 + STDERR.print "-r debug.rb is not available in safe mode\n" + exit 1 +end +SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ class DEBUGGER__ - 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 - line.chomp! - line - end - USE_READLINE = false - end + class Mutex + def initialize + @locker = nil + @waiting = [] + @locked = false; + end - trap("INT") { DEBUGGER__::CONTEXT.interrupt } - $DEBUG = true - def initialize - @break_points = [] - @display = [] - @stop_next = 1 - @frames = [nil] - @last_file = nil - @last = [nil, nil] - @no_step = nil - @finish_pos = 0 - end + def locked? + @locked + end - DEBUG_LAST_CMD = [] + def lock + 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 interrupt - @stop_next = 1 + def unlock + 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 = [] - def debug_eval(str, binding) begin - val = eval(str, binding) - val - rescue - at = caller(0) - STDOUT.printf "%s:%s\n", at.shift, $! - for i in at - break if i =~ /`debug_(eval|command)'$/ #` - STDOUT.printf "\tfrom %s\n", i + 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 - end - def debug_command(file, line, id, binding) - frame_pos = 0 - binding_file = file - binding_line = line - previus_line = nil - if (ENV['EMACS'] == 't') - 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 - display_expressions(binding) - while input = readline("(rdb:-) ", true) - if input == "" - input = DEBUG_LAST_CMD[0] + def initialize + if Thread.current == Thread.main + @stop_next = 1 else - DEBUG_LAST_CMD[0] = input + @stop_next = 0 end + @last_file = nil + @last = [nil, nil] + @file = nil + @line = nil + @no_step = nil + @frames = [] + @finish_pos = 0 + end - case input - when /^b(?:reak)?\s+((?:[^:\n]+:)?.+)$/ - pos = $1 - if pos.index(":") - file, pos = pos.split(":") + def stop_next(n=1) + @stop_next = n + end + + def stdout + DEBUGGER__.stdout + end + def break_points + DEBUGGER__.break_points + end + def display + DEBUGGER__.display + end + + def debug_eval(str, binding) + begin + val = eval(str, binding) + val + rescue + at = eval("caller(0)", binding) + stdout.printf "%s:%s\n", at.shift, $!.to_s.sub(/\(eval\):1:(in `.*?':)?/, '') #` + for i in at + stdout.printf "\tfrom %s\n", i end - file = File.basename(file) - if pos =~ /^\d+$/ - pname = pos - pos = pos.to_i - else - pname = pos = pos.intern.id2name + throw :debug_error + end + end + + def var_list(ary, binding) + ary.sort! + if ary.size > 24 + f = open("|less", "w") + for v in ary + f.printf " %s => %s\n", v, eval(v, binding).inspect end - @break_points.push [true, 0, file, pos] - STDOUT.printf "Set breakpoint %d at %s:%s\n", @break_points.size, file, - pname - - when /^wat(?:ch)?\s+(.+)$/ - exp = $1 - @break_points.push [true, 1, exp] - STDOUT.printf "Set watchpoint %d\n", @break_points.size, exp - - when /^b(?:reak)?$/, /^info b(?:reak)?$/ - 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 + f.close + else + for v in ary + stdout.printf " %s => %s\n", v, eval(v, binding).inspect end - 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 + + def debug_variable_info(input, binding) + case input + when /^\s*g(?:lobal)?$/ + f = open("|less", "w") + var_list(global_variables, binding) + + when /^\s*l(?:ocal)?$/ + var_list(eval("local_variables", binding), binding) + + when /^\s*i(?:nstance)?\s+/ + obj = debug_eval($', binding) + var_list(obj.instance_variables, 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 - STDOUT.print "\n" - - when /^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 + 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($', binding) + unless obj.kind_of? Module + stdout.print "should be Class/Module: ", $', "\n" 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 + len = 0 + for v in obj.instance_methods.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 - when /^disp(?:lay)?\s+(.+)$/ - exp = $1 - @display.push.push [true, exp] - STDOUT.printf " %d: %s = %s\n", @display.size, exp, - debug_eval(exp, binding).to_s + 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 - when /^disp(?:lay)?$/, /^info disp(?:lay)?$/ + def debug_command(file, line, id, binding) + MUTEX.lock + DEBUGGER__.set_last_thread(Thread.current) + frame_pos = 0 + binding_file = file + binding_line = line + previous_line = nil + if (ENV['EMACS'] == 't') + 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) + while input = readline("(rdb:%d) "%thnum(), true) + catch (:debug_error) do + if input == "" + input = DEBUG_LAST_CMD[0] + stdout.print input, "\n" + else + DEBUG_LAST_CMD[0] = input + end + + case input + when /^\s*b(?:reak)?\s+((?:.*?+:)?.+)$/ + pos = $1 + if pos.index(":") + file, pos = pos.split(":") + end + file = File.basename(file) + if pos =~ /^\d+$/ + pname = pos + pos = pos.to_i + else + pname = pos = pos.intern.id2name + end + break_points.push [true, 0, file, pos] + stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, file, + pname + + when /^\s*wat(?:ch)?\s+(.+)$/ + exp = $1 + break_points.push [true, 1, exp] + stdout.printf "Set watchpoint %d\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.push [true, exp] + stdout.printf " %d: %s = %s\n", display.size, exp, + eval(exp, binding) rescue "--" + + 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)?$/ + MUTEX.unlock + return + + when /^\s*s(?:tep)?(?:\s+(\d+))?$/ + if $1 + lev = $1.to_i + else + lev = 1 + end + @stop_next = lev + return - when /^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 + 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 + return + + 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 - end - else - pos = pos.to_i - if @display[pos-1] - @display[pos-1][0] = false + 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.printf "#%d %s:%s\n", frame_pos, binding_file, binding_line + + 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.printf "#%d %s:%s\n", frame_pos, binding_file, binding_line + + when /^\s*fi(?:nish)?$/ + if frame_pos == 0 + stdout.print "\"finish\" not meaningful in the outermost frame.\n" + else + @finish_pos = @frames.size - frame_pos + frame_pos = 0 + return + end + + when /^\s*q(?:uit)?$/ + input = readline("really quit? (y/n) ", false) + exit if input == "y" + + 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 + MUTEX.unlock + return + end + + when /^\s*p\s+/ + p debug_eval($', binding) + else - STDOUT.printf "display expression %d is not defined\n", pos + v = debug_eval(input, binding) + p v unless (v == nil) end end + end + end + + def display_expressions(binding) + n = 1 + for d in display + if d[0] + stdout.printf "%d: %s = %s\n", n, d[1], debug_eval(d[1], binding).to_s + end + n += 1 + end + end - when /^c(?:ont)?$/ - return + def frame_set_pos(file, line) + if @frames[0] + @frames[0][1] = file + @frames[0][2] = line + end + end - when /^s(?:tep)?(?:\s+(\d+))?$/ - if $1 - lev = $1.to_i + def display_frames(pos) + pos += 1 + n = 0 + at = @frames + for bind, file, line, id in at + n += 1 + break unless bind + if pos == n + stdout.printf "--> #%d %s:%s%s\n", n, file, line, id != 0 ? ":in `#{id.id2name}'":"" else - lev = 1 + stdout.printf " #%d %s:%s%s\n", n, file, line, id != 0 ? ":in `#{id.id2name}'":"" + end + 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 debug_funcname(id) + if id == 0 + "toplevel" + else + id.id2name + end + end + + def check_break_points(file, pos, binding, id) + file = File.basename(file) + n = 1 + for b in break_points + if b[0] + if b[1] == 0 and b[2] == file and b[3] == pos + MUTEX.lock + stdout.printf "breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos + return true + elsif b[1] == 1 and debug_eval(b[2], binding) + MUTEX.lock + stdout.printf "watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos + return true + end + end + n += 1 + end + return false + end + + def excn_handle(file, line, id, binding) + if $!.type <= SystemExit + set_trace_func nil + exit + end + MUTEX.lock + fs = @frames.size + tb = caller(0)[-fs..-1] + + stdout.printf "%s\n", $! + if tb + for i in tb + stdout.printf "\tfrom %s\n", i end - @stop_next = lev - return + end + debug_command(file, line, id, binding) + end - when /^n(?:ext)?(?:\s+(\d+))?$/ - if $1 - lev = $1.to_i + def trace_func(event, file, line, id, binding, klass) + @file = file + @line = line + case event + when 'line' + frame_set_pos(file, line) + if !@no_step or @frames.size == @no_step + @stop_next -= 1 + elsif @frames.size < @no_step + @stop_next = 0 # break here before leaving... else - lev = 1 + # nothing to do. skipped. end - @stop_next = lev - @no_step = @frames.size - frame_pos - return - - when /^w(?:here)?$/, /^f(?:rame)?$/ - at = caller(0) - 0.upto(@frames.size - 1) do |n| - if frame_pos == n - STDOUT.printf "--> #%d %s\n", n, at[-(@frames.size - n)] + if @stop_next == 0 or check_break_points(file, line, binding, id) + if [file, line] == @last + @stop_next = 1 else - STDOUT.printf " #%d %s\n", n, at[-(@frames.size - n)] + @no_step = nil + debug_command(file, line, id, binding) + @last = [file, line] end end - when /^l(?:ist)?(?:\s+(.+))?$/ - if not $1 - b = previus_line ? previus_line + 10 : binding_line - 5 - e = b + 9 - elsif $1 == '-' - b = previus_line ? previus_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 - previus_line = b - STDOUT.printf "[%d, %d] in %s\n", b, e, binding_file - line_at(binding_file, binding_line) - if lines = LINES__[binding_file] and lines != true - n = 0 - b.upto(e) do |n| - if n > 0 && lines[n-1] - if n == binding_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", binding_file - end - - when /^up(?:\s+(\d+))?$/ - previus_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 = @frames[frame_pos] - info, binding_file, binding_line = frame_info(frame_pos) - STDOUT.printf "#%d %s\n", frame_pos, info - - when /^down(?:\s+(\d+))?$/ - previus_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" + when 'call' + @frames.unshift [binding, file, line, id] + if check_break_points(file, id.id2name, binding, id) or + check_break_points(klass.to_s, id.id2name, binding, id) + debug_command(file, line, id, binding) end - binding = @frames[frame_pos] - info, binding_file, binding_line = frame_info(frame_pos) - STDOUT.printf "#%d %s\n", frame_pos, info - when /^fi(?:nish)?$/ - if frame_pos == 0 - STDOUT.print "\"finish\" not meaningful in the outermost frame.\n" - else - @finish_pos = @frames.size - frame_pos - frame_pos = 0 - return + when 'c-call' + frame_set_pos(file, line) + + when 'class' + @frames.unshift [binding, file, line, id] + + when 'return', 'end' + @frames.shift + if @frames.size == @finish_pos + @stop_next = 1 end - when /^q(?:uit)?$/ - input = readline("really quit? (y/n) ", false) - exit if input == "y" + when 'end' + @frames.shift - when /^p\s+/ - p debug_eval($', binding) + when 'raise' + excn_handle(file, line, id, binding) - else - v = debug_eval(input, binding) - p v unless (v == nil) end + @last_file = file end end - - def display_expressions(binding) - n = 1 - for d in @display - if d[0] - STDOUT.printf "%d: %s = %s\n", n, d[1], debug_eval(d[1], binding).to_s - end - n += 1 + + trap("INT") { DEBUGGER__.interrupt } +# $DEBUG = true + @last_thread = Thread::main + @max_thread = 1 + @thread_list = {Thread::main => 1} + @break_points = [] + @display = [] + @stdout = STDOUT + + class <