--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Ruby/Debuggee.rb Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,1208 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2005 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +# Debuggee.rb is based in parts on debug.rb from Ruby and debuggee.rb. +# Original copyrights of these files follow below. +# +# debug.rb +# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. +# Copyright (C) 2000 Information-technology Promotion Agency, Japan +# +# debuggee.rb +# Copyright (c) 2000 NAKAMURA, Hiroshi + +=begin edoc +File implementing the real debugger, which is connected to the IDE frontend. +=end + +require 'DebugQuit' +require 'rbconfig' + +class DEBUGGER__ +=begin edoc +Class implementing the real debugger. +=end + class Mutex +=begin edoc +Class implementing a mutex. +=end + def initialize +=begin edoc +Constructor +=end + @locker = nil + @waiting = [] + @locked = false; + end + + def locked? +=begin edoc +Method returning the locked state. + +@return flag indicating the locked state (boolean) +=end + @locked + end + + def lock +=begin edoc +Method to lock the mutex. + +@return the mutex +=end + 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 +=begin edoc +Method to unlock the mutex. + +@return the mutex +=end + 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 +=begin edoc +Class defining the current execution context. +=end + def initialize +=begin edoc +Constructor +=end + if Thread.current == Thread.main + @stop_next = 1 + else + @stop_next = 0 + end + @last_file = 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) +=begin edoc +Method to set the next stop point (i.e. stop at next line). + +@param counter defining the stop point (int) +=end + @stop_next = n + end + + def step_over(n=1) +=begin edoc +Method to set the next stop point skipping function calls. + +@param counter defining the stop point (int) +=end + @stop_next = n + @no_step = @frames.size - @frame_pos + end + + def step_out +=begin edoc +Method to set the next stop point after the function call returns. +=end + if @frame_pos != @frames.size + @finish_pos = @frames.size - @frame_pos + @frame_pos = 0 + @stop_next -= 1 + end + end + + def step_continue +=begin edoc +Method to continue execution until next breakpoint or watch expression. +=end + @stop_next = 1 + @no_step = -1 + end + + def step_quit +=begin edoc +Method to stop debugging. +=end + raise DebugQuit.new + end + + def set_suspend +=begin edoc +Method to suspend all threads. +=end + @suspend_next = true + end + + def clear_suspend +=begin edoc +Method to clear the suspend state. +=end + @suspend_next = false + end + + def suspend_all +=begin edoc +Method to suspend all threads. +=end + DEBUGGER__.suspend + end + + def resume_all +=begin edoc +Method to resume all threads. +=end + DEBUGGER__.resume + end + + def check_suspend +=begin edoc +Method to check the suspend state. +=end + while (Thread.critical = true; @suspend_next) + DEBUGGER__.waiting.push Thread.current + @suspend_next = false + Thread.stop + end + Thread.critical = false + end + + def stdout +=begin edoc +Method returning the stdout object. + +@return reference to the stdout object +=end + DEBUGGER__.stdout + end + + def break_points +=begin edoc +Method to return the list of breakpoints + +@return Array containing all breakpoints. +=end + DEBUGGER__.break_points + end + + def context(th) +=begin edoc +Method returning the context of a thread. + +@param th thread object to get the context for +@return the context for the thread +=end + DEBUGGER__.context(th) + end + + def attached? +=begin edoc +Method returning the attached state. + +@return flag indicating, whether the debugger is attached to the IDE. +=end + DEBUGGER__.attached? + end + + def set_last_thread(th) +=begin edoc +Method to remember the last thread. + +@param th thread to be remembered. +=end + DEBUGGER__.set_last_thread(th) + end + + def debug_silent_eval(str, binding_) +=begin edoc +Method to eval a string without output. + +@param str String containing the expression to be evaluated +@param binding_ the binding for the evaluation +@return the result of the evaluation +=end + val = eval(str, binding_) + val + end + + def thnum +=begin edoc +Method returning the thread number of the current thread. + +@return thread number of the current thread. +=end + 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_) +=begin edoc +Method to execute the next debug command. +=end + MUTEX.lock + set_last_thread(Thread.current) + unless attached? + MUTEX.unlock + resume_all + return + end + @frame_pos = 0 + @frames[0] = [binding_, file, line, id] + stdout.printf_line(@frames) + MUTEX.unlock + resume_all + eventLoop + end + + def frame_set_pos(file, line) +=begin edoc +Method to set the frame position of the current frame. +=end + if @frames[0] + @frames[0][1] = file + @frames[0][2] = line + end + end + + def check_break_points(file, pos, binding_, id) +=begin edoc +Method to check, if the given position contains an active breakpoint. + +@param file filename containing the currently executed line (String) +@param pos line number currently executed (int) +@param binding_ current binding object +@param id (ignored) +@return flag indicating an active breakpoint (boolean) +=end + # bp[0] enabled flag + # bp[1] 0 = breakpoint, 1 = watch expression + # bp[2] filename + # bp[3] linenumber + # bp[4] temporary flag + # bp[5] condition + # bp[6] ignore count + # bp[7] special condition + # bp[8] hash of special values + return false if break_points.empty? + for b in break_points + if b[0] + if b[1] == 0 and b[2] == file and b[3] == pos # breakpoint + # Evaluate condition + if b[5] + begin + if debug_silent_eval(b[5], binding_) + if b[6] == 0 # ignore count reached + # Delete once reached if temporary breakpoint + clear_break_point(file, pos) if b[4] + return true + else + b[6] -= 1 + end + end + rescue StandardError, ScriptError + nil + end + else + if b[6] == 0 # ignore count reached + # Delete once reached if temporary breakpoint + clear_break_point(file, pos) if b[4] + return true + else + b[6] -= 1 + end + end + elsif b[1] == 1 # watch expression + begin + bd = @frame_pos + val = debug_silent_eval(b[5], binding_) + if b[7].length() > 0 + if b[7] == "??created??" + if b[8][bd][0] == false + b[8][bd][0] = true + b[8][bd][1] = val + return true + else + next + end + end + b[8][bd][0] = true + if b[7] == "??changed??" + if b[8][bd][1] != val + b[8][bd][1] = val + if b[8][bd][2] > 0 + b[8][bd][2] -= 1 + next + else + return true + end + else + next + end + end + next + end + if val + if b[6] == 0 # ignore count reached + # Delete once reached if temporary breakpoint + clear_watch_point(b[2]) if b[4] + return true + else + b[6] -= 1 + end + end + rescue StandardError, ScriptError + if b[7].length() > 0 + if b[8][bd] + b[8][bd][0] = false + else + b[8][bd] = [false, nil, b[6]] + end + else + val = nil + end + end + end + end + end + return false + end + + def clear_break_point(file, pos) +=begin edoc +Method to delete a specific breakpoint. + +@param file filename containing the breakpoint (String) +@param pos line number containing the breakpoint (int) +=end + delete_break_point(file, pos) + stdout.printf_clear_breakpoint(file, pos) + end + + def add_break_point(file, pos, temp = false, cond = nil) +=begin edoc +Method to add a breakpoint. + +@param file filename for the breakpoint (String) +@param pos line number for the breakpoint (int) +@param temp flag indicating a temporary breakpoint (boolean) +@param cond condition of a conditional breakpoint (String) +=end + break_points.push [true, 0, file, pos, temp, cond, 0] + end + + def delete_break_point(file, pos) +=begin edoc +Method to delete a breakpoint. + +@param file filename of the breakpoint (String) +@param pos line number of the breakpoint (int) +=end + break_points.delete_if { |bp| + bp[1] == 0 and bp[2] == file and bp[3] == pos + } + end + + def enable_break_point(file, pos, enable) +=begin edoc +Method to set the enabled state of a breakpoint. + +@param file filename of the breakpoint (String) +@param pos line number of the breakpoint (int) +@param enable flag indicating the new enabled state (boolean) +=end + for bp in break_points + if (bp[1] == 0 and bp[2] == file and bp[3] == pos) + bp[0] = enable + break + end + end + end + + def ignore_break_point(file, pos, count) +=begin edoc +Method to set the ignore count of a breakpoint. + +@param file filename of the breakpoint (String) +@param pos line number of the breakpoint (int) +@param count ignore count to be set (int) +=end + for bp in break_points + if (bp[2] == file and bp[3] == pos) + bp[6] = count + break + end + end + end + + def clear_watch_point(cond) +=begin edoc +Method to delete a specific watch expression. + +@param cond expression specifying the watch expression (String) +=end + delete_watch_point(cond) + stdout.printf_clear_watchexpression(cond) + end + + def add_watch_point(cond, temp = false) +=begin edoc +Method to add a watch expression. + +@param cond expression of the watch expression (String) +@param temp flag indicating a temporary watch expression (boolean) +=end + co1, co2 = cond.split() + if co2 == "??created??" or co2 == "??changed??" + break_points.push [true, 1, cond, 0, temp, co1, 0, co2, {}] + else + break_points.push [true, 1, cond, 0, temp, cond, 0, "", {}] + end + end + + def delete_watch_point(cond) +=begin edoc +Method to delete a watch expression. + +@param cond expression of the watch expression (String) +=end + break_points.delete_if { |bp| + bp[1] == 1 and bp[2] == cond + } + end + + def enable_watch_point(cond, enable) +=begin edoc +Method to set the enabled state of a watch expression. + +@param cond expression of the watch expression (String) +@param enable flag indicating the new enabled state (boolean) +=end + for bp in break_points + if (bp[1] == 1 and bp[2] == cond) + bp[0] = enable + break + end + end + end + + def ignore_watch_point(cond, count) +=begin edoc +Method to set the ignore count of a watch expression. + +@param cond expression of the watch expression (String) +@param count ignore count to be set (int) +=end + for bp in break_points + if (bp[1] == 1 and bp[2] == cond) + bp[6] = count + break + end + end + end + + def excn_handle(file, line, id, binding_) +=begin edoc +Method to handle an exception + +@param file filename containing the currently executed line (String) +@param pos line number currently executed (int) +@param id (ignored) +@param binding_ current binding object +=end + if $!.class <= SystemExit + set_trace_func nil + stdout.printf_exit($!.status) + return + elsif $!.class <= ScriptError + msgParts = $!.message.split(":", 3) + filename = File.expand_path(msgParts[0]) + linenr = msgParts[1].to_i + exclist = ["", [filename, linenr, 0]] + stdout.printf_scriptExcn(exclist) + else + exclist = ["%s" % $!.class, "%s" % $!, [file, line]] + @frames.each do |_binding, _file, _line, _id| + next if [_file, _line] == exclist[-1] + exclist << [_file, _line] + end + stdout.printf_excn(exclist) + end + debug_command(file, line, id, binding_) + end + + def skip_it?(file) +=begin edoc +Method to filter out debugger files. + +Tracing is turned off for files that are part of the +debugger that are called from the application being debugged. + +@param file name of the file to be checked (String) +@return flag indicating, whether the file should be skipped (boolean) +=end + if file =~ /\(eval\)/ + return true + end + + if not traceRuby? and + (file =~ /#{Config::CONFIG['sitelibdir']}/ or + file =~ /#{Config::CONFIG['rubylibdir']}/) + return true + end + + if ["AsyncFile.rb", "AsyncIO.rb", "Config.rb", "DebugClient.rb", + "DebugClientBaseModule.rb", "DebugClientCapabilities.rb", + "DebugProtocol.rb", "DebugQuit.rb", "Debuggee.rb"].include?(\ + File.basename(file)) + return true + end + return false + end + + def trace_func(event, file, line, id, binding_, klass) +=begin edoc +Method executed by the tracing facility. + +@param event the tracing event (String) +@param file the name of the file being traced (String) +@param line the line number being traced (int) +@param id object id +@param binding_ a binding object +@param klass name of a class +=end + context(Thread.current).check_suspend + + if skip_it?(file) and not ["call","return"].include?(event) + 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 + @last_file = file + return + end + + @file = file + @line = line + + case event + when 'line' + frame_set_pos(file, line) + eventPoll + 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 + if check_break_points(file, line, binding_, id) or @stop_next == 0 + @no_step = nil + suspend_all + debug_command(file, line, id, binding_) + 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' + @no_step = nil + @stop_next = 0 # break here before leaving... + 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 = [] + @waiting = [] + @stdout = STDOUT + @loaded_files = {} + + class SilentObject +=begin edoc +Class defining an object that ignores all messages. +=end + def method_missing( msg_id, *a, &b ) +=begin edoc +Method invoked for all messages it cannot handle. + +@param msg_id symbol for the method called +@param *a arguments passed to the missing method +@param &b unknown +=end + end + end + SilentClient = SilentObject.new() + @client = SilentClient + @attached = false + + class <<DEBUGGER__ +=begin edoc +Class defining a singleton object for the debugger. +=end + def stdout +=begin edoc +Method returning the stdout object. + +@return reference to the stdout object +=end + @stdout + end + + def stdout=(s) +=begin edoc +Method to set the stdout object. + +@param s reference to the stdout object +=end + @stdout = s + end + + def break_points +=begin edoc +Method to return the list of breakpoints + +@return Array containing all breakpoints. +=end + @break_points + end + + def last_thread +=begin edoc +Method returning the last active thread. + +@return active thread +=end + @last_thread + end + + def attach( debugger ) +=begin edoc +Method to connect the debugger to the IDE. + +@param debugger reference to the object handling the + communication with the IDE. +=end + unless @attached + set_client( debugger ) + @attached = true + interrupt + else + false + end + end + + def client +=begin edoc +Method returning a reference to the client object. + +@return reference to the client object. +=end + @client + end + + def set_client( debugger ) +=begin edoc +Method to set the client handling the connection. + +@param debugger reference to the object handling the connection +=end + @client = Client.new( debugger ) + DEBUGGER__.stdout = @client + end + + def attached? +=begin edoc +Method returning the attached state. + +@return flag indicating, whether the debugger is attached to the IDE. +=end + @attached + end + + def quit(status = 0) +=begin edoc +Method to quit the debugger. + +@param status exit status of the program +=end + @client.printf_exit(status) + STDERR.flush; STDOUT.flush + end + + def waiting +=begin edoc +Method returning the waiting list. + +@return the waiting list +=end + @waiting + end + + def set_last_thread(th) +=begin edoc +Method to remember the last thread. + +@param th thread to be remembered. +=end + @last_thread = th + end + + def suspend +=begin edoc +Method to suspend the program being debugged. +=end + 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 +=begin edoc +Method to resume the program being debugged. +=end + 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) +=begin edoc +Method returning the context of a thread. + +@param th threat the context is requested for +@return context object for the thread +=end + c = thread[:__debugger_data__] + unless c + thread[:__debugger_data__] = c = Context.new + end + c + end + + def interrupt +=begin edoc +Method to stop execution at the next instruction. +=end + context(@last_thread).stop_next + end + + def get_thread(num) +=begin edoc +Method returning a thread by number. + +@param num thread number (int) +@return thread with the requested number +=end + th = @thread_list.index(num) + unless th + @stdout.print "No thread ##{num}\n" + throw :debug_error + end + th + end + + def thread_list(num) +=begin edoc +Method to list the state of a thread. + +@param num thread number (int) +=end + 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 +=begin edoc +Method to list the state of all threads. +=end + for th in @thread_list.values.sort + thread_list(th) + end + end + + def make_thread_list +=begin edoc +Method to create a thread list. +=end + 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_) +=begin edoc +Method handling the thread related debug commands. + +@param input debug command (String) +@param binding_ reference to the binding object +=end + 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 + + def eventLoop +=begin edoc +Method calling the main event loop. +=end + @client.eventLoop + end + + def eventPoll +=begin edoc +Method calling the main function polling for an event sent by the IDE. +=end + @client.eventPoll + end + + def traceRuby? +=begin edoc +Method to check, if we should trace into the Ruby interpreter libraries. +=end + @client.traceRuby? + end + end + + + class Context + def eventLoop +=begin edoc +Method calling the main event loop. +=end + DEBUGGER__.eventLoop + end + + def eventPoll +=begin edoc +Method calling the main function polling for an event sent by the IDE. +=end + DEBUGGER__.eventPoll + end + + def traceRuby? +=begin edoc +Method to check, if we should trace into the Ruby interpreter libraries. +=end + DEBUGGER__.traceRuby? + end + end + + require 'DebugProtocol' + + class Client +=begin edoc +Class handling the connection to the IDE. +=end + def initialize( debugger ) +=begin edoc +Constructor + +@param debugger reference to the object having the IDE connection. +=end + @debugger = debugger + end + + def eventLoop +=begin edoc +Method calling the main event loop. +=end + @debugger.eventLoop() + end + + def eventPoll +=begin edoc +Method calling the main function polling for an event sent by the IDE. +=end + @debugger.eventPoll() + end + + def traceRuby? +=begin edoc +Method to check, if we should trace into the Ruby interpreter libraries. +=end + @debugger.traceRuby + end + + def printf( *args ) +=begin edoc +Method to print something to the IDE. + +@param *args Arguments to be printed. +=end + @debugger.write("#{args.join(', ')}\n") + end + + def printf_line(frames) +=begin edoc +Method to report the current line and the current stack trace to the IDE. + +@param frames reference to the array containing the stack trace. +=end + fr_list = [] + for bind, file, line, id in frames + break unless bind + break if file =~ /\(eval\)/ + fr_list << [file, line, id ? id.id2name : ''] + end + + @debugger.write("%s%s\n" % [ResponseLine, fr_list.inspect]) + end + + def printf_excn(exclist) +=begin edoc +Method to report an exception to the IDE. + +@param exclist info about the exception to be reported +=end + @debugger.write("%s%s\n" % [ResponseException, exclist.inspect]) + end + + def printf_scriptExcn(exclist) +=begin edoc +Method to report a ScriptError to the IDE. + +@param exclist info about the exception to be reported +=end + @debugger.write("%s%s\n" % [ResponseSyntax, exclist.inspect]) + end + + def printf_clear_breakpoint(file, line) +=begin edoc +Method to report the deletion of a temporary breakpoint to the IDE. + +@param file filename of the breakpoint (String) +@param line line number of the breakpoint (int) +=end + @debugger.write("%s%s,%d\n" % [ResponseClearBreak, file, line]) + end + + def printf_clear_watchexpression(cond) +=begin edoc +Method to report the deletion of a temporary watch expression to the IDE. + +@param cond expression of the watch expression (String) +=end + @debugger.write("%s%s\n" % [ResponseClearWatch, cond]) + end + + def printf_exit(status) +=begin edoc +Method to report the exit status to the IDE. + +@param status exit status of the program (int) +=end + @debugger.write("%s%d\n" % [ResponseExit, status]) + end + end + + class Context + def current_frame +=begin edoc +Method returning the current execution frame. + +@return current execution frame +=end + @frames[@frame_pos] + end + + def get_frame(frameno) +=begin edoc +Method returning a specific execution frame. + +@param frameno frame number of the frame to be returned (int) +@return the requested execution frame +=end + @frames[frameno] + end + + def current_binding +=begin edoc +Method returning the binding object of the current execution frame. + +@return binding object of the current execution frame +=end + @frames[@frame_pos][0] + end + + def get_binding(frameno) +=begin edoc +Method returning the binding object of a specific execution frame. + +@param frameno frame number of the frame (int) +@return the requested binding object +=end + @frames[frameno][0] + end + end + + Thread.main["name"] = 'Main' +end