--- a/DebugClients/Ruby/Debuggee.rb Sat Nov 07 15:54:09 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1148 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2005 - 2015 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 'continuation' - -require 'DebugQuit' -require 'rbconfig' - -class DEBUGGER__ -=begin edoc -Class implementing the real debugger. -=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 MUTEX.synchronize { - if @suspend_next - DEBUGGER__.waiting.push Thread.current - @suspend_next = false - true - end - } - end - 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? - break_points.each do |b| - 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 - break_points.each do |bp| - 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 - break_points.each do |bp| - 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 - break_points.each do |bp| - 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 - break_points.each do |bp| - 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 =~ /#{RbConfig::CONFIG['sitelibdir']}/ or - file =~ /#{RbConfig::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 '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 '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 - MUTEX.synchronize do - make_thread_list - for th, in @thread_list - next if th == Thread.current - context(th).set_suspend - end - end - # Schedule other threads to suspend as soon as possible. - Thread.pass - end - - def resume -=begin edoc -Method to resume the program being debugged. -=end - MUTEX.synchronize do - 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 - end - # 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.key(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