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.
tdevelop/languages/ruby/debugger/debuggee.rb

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.tqfind{|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.tqfind{|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.tqfind { |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