You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1215 lines
27 KiB
1215 lines
27 KiB
# 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
|