# debuggee.rb
#
# $Id: debuggee.rb,v 1.13 2004/11/20 21:10:39 ljulliar Exp $
# Copyright (c) 2000 NAKAMURA, Hiroshi
#
# debuggee.rb is copyrighted free software by NAKAMURA, Hiroshi.
# You can redistribute it and/or modify it under the same term as Ruby.
#
# Many part of debuggee.rb is copied from debug.rb in ruby 1.6.7 and recycled.
# Those part is copyrighted by its authors.
#
# Upgraded to debug.rb from Ruby 1.6.7 (debug 1.20.2.5) by Laurent JULLIARD
# debug.rb
#
# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
# Copyright (C) 2000 Information-technology Promotion Agency, Japan
require 'tracer'
class Tracer
def Tracer.trace_func(*vars)
Single.trace_func(*vars)
end
end
# FreeRIDE must always intercept exits hence the exit! redefinition
# at_exit calls the quit method to cleanly disconnect from the
# FreeRIDE 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__
###
# Redefine the DRb run method to mark all DRb threads with
# a 'hidden' flag so they are not taken into account in the debugging
# process
# (drb is included directly from the command line to avoid a -I option
# pointing to the FR redist directory that could interfere with the include
# pathes of the debugged process.
#
module DRb
class DRbServer
# We need to override the run method to mark the Drb threads
# with a special name so that they remain hidden from the user
# view
if DRb.const_defined? "DRbMessage"
# we are using DRb 2.0.x
def run
top_th = Thread.start do
begin
while true
sub_th = main_loop
sub_th[:__debugger_hidden__] = true
sub_th
end
ensure
@protocol.close if @protocol
kill_sub_thread
end
end
top_th[:__debugger_hidden__] = true
top_th
end
else
# We are using DRb 1.3
def run
top_th = Thread.start do
begin
while true
sub_th = proc
sub_th[:__debugger_hidden__] = true
sub_th
end
ensure
@soc.close if @soc
kill_sub_thread
end
end
top_th[:__debugger_hidden__] = true
top_th
end
end
end # class DRbServer
end # module DRb
class DEBUGGER__
class Mutex
def initialize
@locker = nil
@waiting = []
@locked = false;
end
def locked?
@locked
end
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 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 = []
begin
require 'readline'
def readline(prompt, hist)
Readline::readline(prompt, hist)
end
rescue LoadError
def readline(prompt, hist)
STDOUT.print prompt
STDOUT.flush
line = STDIN.gets
exit unless line
line.chomp!
line
end
USE_READLINE = false
end
def initialize
if Thread.current == Thread.main
@stop_next = 1
else
@stop_next = 0
end
@last_file = nil
@last = [nil, nil]
@file = nil
@line = nil
@no_step = nil
@frames = []
@frame_pos = 0 #LJ - for FR
@finish_pos = 0
@trace = false
@catch = ["StandardError"] #LJ - for FR
@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
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 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 attached?
DEBUGGER__.attached?
end
def detach
DEBUGGER__.detach
end
def set_last_thread(th)
DEBUGGER__.set_last_thread(th)
end
def debug_eval(str, binding)
begin
val = eval(str, binding)
val
rescue StandardError, ScriptError
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
throw :debug_error
end
end
def fr_debug_eval(str, binding)
begin
val = eval(str, binding)
out = val.inspect unless val.nil?
rescue StandardError, ScriptError
at = eval("caller(0)", binding)
out = sprintf("%s:%s\n", at.shift, $!.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')) #`
for i in at
out << sprintf("\tfrom %s\n", i)
end
end
out
end
def debug_silent_eval(str, binding)
begin
val = eval(str, binding)
val
rescue StandardError, ScriptError
nil
end
end
def var_list(ary, binding)
ary.sort!
for v in ary
stdout.printf " %s => %s\n", v, eval(v, binding).inspect
end
end
def debug_variable_info(input, binding)
case input
when /^\s*g(?:lobal)?$/
stdout.global_vars(global_variables)
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, obj.instance_eval{binding()})
when /^\s*c(?:onst(?:ant)?)?\s+/
obj = debug_eval($', binding)
unless obj.kind_of? Module
stdout.print "Should be Class/Module: ", $', "\n"
else
var_list(obj.constants, obj.module_eval{binding()})
end
end
end
def 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)
unless attached?
MUTEX.unlock
resume_all
return
end
@frame_pos = 0
binding_file = file
binding_line = line
previous_line = nil
# LJ - FR commented out
#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
stdout.printf_line(binding_file, binding_line)
@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 == ""
input = DEBUG_LAST_CMD[0]
stdout.print input, "\n"
else
DEBUG_LAST_CMD[0] = input
end
case input
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 = $1
if pos.index(":")
file, pos = pos.split(":")
end
add_break_point(file, pos) #LJ
stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, file, pname
when /^\s*wat(?:ch)?\s+(.+)$/
exp = $1
add_watch_point(exp) # LJ
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
#LJ input = readline("Clear all breakpoints? (y/n) ", false)
#LJ if input == "y"
for b in break_points
b[0] = false
end
#LJ 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.printf "#%d %s:%s\n", @frame_pos+1, 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+1, binding_file, binding_line
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*d(?:etach)?$/
input = readline("really detach? (y/n) ", false)
if input == "y"
detach
return # Continue executing...
end
when /^\s*cat(?:ch)?(?:\s+(.+))?$/
if $1
excn = $1
if excn == 'off'
@catch = nil
stdout.print "Clear catchpoint.\n"
else
@catch = excn.split(',')
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)?$/
#LJ input = readline("Really quit? (y/n) ", false)
#LJ if input == "y"
#LJ (see at_exit) DEBUGGER__.quit
exit! # exit -> exit!: No graceful way to stop threads...
#LJ 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*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 unless (v == nil)
end
end
end
MUTEX.unlock
resume_all
end
def debug_print_help
stdout.print <<EOHELP
Debugger help v.-0.002b
Commands
b[reak] [file|method:]<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] 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)
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 ? ":in `#{id.id2name}'":""
else
stdout.printf " #%d %s:%s%s\n", n, file, line, id ? ":in `#{id.id2name}'":""
end
end
end
# LJ - FR
def fr_frame_list_all
pos = @frame_pos+1
n = 0
at = @frames
fr_list = []
for bind, file, line, id in at
n += 1
break unless bind
fr_list << [n, file, line, id ? id.id2name : '' , (pos == n)]
end
return fr_list
end
# LJ - FR
def fr_select_frame(level)
@frame_pos = level-1
# a bit of paranoia...
@frame_pos = 0 if @frame_pos < 0 # at stack bottom
@frame_pos = @frames.size - 1 if @frame_pos >= @frames.size #at toplelel
return @frame_pos+1
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, pos, binding, id)
return false if break_points.empty?
#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
stdout.printf_breakpoint(n, debug_funcname(id), file, pos)
# LJ Delete once reached if temporary breakpoints
delete_break_point(n) if b[4]
return true
elsif b[1] == 1
if debug_silent_eval(b[2], binding)
stdout.printf_watchpoint(n, debug_funcname(id), file, pos)
return true
end
end
end
n += 1
end
return false
end
# LJ - Added for FreeRIDE.
def add_break_point(file,pos, temp = false)
# LJ - commented out because the basename is not enough in case we
# have 2 files with the same names but with distinct path
# 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, temp]
return break_points.size
end
# LJ - Added for FreeRIDE.
def delete_break_point(idx)
if break_points[idx-1]
break_points[idx-1][0] = false
return true
else
return false
end
end
# LJ - Added for FreeRIDE.
def add_watch_point(exp)
break_points.push [true, 1, exp]
return break_points.size
end
# LJ - Added for FreeRIDE.
def delete_watch_point(idx)
delete_break_point(idx)
end
def excn_handle(file, line, id, binding)
excn_out = []
excn_out << sprintf("%s:%d: `%s' (%s)\n", file, line, $!, $!.class)
if $!.class <= SystemExit
set_trace_func nil
#LJ (see at_exit) DEBUGGER__.quit
exit
end
if @catch and ($!.class.ancestors.find { |e| @catch.include?(e.to_s) })
fs = @frames.size
tb = caller(0)[-fs..-1]
if tb
for i in tb
excn_out << sprintf("\tfrom %s\n", i)
end
end
stdout.printf_excn(excn_out,false)
suspend_all
debug_command(file, line, id, binding)
else
stdout.printf_excn(excn_out, true)
end
end
def trace_func(event, file, line, id, binding, klass)
#STDOUT.print "#{File.basename(file)}:#{line},c: #{Thread.current}/#{Thread.current.status}/#{Thread.current[:__debugger_hidden__]}, m: #{Thread.main}, svr: #{DebugSvr.thread}\n"
##
# Don't step through our DRb code. the internal mechanics of the remote debugger
# must remain invisible to the end user
#
# FIXME: make sure we are not going to step through code in our debuggee.rb file
# this only happens when calling the at_exit method to finish the debugger
# just before we reset the trace function (see at top of file) -- Don't know if there is
# a workaround for that ??
return if (Thread.current[:__debugger_hidden__]) ||
(File.basename(file) == 'debuggee.rb')
#STDOUT.print "#{event}:#{line}(fsz=#{@frames.size}, no_step=#{@no_step}, stop_next=#{@stop_next})\n"
Tracer.trace_func(event, file, line, id, binding, klass) if trace?
context(Thread.current).check_suspend
@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
# 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, 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
#if [file, line] == @last
# @stop_next = 1
#else
@no_step = nil
suspend_all
debug_command(file, line, id, binding)
@last = [file, line]
#end
end
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)
suspend_all
debug_command(file, line, id, binding)
end
when 'c-call'
frame_set_pos(file, line)
if id == :require and klass == Kernel
@frames.unshift [binding, file, line, id]
else
frame_set_pos(file, line)
end
when 'c-return'
if id == :require and klass == Kernel
if @frames.size == @finish_pos
@stop_next = 1
@finish_pos = 0
end
@frames.shift
end
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
@loaded_files = {}
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
# LJ - FR
def last_thread
@last_thread
end
def prompt( str )
@client.prompt( str )
end
def attach( debugger )
unless @attached
set_client( debugger )
@attached = true
interrupt
"#{ $0 } on #{ DebugSvr.uri }"
else
# Does NOT support multiple debugger.
# raise "Already attached."
false
end
end
def detach
@attached = false
@client.detach
set_client( SilentClient )
end
##
# add a file to the list of files loaded by the debugger
# LJ for FreeRIDE
def add_loaded_file (file)
@loaded_files[file] = true
end
##
# check whether a given file is already loaded in the debugger are stored
# LJ for FreeRIDE
def check_loaded_file(file)
@loaded_files.has_key? file
end
# LJ for FreeRIDE
def client
@client
end
def set_client( debugger )
@client = Client.new( debugger )
DEBUGGER__.stdout = Tracer.stdout = @client
end
def attached?
@attached
end
def quit
#LJ flush STDOUT and ERR
STDERR.flush; STDOUT.flush
detach
DebugSvr.stop_service
end
def waiting
@waiting
end
def set_trace( arg )
Thread.critical = true
make_thread_list
for th, in @thread_list
context(th).set_trace arg
end
Thread.critical = false
end
def set_last_thread(th)
@last_thread = th
end
def suspend
Thread.critical = true
make_thread_list
for th, in @thread_list
next if th == Thread.current
context(th).set_suspend
end
Thread.critical = false
# Schedule other threads to suspend as soon as possible.
Thread.pass
end
def resume
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 = false
# 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
# LJ - FreeRIDE
# build an array with thread info: thread num, thread objet, current?, status, thread name, file, line
def fr_thread_list(num)
th_info = []
th = get_thread(num)
th_info << num
th_info << th.to_s
th_info << (th == Thread.current)
th_info << th.status
th_info << th["name"] # thread name if defined by user
th_info << context(th).instance_eval{@file}
th_info << context(th).instance_eval{@line}
return th_info
end
def thread_list_all
for th in @thread_list.values.sort
thread_list(th)
end
end
# LJ - FreeRIDE
# build an array with all threads info
def fr_thread_list_all
make_thread_list()
all_th_info = []
for th in @thread_list.values.sort
all_th_info << fr_thread_list(th)
end
return all_th_info
end
def make_thread_list
hash = {}
for th in Thread::list
next if (th[:__debugger_hidden__])
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
class Context
def readline( prompt, hist )
DEBUGGER__.prompt( prompt )
end
end
##
# DEBUGGEE -> DRB -> FreeRIDE
# The Client class holds all the methods invoked from the debuggee and executed in the
# FreeRIDE context. Method invocation goes over DRb, is executed in FreeRIDE
# and returns to the debuggee
#
class Client
def initialize( debugger )
@debugger = debugger
end
def prompt( str )
@debugger.prompt( str )
end
def detach
@debugger.quit
end
def printf( *args )
@debugger.printf( *args )
end
def printf_line(file,line)
@debugger.printf_line(file,line)
end
def printf_excn(excn_trace, ignored)
@debugger.printf_excn(excn_trace, ignored)
end
def printf_breakpoint(n, funcname, file, line)
@debugger.printf_breakpoint(n, funcname, file, line)
end
def printf_watchpoint(n, funcname, file, line)
@debugger.printf_watchpoint(n, funcname, file, line)
end
def print( *args )
@debugger.print( *args )
end
def global_vars(gv)
@debugger.global_vars(gv)
end
def file_loaded(file)
@debugger.file_loaded(file)
end
end
##
# FreeRIDE -> DRB -> DEBUGGEE
# The Front class holds all the methods invoked from FreeRIDE to collect information
# from the debugged process. Method invocation goes over DRb, is executed in the
# context of the debugger and returned to FreeRIDE.
#
class Front
include DRbUndumped
def attach( debugger )
DEBUGGER__.attach( debugger )
end
def signal( type )
case type
when 'INT'
DEBUGGER__.interrupt
else
Process.kill( type, Process.pid )
end
end
# LJ - FreeRIDE
def get_break_points()
DEBUGGER__.break_points
end
# LJ - FreeRIDE
def add_break_point(file,line, temp = false)
return DEBUGGER__.context.add_break_point(file, line.to_s, temp)
end
# LJ - FreeRIDE
def delete_break_point(pos)
return DEBUGGER__.context.delete_break_point(pos)
end
# LJ - FreeRIDE
def add_watch_point(exp)
return DEBUGGER__.context.add_watch_point(exp)
end
# LJ - FreeRIDE
def delete_watch_point(pos)
return DEBUGGER__.context.delete_watch_point(pos)
end
# LJ - FreeRIDE
def fr_thread_list_all()
return DEBUGGER__.fr_thread_list_all
end
# LJ - FreeRIDE
def fr_frame_list_all()
return DEBUGGER__.context(DEBUGGER__.last_thread).fr_frame_list_all
end
# LJ - FreeRIDE
def fr_select_frame(index)
return DEBUGGER__.context(DEBUGGER__.last_thread).fr_select_frame(index)
end
# LJ - FreeRIDE
def fr_local_variables()
lv_ary = {}
binding, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread).current_frame
for v in eval("local_variables", binding)
lv_ary[v] = eval(v, binding).inspect
end
lv_ary
end
# LJ - FreeRIDE
def fr_global_variables()
gv_ary = {}
binding, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread).current_frame
global_variables.each { |v| gv_ary[v] = eval(v, binding).inspect }
gv_ary
end
# LJ - FreeRIDE
def fr_eval_expr(expr)
binding, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread).current_frame
v = DEBUGGER__.context(DEBUGGER__.last_thread).fr_debug_eval(expr,binding)
return v
end
end
# LJ - FreeRIDE
class Context
def current_frame
@frames[@frame_pos]
end
end
# LJ - On Windows redirect STDERR to STDOUT because there is
# no popen3 call available on mswin32
#if RUBY_PLATFORM =~ /(mswin32|mingw32)/
STDERR.reopen(STDOUT)
#end
#LJ - Give a name to the main Thread
Thread.main["name"] = 'Main'
# LJ - forces STDERR and STDOUT to synchronize mode (FreeRIDE)
STDERR.sync=true
STDOUT.sync=true
DebugSvr = DRb.start_service( nil, Front.new() )
#STDOUT.print "#{DebugSvr.uri},#{Process.pid}\n"
File.open(ARGV.pop,"w") { |file| file.print "#{DebugSvr.uri},#{Process.pid}\n" }
while not attached?
sleep 1
end
stdout.printf "Debug.rb\n"
stdout.printf "Emacs support available.\n\n"
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 FreeRIDE and can only be determined here
# in the context of the debugged process
file = File.expand_path(file)
# if file is loaded for the first time then ask our client FreeRIDE to set
# all the breakpoints
if (!DEBUGGER__.check_loaded_file(file))
DEBUGGER__.add_loaded_file(file)
DEBUGGER__.client.file_loaded(file)
end
DEBUGGER__.context.trace_func event, file, line, id, binding, klass
}
end
syntax highlighted by Code2HTML, v. 0.9.1