Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 174 lines (150 sloc) 5.082 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
require 'pp'
require 'stringio'
require 'socket'
require 'thread'
require 'ruby-debug-base'
require 'ruby-debug/processor'

module Debugger
  self.handler = CommandProcessor.new

  # the port number used for remote debugging
  PORT = 8989 unless defined?(PORT)

  # What file is used for debugger startup commands.
  unless defined?(INITFILE)
    if RUBY_PLATFORM =~ /mswin/
      # Of course MS Windows has to be different
      INITFILE = 'rdebug.ini'
      HOME_DIR = (ENV['HOME'] ||
                   ENV['HOMEDRIVE'].to_s + ENV['HOMEPATH'].to_s).to_s
    else
      INITFILE = '.rdebugrc'
      HOME_DIR = ENV['HOME'].to_s
    end
  end

  class << self
    # gdb-style annotation mode. Used in GNU Emacs interface
    attr_accessor :annotate

    # in remote mode, wait for the remote connection
    attr_accessor :wait_connection

    # If set, a string to look for in caller() and is used to see
    # if the call stack is truncated.
    attr_accessor :start_sentinal

    attr_reader :thread, :control_thread

    def interface=(value) # :nodoc:
      handler.interface = value
    end

    #
    # Starts a remote debugger.
    #
    def start_remote(host = nil, port = PORT)
      return if @thread

      self.interface = nil
      start

      if port.kind_of?(Array)
        cmd_port, ctrl_port = port
      else
        cmd_port, ctrl_port = port, port + 1
      end

      start_control(host, ctrl_port)

      yield if block_given?

      mutex = Mutex.new
      proceed = ConditionVariable.new

      @thread = DebugThread.new do
        server = TCPServer.new(host, cmd_port)
        while (session = server.accept)
          self.interface = RemoteInterface.new(session)
          if wait_connection
            mutex.synchronize do
              proceed.signal
            end
          end
        end
      end
      if wait_connection
        mutex.synchronize do
          proceed.wait(mutex)
        end
      end
    end
    alias start_server start_remote

    def start_control(host = nil, ctrl_port = PORT + 1) # :nodoc:
      return if defined?(@control_thread) && @control_thread
      @control_thread = DebugThread.new do
        server = TCPServer.new(host, ctrl_port)
        while (session = server.accept)
          interface = RemoteInterface.new(session)
          processor = ControlCommandProcessor.new(interface)
          processor.process_commands
        end
      end
    end

    #
    # Connects to the remote debugger
    #
    def start_client(host = 'localhost', port = PORT)
      require "socket"
      interface = Debugger::LocalInterface.new
      socket = TCPSocket.new(host, port)
      puts "Connected."

      catch(:exit) do
        while (line = socket.gets)
          case line
          when /^PROMPT (.*)$/
            input = interface.read_command($1)
            throw :exit unless input
            socket.puts input
          when /^CONFIRM (.*)$/
            input = interface.confirm($1)
            throw :exit unless input
            socket.puts input
          else
            print line
          end
        end
      end
      socket.close
    end

    # Runs normal debugger initialization scripts
    # Reads and executes the commands from init file (if any) in the
    # current working directory. This is only done if the current
    # directory is different from your home directory. Thus, you can
    # have more than one init file, one generic in your home directory,
    # and another, specific to the program you are debugging, in the
    # directory where you invoke ruby-debug.
    def run_init_script(out = handler.interface)
      cwd_script_file = File.expand_path(File.join(".", INITFILE))
      run_script(cwd_script_file, out) if File.exists?(cwd_script_file)

      home_script_file = File.expand_path(File.join(HOME_DIR, INITFILE))
      run_script(home_script_file, out) if File.exists?(home_script_file) and
        cwd_script_file != home_script_file
    end

    #
    # Runs a script file
    #
    def run_script(file, out = handler.interface, verbose=false)
      interface = ScriptInterface.new(File.expand_path(file), out)
      processor = ControlCommandProcessor.new(interface)
      processor.process_commands(verbose)
    end
  end
end

module Kernel

  # Enters the debugger in the current thread after _steps_ line events occur.
  # Before entering the debugger startup script is read.
  #
  # Setting _steps_ to 0 will cause a break in the debugger subroutine
  # and not wait for a line event to occur. You will have to go "up 1"
  # in order to be back in your debugged program rather than the
  # debugger. Settings _steps_ to 0 could be useful you want to stop
  # right after the last statement in some scope, because the next
  # step will take you out of some scope.
  def debugger(steps = 1)
    Debugger.start
    Debugger.run_init_script(StringIO.new)
    if 0 == steps
      Debugger.current_context.stop_frame = 0
    else
      Debugger.current_context.stop_next = steps
    end
  end
  alias breakpoint debugger unless respond_to?(:breakpoint)
end
Something went wrong with that request. Please try again.