summaryrefslogtreecommitdiffstats
path: root/languages/ruby/debugger/debuggee.rb
diff options
context:
space:
mode:
Diffstat (limited to 'languages/ruby/debugger/debuggee.rb')
-rw-r--r--languages/ruby/debugger/debuggee.rb1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/languages/ruby/debugger/debuggee.rb b/languages/ruby/debugger/debuggee.rb
new file mode 100644
index 00000000..38e2dea7
--- /dev/null
+++ b/languages/ruby/debugger/debuggee.rb
@@ -0,0 +1,1214 @@
+# 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>
+
+# Changes for the FreeRIDE IDE by Laurent JULLIARD. FreeRIDE uses
+# Distributed ruby (DRuby) to communicate with the debugger back
+# end. However, this can't interoperate with C++ in KDevelop and so
+# a Unix domain socket connection is used instead.
+
+# Adapted for KDevelop debugging
+# ------------------------------
+# begin : Mon Nov 1 2004
+# copyright : (C) 2004 by Richard Dale
+# email : Richard_Dale@tipitina.demon.co.uk
+
+if $SAFE > 0
+ STDERR.print "-r debug.rb is not available in safe mode\n"
+ exit 1
+end
+
+require 'tracer'
+require 'pp'
+require 'rbconfig'
+
+class Tracer
+ def Tracer.trace_func(*vars)
+ Single.trace_func(*vars)
+ end
+end
+
+# FreeRIDE/KDevelop must always intercept exits hence the exit! redefinition
+# at_exit calls the quit method to cleanly disconnect from the
+# FreeRIDE/KDevelop debugger client
+module Kernel
+ alias_method :exit!, :exit
+end
+
+BEGIN {
+ at_exit do
+ set_trace_func nil
+ DEBUGGER__.quit
+ end
+}
+
+SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
+
+class 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
+
+class Context
+ DEBUG_LAST_CMD = []
+
+ def readline(prompt_cmd, hist)
+ DEBUGGER__.client.readline(prompt_cmd)
+ 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
+ @trace_ruby = 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
+ return if Thread.critical
+ while (Thread.critical = true; @suspend_next)
+ DEBUGGER__.waiting.push Thread.current
+ @suspend_next = false
+ Thread.stop
+ end
+ Thread.critical = false
+ end
+
+ def trace?
+ @trace
+ end
+
+ def set_trace(arg)
+ @trace = arg
+ end
+
+ def trace_ruby?
+ @trace_ruby
+ end
+
+ def set_trace_ruby(arg)
+ @trace_ruby = 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
+ 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
+
+ # Temporarily change the pretty_print methods to not expand arrays
+ # and hashes, just give the length
+ def customize_debug_pp
+ Array.module_eval %q{
+ def pretty_print(pp)
+ pp.pp "Array (%d element(s))" % length
+ end
+ }
+
+ Hash.module_eval %q{
+ def pretty_print(pp)
+ pp.pp "Hash (%d element(s))" % length
+ end
+ }
+ end
+
+ # Restore the original pretty_print methods for arrays and hashes
+ def restore_debug_pp
+ Array.module_eval %q{
+ def pretty_print(q)
+ q.group(1, '[', ']') {
+ self.each {|v|
+ q.comma_breakable unless q.first?
+ q.pp v
+ }
+ }
+ end
+ }
+ Hash.module_eval %q{
+ def pretty_print(q)
+ q.pp_hash self
+ end
+ }
+ end
+
+ # Prevent the 'var *' commands from expanding Arrays and Hashes
+ # This could be done by redefining inspect, but that would affect
+ # everywhere not just here and in the pp command.
+ def debug_inspect(obj)
+ if obj.kind_of? Array
+ "Array (%d element(s))" % obj.length
+ elsif obj.kind_of? Hash
+ "Hash (%d element(s))" % obj.length
+ elsif obj.kind_of? String
+ str = obj.inspect
+ if str.length > 255
+ "String (length %d)" % obj.length
+ else
+ str
+ end
+ else
+ obj.inspect
+ end
+ end
+
+ def var_list(ary, binding)
+ ary.sort!
+ for v in ary
+ stdout.printf " %s => %s\n", v, debug_inspect(eval(v, binding))
+ end
+ end
+
+ def const_list(ary, obj)
+ ary.sort!
+ for c in ary
+ str = debug_inspect(obj.module_eval(c))
+ if c.to_s != str &&
+ str !~ /^Qt::|^KDE::/ && c.to_s !~ /@@classes$|@@cpp_names$|@@idclass$|@@debug_level$/ &&
+ c.to_s !~ /^DCOPMeta$|^Meta$|SCRIPT_LINES__|TRUE|FALSE|NIL|MatchingData/ &&
+ c.to_s !~ /^PLATFORM$|^RELEASE_DATE$|^VERSION$|SilentClient|SilentObject/ &&
+ c.to_s !~ /^Client$|^Context$|^DEBUG_LAST_CMD$|^MUTEX$|^Mutex$|^SimpleDelegater$|^Delegater$/ &&
+ c.to_s !~ /IPsocket|IPserver|UDPsocket|UDPserver|TCPserver|TCPsocket|UNIXserver|UNIXsocket/
+ if c.to_s == "ENV"
+ stdout.printf " %s => Hash (%d element(s))\n", c, obj.module_eval(c).length
+ else
+ stdout.printf " %s => %s\n", c, str
+ end
+ end
+ end
+ end
+
+ def debug_variable_info(input, binding)
+ case input
+ when /^\s*g(?:lobal)?$/
+ var_list(global_variables, binding)
+
+ when /^\s*l(?:ocal)?$/
+ var_list(eval("local_variables", binding) << "self", binding)
+
+ when /^\s*i(?:nstance)?\s+/
+ obj = debug_eval($', binding)
+ var_list(obj.instance_variables, obj.instance_eval{binding()})
+
+ when /^\s*cl(?:ass)?\s+/
+ obj = debug_eval($', binding)
+ unless obj.kind_of? Module
+ stdout.print "Should be Class/Module: ", $', "\n"
+ else
+ const_list(obj.class_variables, obj)
+ end
+
+ when /^\s*c(?:onst(?:ant)?)?\s+/
+ obj = debug_eval($', binding)
+ unless obj.kind_of? Module
+ stdout.print "Should be Class/Module: ", $', "\n"
+ else
+ const_list(obj.constants, obj)
+ 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
+ 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*trace_ruby(?:\s+(on|off))?$/
+ if defined?( $1 )
+ if $1 == 'on'
+ set_trace_ruby true
+ else
+ set_trace_ruby false
+ end
+ end
+
+ 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 = $1
+ 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\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+/
+ obj_name = $'
+ obj = debug_eval($', binding)
+ customize_debug_pp
+ if obj.kind_of? Array
+ obj.each_index { |i| stdout.printf "[%d]=%s\n", i.to_s, debug_inspect(obj[i]) }
+ elsif obj.kind_of? Hash or obj_name =~ /^ENV$/
+ # Special case ENV to print like a hash
+ obj.each { |key, value| stdout.printf "[%s]=%s\n", key.inspect, debug_inspect(value) }
+ elsif obj.kind_of?(String) && obj.inspect.length > 255
+ # Assume long strings contain packed data and show them as a
+ # sequence of 12 byte slices in hex
+ i = 0
+ while i < obj.length
+ j = (i + 12 < obj.length ? i + 12 : obj.length) - 1
+ stdout.printf "[%d..%d]=0x", i, j
+ for k in i..j
+ stdout.printf "%2.2x", obj[k]
+ end
+ stdout.printf " %s\n", obj[i..j].dump
+
+ i += 12
+ end
+ else
+ PP.pp(obj, stdout)
+ end
+ restore_debug_pp
+
+ when /^\s*p\s+/
+ stdout.printf "%s\n", debug_eval($', binding).inspect
+
+ 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] <an Exception> 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] cl[ass] <object> show class 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
+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)
+ 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
+ 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.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
+
+ if not trace_ruby? and
+ ( file =~ /#{Config::CONFIG['sitelibdir']}/ or
+ file =~ /#{Config::CONFIG['rubylibdir']}/ or
+ file =~ %r{/debuggee.rb} )
+ case event
+ when 'line'
+ frame_set_pos(file, line)
+
+ when 'call'
+ @frames.unshift [binding, file, line, id]
+
+ when 'c-call'
+ frame_set_pos(file, line)
+
+ when 'class'
+ @frames.unshift [binding, file, line, id]
+
+ when 'return', 'end'
+ @frames.shift
+
+ when 'end'
+ @frames.shift
+
+ when 'raise'
+ excn_handle(file, line, id, binding)
+
+ end
+ return
+ end
+
+ @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
+ #LJ reverse the test here because we always want the breakpoint reached
+ # message to be display. if stop_next is null *AND* there is also a break point
+ # the message will never display.
+ if check_break_points(file, nil, line, binding, id) or @stop_next == 0
+ # LJ this test doesn't make sense and cause troubles when
+ # on a line with a recursive call and a breakpoint on it (e.g factorial)
+ # or when in a while loop with one line only inside the loop
+ #
+ # RJD: reinstated the test with a check on whether '@frames.size'
+ # has changed to catch the recursive factorial case LJ describes
+ # above. The while loop problem still exists though
+ if [file, line, @frames.size] == @last
+ @stop_next = 1
+ else
+ @no_step = nil
+ suspend_all
+ debug_command(file, line, id, binding)
+ @last = [file, line, @frames.size]
+ 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)
+ 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 '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 SilentObject
+ def method_missing( msg_id, *a, &b ); end
+ end
+ SilentClient = SilentObject.new()
+ @client = SilentClient
+ @attached = false
+
+class << DEBUGGER__
+ def stdout
+ @stdout
+ end
+
+ def stdout=(s)
+ @stdout = s
+ end
+
+ def display
+ @display
+ end
+
+ def break_points
+ @break_points
+ end
+
+ def client
+ @client
+ end
+
+ def set_client( client )
+ @client = client
+ DEBUGGER__.stdout = Tracer.stdout = @client
+ end
+
+ def quit
+ #LJ flush STDOUT and ERR
+# @stdout.print "Quitting debugger"
+ STDERR.flush; STDOUT.flush
+ $stderr.flush; $stdout.flush
+# STDERR.close; STDOUT.close
+ detach
+ #DebugSvr.stop_service
+ end
+
+ def detach
+ @attached = false
+ @client.detach
+ set_client( SilentClient )
+ end
+
+ 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
+ Thread.critical = saved_crit
+ arg
+ end
+
+ def set_last_thread(th)
+ @last_thread = th
+ 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
+
+ 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
+
+ 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.index(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
+
+ 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
+
+require 'socket'
+
+ ##
+ # DEBUGGEE -> socket -> KDevelop
+ # The Client class holds all the methods invoked from the debuggee that send and
+ # receive data from KDevelop via the Unix domain socket.
+ #
+ class Client
+ def initialize(path)
+ @debugger = UNIXSocket.open(path)
+ @debugger.sync=true
+ end
+
+ def detach
+# @debugger.close
+ end
+
+ def printf( *args )
+ str = sprintf(*args)
+ @debugger.send(str, 0)
+ end
+
+ def print( *args )
+ str = args.to_s
+ @debugger.send(str, 0)
+ end
+
+ def <<( arg )
+ @debugger.send(arg, 0)
+ end
+
+ # Return next command
+ def readline(prompt_cmd)
+ @debugger.send(prompt_cmd, 0)
+ msg = @debugger.recvfrom(2048)
+ return msg[0]
+ end
+ end
+
+#stdout.printf "Debug.rb\n"
+#stdout.printf "Emacs support available.\n\n"
+
+STDERR.sync=true
+STDOUT.sync=true
+
+path = $stdin.gets.chomp
+
+DEBUGGER__.set_client( Client.new(path) )
+
+set_trace_func proc { |event, file, line, id, binding, klass, *rest|
+
+ # LJ make sure the file path is always absolute. It is needed by
+ # the Debugger plugin in KDevelop and can only be determined here
+ # in the context of the debugged process
+ file = File.expand_path(file)
+
+ DEBUGGER__.context.trace_func event, file, line, id, binding, klass
+}
+
+end