Thu, 01 Jan 2015 13:11:59 +0100
Updated copyright for 2015.
# -*- coding: utf-8 -*- # Copyright (c) 2005 - 2015 Detlev Offenbach <detlev@die-offenbachs.de> # =begin edoc File implementing a debug client base module. =end require 'socket' require 'DebugQuit' require 'DebugProtocol' require 'DebugClientCapabilities' require 'AsyncFile' require 'Config' require 'Completer' $DebugClientInstance = nil $debugging = false module DebugClientBase =begin edoc Module implementing the client side of the debugger. It provides access to the Ruby interpeter from a debugger running in another process. The protocol between the debugger and the client assumes that there will be a single source of debugger commands and a single source of Ruby statements. Commands and statement are always exactly one line and may be interspersed. The protocol is as follows. First the client opens a connection to the debugger and then sends a series of one line commands. A command is either >Load<, >Step<, >StepInto<, ... or a Ruby statement. See DebugProtocol.rb for a listing of valid protocol tokens. A Ruby statement consists of the statement to execute, followed (in a separate line) by >OK?<. If the statement was incomplete then the response is >Continue<. If there was an exception then the response is >Exception<. Otherwise the response is >OK<. The reason for the >OK?< part is to provide a sentinal (ie. the responding >OK<) after any possible output as a result of executing the command. The client may send any other lines at any other time which should be interpreted as program output. If the debugger closes the session there is no response from the client. The client may close the session at any time as a result of the script being debugged closing or crashing. <b>Note</b>: This module is meant to be mixed in by individual DebugClient classes. Do not use it directly. =end @@clientCapabilities = HasDebugger | HasInterpreter | HasShell | HasCompleter attr_accessor :passive, :traceRuby def initializeDebugClient =begin edoc Method to initialize the module =end # The context to run the debugged program in. @debugBinding = eval("def e4dc_DebugBinding; binding; end; e4dc_DebugBinding", TOPLEVEL_BINDING, __FILE__, __LINE__ - 3) # The context to run the shell commands in. @shellBinding = eval("def e4dc_ShellBinding; binding; end; e4dc_ShellBinding", TOPLEVEL_BINDING, __FILE__, __LINE__ - 3) # stack frames @frames = [] @framenr = 0 # The list of complete lines to execute. @buffer = '' @lineno = 0 # The list of regexp objects to filter variables against @globalsFilterObjects = [] @localsFilterObjects = [] @pendingResponse = ResponseOK @mainProcStr = nil # used for the passive mode @passive = false # used to indicate the passive mode @running = nil @readstream = nil @writestream = nil @errorstream = nil @variant = 'You should not see this' @completer = Completer.new(@shellBinding) end def handleException =begin edoc Private method called in the case of an exception It ensures that the debug server is informed of the raised exception. =end @pendingResponse = ResponseException end def sessionClose =begin edoc Privat method to close the session with the debugger and terminate. =end set_trace_func nil if $debugging $debugging = false @running = nil DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() end # clean up asyncio disconnect() # make sure we close down our end of the socket # might be overkill as normally stdin, stdout and stderr # SHOULD be closed on exit, but it does not hurt to do it here @readstream.close() @writestream.close() @errorstream.close() # Ok, go away. exit() end def unhandled_exception(exc) =begin edoc Private method to report an unhandled exception. @param exc the exception object =end if SystemExit === exc if $debugging $debugging = false else progTerminated(exc.status) end return end # split the exception message msgParts = exc.message.split(":", 3) filename = File.expand_path(msgParts[0]) linenr = msgParts[1].to_i if ScriptError === exc msgParts = msgParts[2].split(":", 3) filename = msgParts[0].sub(/in `require'/, "") linenr = msgParts[1].to_i exclist = [""] exclist << [filename, linenr, 0] write("%s%s\n" % [ResponseSyntax, exclist.inspect]) return end exclist = ["unhandled %s" % exc.class, msgParts[2].sub(/in /, "")] exclist << [filename, linenr] # process the traceback frList = exc.backtrace frList.each do |frame| next if frame =~ /DebugClientBaseModule/ break if frame =~ /\(eval\)/ frameParts = frame.split(":", 3) filename = File.expand_path(frameParts[0]) linenr = frameParts[1].to_i next if [filename, linenr] == exclist[-1] exclist << [filename, linenr] end write("%s%s\n" % [ResponseException, exclist.inspect]) end def handleLine(line) =begin edoc Private method to handle the receipt of a complete line. It first looks for a valid protocol token at the start of the line. Thereafter it trys to execute the lines accumulated so far. @param line the received line =end # Remove any newline if line[-1] == "\n" line = line[0...-1] end ## STDOUT << line << "\n" ## debug eoc = line.index("<") if eoc and eoc >= 0 and line[0,1] == ">" # Get the command part and any argument cmd = line[0..eoc] arg = line[eoc+1..-1] case cmd when RequestOK write(@pendingResponse + "\n") @pendingResponse = ResponseOK return when RequestEnv # convert a Python stringified hash to a Ruby stringified hash arg.gsub!(/: u/, "=>") arg.gsub!(/u'/, "'") eval(arg).each do |key, value| if key[-1..-1] == "+" key = key[0..-2] if ENV[key] ENV[key] += value else ENV[key] = value end else ENV[key] = value end end return when RequestVariables frmnr, scope, filter = eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"')) dumpVariables(frmnr.to_i, scope.to_i, filter) return when RequestVariable var, frmnr, scope, filter = \ eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"')) dumpVariable(var, frmnr.to_i, scope.to_i, filter) return when RequestStep DEBUGGER__.context(DEBUGGER__.last_thread).stop_next() @eventExit = true return when RequestStepOver DEBUGGER__.context(DEBUGGER__.last_thread).step_over() @eventExit = true return when RequestStepOut DEBUGGER__.context(DEBUGGER__.last_thread).step_out() @eventExit = true return when RequestStepQuit set_trace_func nil wasDebugging = $debugging $debugging = false @running = nil if @passive progTerminated(42) else DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() if wasDebugging end return when RequestContinue special = arg.to_i if special == 0 DEBUGGER__.context(DEBUGGER__.last_thread).step_continue() else # special == 1 means a continue while doing a step over # this occurs when an expception is raised doing a step over DEBUGGER__.context(DEBUGGER__.last_thread).step_over() end @eventExit = true return when RequestSetFilter scope, filterString = eval("[%s]" % arg) generateFilterObjects(scope.to_i, filterString) return when RequestLoad $debugging = true ARGV.clear() wd, fn, args, traceRuby = arg.split("|", -4) @traceRuby = traceRuby.to_i == 1 ? true : false ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"'))) $:.insert(0, File.dirname(fn)) if wd == '' Dir.chdir($:[0]) else Dir.chdir(wd) end @running = fn command = "$0 = '%s'; require '%s'" % [fn, fn] RubyVM::InstructionSequence.compile_option = { trace_instruction: true } set_trace_func proc { |event, file, line, id, binding_, klass, *rest| DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass) } begin eval(command, @debugBinding) rescue DebugQuit # just ignore it rescue Exception => exc unhandled_exception(exc) ensure set_trace_func(nil) @running = nil end return when RequestRun $debugging = false ARGV.clear() wd, fn, args = arg.split("|", -3) ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"'))) $:.insert(0, File.dirname(fn)) if wd == '' Dir.chdir($:[0]) else Dir.chdir(wd) end command = "$0 = '%s'; require '%s'" % [fn, fn] @frames = [] set_trace_func proc { |event, file, line, id, binding_, klass, *rest| trace_func(event, file, line, id, binding_, klass) } begin eval(command, @debugBinding) rescue SystemExit # ignore it rescue Exception => exc unhandled_exception(exc) ensure set_trace_func(nil) end return when RequestShutdown sessionClose() return when RequestBreak fn, line, temporary, set, cond = arg.split("@@") line = line.to_i set = set.to_i temporary = temporary.to_i == 1 ? true : false if set == 1 if cond == 'None' cond = nil end DEBUGGER__.context(DEBUGGER__.last_thread)\ .add_break_point(fn, line, temporary, cond) else DEBUGGER__.context(DEBUGGER__.last_thread)\ .delete_break_point(fn, line) end return when RequestBreakEnable fn, line, enable = arg.split(',') line = line.to_i enable = enable.to_i == 1 ? true : false DEBUGGER__.context(DEBUGGER__.last_thread)\ .enable_break_point(fn, line, enable) return when RequestBreakIgnore fn, line, count = arg.split(',') line = line.to_i count = count.to_i DEBUGGER__.context(DEBUGGER__.last_thread)\ .ignore_break_point(fn, line, count) return when RequestWatch cond, temporary, set = arg.split('@@') set = set.to_i temporary = temporary.to_i == 1 ? true : false if set == 1 DEBUGGER__.context(DEBUGGER__.last_thread)\ .add_watch_point(cond, temporary) else DEBUGGER__.context(DEBUGGER__.last_thread).delete_watch_point(cond) end return when RequestWatchEnable cond, enable = arg.split(',') enable = enable.to_i == 1 ? true : false DEBUGGER__.context(DEBUGGER__.last_thread)\ .enable_watch_point(cond, enable) return when RequestWatchIgnore cond, count = arg.split(',') count = count.to_i DEBUGGER__.context(DEBUGGER__.last_thread).ignore_watch_point(cond, count) return when RequestEval, RequestExec if not @running binding_ = @shellBinding else binding_ = DEBUGGER__.context(DEBUGGER__.last_thread).current_binding end write("\n") begin value = eval(arg, binding_) rescue Exception => exc list = [] list << "%s: %s\n" % \ [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')] $@.each do |l| break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \ l =~ /:in `require'$/ list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)") end list.each do |entry| write(entry) end else write("=> #{value.inspect}\n") write("#{ResponseOK}\n") end return when RequestBanner version = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" write("%s('%s','%s','%s')\n" % \ [ResponseBanner, version, Socket.gethostname(), @variant]) return when RequestCapabilities write("%s%d, 'Ruby'\n" % \ [ResponseCapabilities, @@clientCapabilities]) return when RequestCompletion completionList(arg) return else puts "Got unsupported command %s.\n" % cmd return end end if @buffer @buffer << "\n" << line @lineno += 1 else @buffer = line.dup end # check for completeness if not canEval? @pendingResponse = ResponseContinue else command = @buffer.dup @buffer = "" begin res = "=> " if not @running res << eval(command, @shellBinding, "stdin", @lineno).inspect << "\n" else res << eval(command, DEBUGGER__.context(DEBUGGER__.last_thread).get_binding(@framenr), "stdin", @lineno).inspect << "\n" end write(res) rescue SystemExit => exc progTerminated(exc.status) rescue ScriptError, StandardError => exc list = [] list << "%s: %s\n" % \ [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')] $@.each do |l| break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \ l =~ /:in `require'$/ list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)") end list.each do |entry| write(entry) end handleException() end end end def canEval? =begin edoc Private method to check if the buffer's contents can be evaluated. @return flag indicating if an eval might succeed (boolean) =end indent = 0 if @buffer =~ /,\s*$/ return false end @buffer.split($/).each do |l| if l =~ /^\s*(class|module|def|if|unless|case|while|until|for|begin)\b[^_]/ indent += 1 end if l =~ /\s*do\s*(\|.*\|)?\s*$/ indent += 1 end if l =~ /^\s*end\s*$|^\s*end\b[^_]/ indent -= 1 end if l =~ /\{\s*(\|.*\|)?\s*$/ indent += 1 end if l =~ /^\s*\}/ indent -= 1 end end if indent > 0 return false end return true end def trace_func(event, file, line, id, binding_, klass) =begin edoc Method executed by the tracing facility. It is used to save the execution context of an exception. @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 case event when 'line' if @frames[0] @frames[0] = binding_ else @frames.unshift binding_ end when 'call' @frames.unshift binding_ when 'class' @frames.unshift binding_ when 'return', 'end' @frames.shift when 'end' @frames.shift when 'raise' set_trace_func nil end end def write(s) =begin edoc Private method to write data to the output stream. @param s data to be written (string) =end @writestream.write(s) @writestream.flush() end def interact =begin edoc Private method to Interact with the debugger. =end setDescriptors(@readstream, @writestream) $DebugClientInstance = self if not @passive # At this point simulate an event loop. eventLoop() end end def eventLoop =begin edoc Private method implementing our event loop. =end @eventExit = nil while @eventExit == nil wrdy = [] if AsyncPendingWrite(@writestream) > 0 wrdy << @writestream.getSock() end if AsyncPendingWrite(@errorstream) > 0 wrdy << @errorstream.getSock() end rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, []) if rrdy.include?(@readstream.getSock()) readReady(@readstream.fileno()) end if wrdy.include?(@writestream.getSock()) writeReady(@writestream.fileno()) end if wrdy.include?(@errorstream.getSock()) writeReady(@errorstream.fileno()) end end @eventExit = nil end def eventPoll =begin edoc Private method to poll for events like 'set break point'. =end # the choice of a ~0.5 second poll interval is arbitrary. lasteventpolltime = @lasteventpolltime ? @lasteventpolltime : Time.now now = Time.now if now - lasteventpolltime < 0.5 @lasteventpolltime = lasteventpolltime return else @lasteventpolltime = now end wrdy = [] if AsyncPendingWrite(@writestream) > 0 wrdy << @writestream.getSock() end if AsyncPendingWrite(@errorstream) > 0 wrdy << @errorstream.getSock() end rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [], 0) if rrdy == nil return end if rrdy.include?(@readstream.getSock()) readReady(@readstream.fileno()) end if wrdy.include?(@writestream.getSock()) writeReady(@writestream.fileno()) end if wrdy.include?(@errorstream.getSock()) writeReady(@errorstream.fileno()) end end def connectDebugger(port, remoteAddress=nil, redirect=true) =begin edoc Public method to establish a session with the debugger. It opens a network connection to the debugger, connects it to stdin, stdout and stderr and saves these file objects in case the application being debugged redirects them itself. @param port the port number to connect to (int) @param remoteAddress the network address of the debug server host (string) @param redirect flag indicating redirection of stdin, stdout and stderr (boolean) =end if remoteAddress == nil sock = TCPSocket.new(DebugAddress, port) else if remoteAddress =~ /@@i/ remoteAddress, interface = remoteAddress.split("@@i") else interface = 0 end if remoteAddress.downcase =~ /^fe80/ remoteAddress = "%s%%%s" % [remoteAddress, interface] end sock = TCPSocket.new(remoteAddress, port) end @readstream = AsyncFile.new(sock, "r", "stdin") @writestream = AsyncFile.new(sock, "w", "stdout") @errorstream = AsyncFile.new(sock, "w", "stderr") if redirect $stdin = @readstream $stdout = @writestream $stderr = @errorstream end end def progTerminated(status) =begin edoc Private method to tell the debugger that the program has terminated. @param status the return status =end if status == nil status = 0 else begin Integer(status) rescue status = 1 end end set_trace_func(nil) @running = nil write("%s%d\n" % [ResponseExit, status]) end def dumpVariables(frmnr, scope, filter) =begin edoc Private method to return the variables of a frame to the debug server. @param frmnr distance of frame reported on. 0 is the current frame (int) @param scope 1 to report global variables, 0 for local variables (int) @param filter the indices of variable types to be filtered (list of int) =end if $debugging if scope == 0 @framenr = frmnr end binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\ .get_frame(frmnr) else binding_ = @frames[frmnr] end varlist = [scope] if scope >= 1 # dump global variables vlist = formatVariablesList(global_variables, binding_, scope, filter) elsif scope == 0 # dump local variables vlist = formatVariablesList(eval("local_variables", binding_), binding_, scope, filter) end varlist.concat(vlist) write("%s%s\n" % [ResponseVariables, varlist.inspect]) end def dumpVariable(var, frmnr, scope, filter) =begin edoc Private method to return the variables of a frame to the debug server. @param var list encoded name of the requested variable (list of strings) @param frmnr distance of frame reported on. 0 is the current frame (int) @param scope 1 to report global variables, 0 for local variables (int) @param filter the indices of variable types to be filtered (list of int) =end if $debugging binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\ .get_frame(frmnr) else binding_ = @frames[frmnr] end varlist = [scope, var] i = 0 obj = nil keylist = nil formatSequences = false access = "" isDict = false while i < var.length if ["[]", "{}"].include?(var[i][-2..-1]) if i+1 == var.length keylist = [var[i][0..-3]] formatSequences = true if access.length == 0 access = "#{var[i][0..-3]}" else access << "[#{var[i][0..-3]}]" end if var[i][-2..-1] == "{}" isDict = true end break else if access.length == 0 access = "#{var[i][0..-3]}" else access << "[#{var[i][0..-3]}]" end end else if access.length != 0 access << "[#{var[i]}]" obj = eval(access, binding_) binding_ = obj.instance_eval{binding()} access = "" else obj = eval(var[i], binding_) binding_ = obj.instance_eval{binding()} end end i += 1 end if formatSequences bind = binding_ else bind = obj.instance_eval{binding()} end if not keylist keylist = obj.instance_variables access = nil else if access.length != 0 obj = eval(access, bind) end if isDict keylist = obj.keys() else keylist = Array.new(obj.length){|i| i} end end vlist = formatVariablesList(keylist, bind, scope, filter, excludeSelf=true, access=access) varlist.concat(vlist) write("%s%s\n" % [ResponseVariable, varlist.inspect]) end def formatVariablesList(keylist, binding_, scope, filter = [], excludeSelf = false, access = nil) =begin edoc Private method to produce a formated variables list. The binding passed in to it is scanned. Variables are only added to the list, if their type is not contained in the filter list and their name doesn't match any of the filter expressions. The formated variables list (a list of lists of 3 values) is returned. @param keylist keys of the dictionary @param binding_ the binding to be scanned @param scope 1 to filter using the globals filter, 0 using the locals filter (int). Variables are only added to the list, if their name do not match any of the filter expressions. @param filter the indices of variable types to be filtered. Variables are only added to the list, if their type is not contained in the filter list. @param excludeSelf flag indicating if the self object should be excluded from the listing (boolean) @param access String specifying the access path to (String) @return A list consisting of a list of formatted variables. Each variable entry is a list of three elements, the variable name, its type and value. =end varlist = [] if scope >= 1 patternFilterObjects = @globalsFilterObjects else patternFilterObjects = @localsFilterObjects begin obj = eval("self", binding_) rescue StandardError, ScriptError obj = nil end if not excludeSelf and obj.class != Object keylist << "self" end end keylist.each do |key| # filter based on the filter pattern matched = false patternFilterObjects.each do |pat| if pat.match(key) matched = true break end end next if matched if key.to_s == '$KCODE' or key.to_s == '$=' or key.to_s == '$-K' varlist << [key.to_s, "NilClass", "nil"] next end begin if access if key.to_s == key key = "'%s'" % key else key = key.to_s end k = "#{access}[%s]" % key obj = eval(k, binding_) else obj = eval(key.to_s, binding_) end rescue NameError next end if obj or obj.class == NilClass or obj.class == FalseClass otype = obj.class.to_s if obj.inspect.nil? otype = "" oval = "" else oval = obj.inspect.gsub(/=>/,":") end else otype = "" oval = obj.inspect end next if inFilter?(filter, otype, oval) if oval.index("#<") == 0 addr = extractAddress(oval) oval = "<#{otype} object at #{addr}>" end if obj if obj.class == Array or obj.class == Hash oval = "%d" % obj.length() end end varlist << [key.to_s, otype, oval] end return varlist end def extractAddress(var) =begin edoc Private method to extract the address part of an object description. @param var object description (String) @return the address contained in the object description (String) =end m = var.match(/^#<.*?:([^:]*?) /) if m return m[1] else return "" end end def extractTypeAndAddress(var) =begin edoc Private method to extract the address and type parts of an object description. @param var object description (String) @return list containing the type and address contained in the object description (Array of two String) =end m = var.match(/^#<(.*?):(.*?) /) if m return [m[1], m[2]] else return ["", ""] end end def inFilter?(filter, otype, oval) =begin edoc Private method to check, if a variable is to be filtered based on its type. @param filter the indices of variable types to be filtered (Array of int. @param otype type of the variable to be checked (String) @param oval variable value to be checked (String) @return flag indicating, whether the variable should be filtered (boolean) =end cindex = ConfigVarTypeStrings.index(otype) if cindex == nil if oval.index("#<") == 0 if filter.include?(ConfigVarTypeStrings.index("instance")) return true else return false end elsif ['FalseClass', 'TrueClass'].include?(otype) if filter.include?(ConfigVarTypeStrings.index("bool")) return true else return false end else if filter.include?(ConfigVarTypeStrings.index("other")) return true else return false end end end if filter.include?(cindex) return true end return false end def generateFilterObjects(scope, filterString) =begin edoc Private method to convert a filter string to a list of filter objects. @param scope 1 to generate filter for global variables, 0 for local variables (int) @param filterString string of filter patterns separated by ';' =end patternFilterObjects = [] for pattern in filterString.split(';') patternFilterObjects << Regexp.compile('^%s$' % pattern) end if scope == 1 @globalsFilterObjects = patternFilterObjects[0..-1] else @localsFilterObjects = patternFilterObjects[0..-1] end end def completionList(text) =begin edoc Method used to handle the command completion request @param text the text to be completed (string) =end completions = @completer.complete(text).compact.sort.uniq write("%s%s||%s\n" % [ResponseCompletion, completions.inspect, text]) end def startProgInDebugger(progargs, wd = '', host = nil, port = nil, exceptions = true, traceRuby = false, redirect=true) =begin edoc Method used to start the remote debugger. @param progargs commandline for the program to be debugged (list of strings) @param wd working directory for the program execution (string) @param host hostname of the debug server (string) @param port portnumber of the debug server (int) @param exceptions flag to enable exception reporting of the IDE (boolean) @param traceRuby flag to enable tracing into the Ruby library @param redirect flag indicating redirection of stdin, stdout and stderr (boolean) =end $debugging = true if host == nil host = ENV.fetch('ERICHOST', 'localhost') end if port == nil port = ENV.fetch('ERICPORT', 42424) end connectDebugger(port, host, redirect) #TCPSocket.gethostbyname(host)[3]) DEBUGGER__.attach(self) @traceRuby = traceRuby fn = progargs.shift fn = File.expand_path(fn) ARGV.clear() ARGV.concat(progargs) $:.insert(0, File.dirname(fn)) if wd == '' Dir.chdir($:[0]) else Dir.chdir(wd) end @running = fn @passive = true write("%s%s|%d\n" % [PassiveStartup, @running, exceptions ? 1 : 0]) interact() command = "$0 = '%s'; require '%s'" % [fn, fn] set_trace_func proc { |event, file, line, id, binding_, klass, *rest| DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass) } begin eval(command, @debugBinding) rescue DebugQuit # just ignore it rescue Exception => exc unhandled_exception(exc) ensure set_trace_func(nil) @running = nil end # just a short delay because client might shut down too quickly otherwise sleep(1) end def main =begin edoc Public method implementing the main method. =end if ARGV.include?('--') args = ARGV[0..-1] host = nil port = nil wd = '' traceRuby = false exceptions = true redirect = true while args[0] if args[0] == '-h' host = args[1] args.shift args.shift elsif args[0] == '-p' port = args[1] args.shift args.shift elsif args[0] == '-w' wd = args[1] args.shift args.shift elsif args[0] == '-t' traceRuby = true args.shift elsif args[0] == '-e' exceptions = false args.shift elsif args[0] == '-n' redirect = false args.shift elsif args[0] == '--' args.shift break else # unknown option args.shift end end if args.length == 0 STDOUT << "No program given. Aborting!" else startProgInDebugger(args, wd, host, port, exceptions = exceptions, traceRuby = traceRuby, redirect = redirect) end else if ARGV[0] == '--no-encoding' # just ignore it, it's here to be compatible with python debugger ARGV.shift end begin port = ARGV[0].to_i rescue port = -1 end begin redirect = ARGV[1].to_i redirect = redirect == 1 ? true : false rescue redirect = true end begin remoteAddress = ARGV[2] rescue remoteAddress = nil end ARGV.clear() $:.insert(0,"") if port >= 0 connectDebugger(port, remoteAddress, redirect) DEBUGGER__.attach(self) interact() end end end end