debug 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +116 -5
- data/ext/debug/debug.c +2 -1
- data/ext/debug/extconf.rb +2 -0
- data/lib/debug/breakpoint.rb +2 -2
- data/lib/debug/client.rb +69 -46
- data/lib/debug/config.rb +74 -21
- data/lib/debug/console.rb +92 -25
- data/lib/debug/local.rb +22 -1
- data/lib/debug/prelude.rb +49 -0
- data/lib/debug/server.rb +186 -25
- data/lib/debug/server_cdp.rb +412 -0
- data/lib/debug/server_dap.rb +53 -23
- data/lib/debug/session.rb +387 -120
- data/lib/debug/thread_client.rb +4 -2
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +95 -1
- metadata +8 -6
    
        data/lib/debug/console.rb
    CHANGED
    
    | @@ -31,6 +31,7 @@ module DEBUGGER__ | |
| 31 31 | 
             
                  end if SIGWINCH_SUPPORTED
         | 
| 32 32 |  | 
| 33 33 | 
             
                  def readline_setup prompt
         | 
| 34 | 
            +
                    load_history_if_not_loaded
         | 
| 34 35 | 
             
                    commands = DEBUGGER__.commands
         | 
| 35 36 |  | 
| 36 37 | 
             
                    Reline.completion_proc = -> given do
         | 
| @@ -87,40 +88,106 @@ module DEBUGGER__ | |
| 87 88 | 
             
                    Reline.readmultiline(prompt, true){ true }
         | 
| 88 89 | 
             
                  end
         | 
| 89 90 |  | 
| 91 | 
            +
                  def history
         | 
| 92 | 
            +
                    Reline::HISTORY
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 90 95 | 
             
                rescue LoadError
         | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 96 | 
            +
                  begin
         | 
| 97 | 
            +
                    require 'readline.so'
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def readline_setup
         | 
| 100 | 
            +
                      load_history_if_not_loaded
         | 
| 101 | 
            +
                      commands = DEBUGGER__.commands
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                      Readline.completion_proc = proc{|given|
         | 
| 104 | 
            +
                        buff = Readline.line_buffer
         | 
| 105 | 
            +
                        Readline.completion_append_character= ' '
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                        if /\s/ =~ buff # second parameters
         | 
| 108 | 
            +
                          given = File.expand_path(given + 'a').sub(/a\z/, '')
         | 
| 109 | 
            +
                          files = Dir.glob(given + '*')
         | 
| 110 | 
            +
                          if files.size == 1 && File.directory?(files.first)
         | 
| 111 | 
            +
                            Readline.completion_append_character= '/'
         | 
| 112 | 
            +
                          end
         | 
| 113 | 
            +
                          files
         | 
| 114 | 
            +
                        else
         | 
| 115 | 
            +
                          commands.keys.grep(/\A#{given}/)
         | 
| 116 | 
            +
                        end
         | 
| 117 | 
            +
                      }
         | 
| 118 | 
            +
                    end
         | 
| 93 119 |  | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
                       | 
| 97 | 
            -
             | 
| 120 | 
            +
                    def readline prompt
         | 
| 121 | 
            +
                      readline_setup
         | 
| 122 | 
            +
                      Readline.readline(prompt, true)
         | 
| 123 | 
            +
                    end
         | 
| 98 124 |  | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                       | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
                     | 
| 125 | 
            +
                    def history
         | 
| 126 | 
            +
                      Readline::HISTORY
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  rescue LoadError
         | 
| 130 | 
            +
                    def readline prompt
         | 
| 131 | 
            +
                      print prompt
         | 
| 132 | 
            +
                      gets
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    def history
         | 
| 136 | 
            +
                      nil
         | 
| 137 | 
            +
                    end
         | 
| 110 138 | 
             
                  end
         | 
| 139 | 
            +
                end
         | 
| 111 140 |  | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 141 | 
            +
                def history_file
         | 
| 142 | 
            +
                  CONFIG[:history_file] || File.expand_path("~/.rdbg_history")
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                FH = "# Today's OMIKUJI: "
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def read_history_file
         | 
| 148 | 
            +
                  if history && File.exists?(path = history_file)
         | 
| 149 | 
            +
                    f = (['', 'DAI-', 'CHU-', 'SHO-'].map{|e| e+'KICHI'}+['KYO']).sample
         | 
| 150 | 
            +
                    ["#{FH}#{f}".dup] + File.readlines(path)
         | 
| 151 | 
            +
                  else
         | 
| 152 | 
            +
                    []
         | 
| 115 153 | 
             
                  end
         | 
| 154 | 
            +
                end
         | 
| 116 155 |  | 
| 117 | 
            -
                 | 
| 118 | 
            -
                   | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 156 | 
            +
                def initialize
         | 
| 157 | 
            +
                  @init_history_lines = nil
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def load_history_if_not_loaded
         | 
| 161 | 
            +
                  return if @init_history_lines
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  @init_history_lines = load_history
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                def deactivate
         | 
| 167 | 
            +
                  if history && @init_history_lines
         | 
| 168 | 
            +
                    added_records = history.to_a[@init_history_lines .. -1]
         | 
| 169 | 
            +
                    path = history_file
         | 
| 170 | 
            +
                    max = CONFIG[:save_history] || 10_000
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    if !added_records.empty? && !path.empty?
         | 
| 173 | 
            +
                      orig_records = read_history_file
         | 
| 174 | 
            +
                      open(history_file, 'w'){|f|
         | 
| 175 | 
            +
                        (orig_records + added_records).last(max).each{|line|
         | 
| 176 | 
            +
                          if !line.start_with?(FH) && !line.strip.empty?
         | 
| 177 | 
            +
                            f.puts line.strip
         | 
| 178 | 
            +
                          end
         | 
| 179 | 
            +
                        }
         | 
| 180 | 
            +
                      }
         | 
| 181 | 
            +
                    end
         | 
| 121 182 | 
             
                  end
         | 
| 122 183 | 
             
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                def load_history
         | 
| 186 | 
            +
                  read_history_file.count{|line|
         | 
| 187 | 
            +
                    line.strip!
         | 
| 188 | 
            +
                    history << line unless line.empty?
         | 
| 189 | 
            +
                  }
         | 
| 123 190 | 
             
                end
         | 
| 124 | 
            -
              end
         | 
| 191 | 
            +
              end # class Console
         | 
| 125 192 | 
             
            end
         | 
| 126 193 |  | 
    
        data/lib/debug/local.rb
    CHANGED
    
    | @@ -26,8 +26,11 @@ module DEBUGGER__ | |
| 26 26 |  | 
| 27 27 | 
             
                def deactivate
         | 
| 28 28 | 
             
                  if SESSION.intercept_trap_sigint?
         | 
| 29 | 
            -
                     | 
| 29 | 
            +
                    prev = SESSION.intercept_trap_sigint_end
         | 
| 30 | 
            +
                    trap(:SIGINT, prev)
         | 
| 30 31 | 
             
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  @console.deactivate
         | 
| 31 34 | 
             
                end
         | 
| 32 35 |  | 
| 33 36 | 
             
                def width
         | 
| @@ -83,6 +86,24 @@ module DEBUGGER__ | |
| 83 86 | 
             
                    trap(:INT, prev_handler)
         | 
| 84 87 | 
             
                  end
         | 
| 85 88 | 
             
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def after_fork_parent
         | 
| 91 | 
            +
                  parent_pid = Process.pid
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  at_exit{
         | 
| 94 | 
            +
                    SESSION.intercept_trap_sigint_end
         | 
| 95 | 
            +
                    trap(:SIGINT, :IGNORE)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    if Process.pid == parent_pid
         | 
| 98 | 
            +
                      # only check child process from its parent
         | 
| 99 | 
            +
                      begin
         | 
| 100 | 
            +
                        # wait for all child processes to keep terminal
         | 
| 101 | 
            +
                        loop{ Process.waitpid }
         | 
| 102 | 
            +
                      rescue Errno::ESRCH, Errno::ECHILD
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
                  }
         | 
| 106 | 
            +
                end
         | 
| 86 107 | 
             
              end
         | 
| 87 108 | 
             
            end
         | 
| 88 109 |  | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            return if defined?(::DEBUGGER__)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # put the following line in .bash_profile
         | 
| 7 | 
            +
            #   export RUBYOPT="-r .../debug/prelude $(RUBYOPT)"
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
            module Kernel
         | 
| 10 | 
            +
              def debugger(*a, up_level: 0, **kw)
         | 
| 11 | 
            +
                begin
         | 
| 12 | 
            +
                  require_relative 'version'
         | 
| 13 | 
            +
                  cur_version = ::DEBUGGER__::VERSION
         | 
| 14 | 
            +
                  require_relative 'frame_info'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  if !defined?(::DEBUGGER__::SO_VERSION) || ::DEBUGGER__::VERSION != ::DEBUGGER__::SO_VERSION
         | 
| 17 | 
            +
                    ::Object.send(:remove_const, :DEBUGGER__)
         | 
| 18 | 
            +
                    raise LoadError
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                  require_relative 'session'
         | 
| 21 | 
            +
                  up_level += 1
         | 
| 22 | 
            +
                rescue LoadError
         | 
| 23 | 
            +
                  $LOADED_FEATURES.delete_if{|e|
         | 
| 24 | 
            +
                    e.start_with?(__dir__) || e.end_with?('debug/debug.so')
         | 
| 25 | 
            +
                  }
         | 
| 26 | 
            +
                  require 'debug/session'
         | 
| 27 | 
            +
                  require 'debug/version'
         | 
| 28 | 
            +
                  ::DEBUGGER__.info "Can not activate debug #{cur_version} specified by debug/prelude.rb. Activate debug #{DEBUGGER__::VERSION} instead."
         | 
| 29 | 
            +
                  up_level += 1
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                ::DEBUGGER__::start no_sigint_hook: true, nonstop: true
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                begin
         | 
| 35 | 
            +
                  debugger(*a, up_level: up_level, **kw)
         | 
| 36 | 
            +
                  self
         | 
| 37 | 
            +
                rescue ArgumentError # for 1.2.4 and earlier
         | 
| 38 | 
            +
                  debugger(*a, **kw)
         | 
| 39 | 
            +
                  self
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              alias bb debugger if ENV['RUBY_DEBUG_BB']
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            class Binding
         | 
| 47 | 
            +
              alias break debugger
         | 
| 48 | 
            +
              alias b debugger
         | 
| 49 | 
            +
            end
         | 
    
        data/lib/debug/server.rb
    CHANGED
    
    | @@ -15,6 +15,8 @@ module DEBUGGER__ | |
| 15 15 | 
             
                  @q_ans = nil
         | 
| 16 16 | 
             
                  @unsent_messages = []
         | 
| 17 17 | 
             
                  @width = 80
         | 
| 18 | 
            +
                  @repl = true
         | 
| 19 | 
            +
                  @session = nil
         | 
| 18 20 | 
             
                end
         | 
| 19 21 |  | 
| 20 22 | 
             
                class Terminate < StandardError
         | 
| @@ -22,6 +24,7 @@ module DEBUGGER__ | |
| 22 24 |  | 
| 23 25 | 
             
                def deactivate
         | 
| 24 26 | 
             
                  @reader_thread.raise Terminate
         | 
| 27 | 
            +
                  @reader_thread.join
         | 
| 25 28 | 
             
                end
         | 
| 26 29 |  | 
| 27 30 | 
             
                def accept
         | 
| @@ -36,9 +39,11 @@ module DEBUGGER__ | |
| 36 39 | 
             
                end
         | 
| 37 40 |  | 
| 38 41 | 
             
                def activate session, on_fork: false
         | 
| 42 | 
            +
                  @session = session
         | 
| 39 43 | 
             
                  @reader_thread = Thread.new do
         | 
| 40 44 | 
             
                    # An error on this thread should break the system.
         | 
| 41 45 | 
             
                    Thread.current.abort_on_exception = true
         | 
| 46 | 
            +
                    Thread.current.name = 'DEBUGGER__::Server::reader'
         | 
| 42 47 |  | 
| 43 48 | 
             
                    accept do |server, already_connected: false|
         | 
| 44 49 | 
             
                      DEBUGGER__.warn "Connected."
         | 
| @@ -52,7 +57,7 @@ module DEBUGGER__ | |
| 52 57 | 
             
                        # flush unsent messages
         | 
| 53 58 | 
             
                        @unsent_messages.each{|m|
         | 
| 54 59 | 
             
                          @sock.puts m
         | 
| 55 | 
            -
                        }
         | 
| 60 | 
            +
                        } if @repl
         | 
| 56 61 | 
             
                        @unsent_messages.clear
         | 
| 57 62 |  | 
| 58 63 | 
             
                        @q_msg = Queue.new
         | 
| @@ -68,6 +73,7 @@ module DEBUGGER__ | |
| 68 73 | 
             
                      raise # should catch at outer scope
         | 
| 69 74 | 
             
                    rescue => e
         | 
| 70 75 | 
             
                      DEBUGGER__.warn "ReaderThreadError: #{e}"
         | 
| 76 | 
            +
                      pp e.backtrace
         | 
| 71 77 | 
             
                    ensure
         | 
| 72 78 | 
             
                      DEBUGGER__.warn "Disconnected."
         | 
| 73 79 | 
             
                      @sock = nil
         | 
| @@ -103,23 +109,58 @@ module DEBUGGER__ | |
| 103 109 |  | 
| 104 110 | 
             
                    raise unless @sock.read(2) == "\r\n"
         | 
| 105 111 | 
             
                    self.extend(UI_DAP)
         | 
| 112 | 
            +
                    @repl = false
         | 
| 106 113 | 
             
                    dap_setup @sock.read($1.to_i)
         | 
| 114 | 
            +
                  when /^GET \/ HTTP\/1.1/
         | 
| 115 | 
            +
                    require_relative 'server_cdp'
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    self.extend(UI_CDP)
         | 
| 118 | 
            +
                    @repl = false
         | 
| 119 | 
            +
                    @web_sock = UI_CDP::WebSocket.new(@sock)
         | 
| 120 | 
            +
                    @web_sock.handshake
         | 
| 107 121 | 
             
                  else
         | 
| 108 122 | 
             
                    raise "Greeting message error: #{g}"
         | 
| 109 123 | 
             
                  end
         | 
| 110 124 | 
             
                end
         | 
| 111 125 |  | 
| 112 126 | 
             
                def process
         | 
| 113 | 
            -
                  while  | 
| 127 | 
            +
                  while true
         | 
| 128 | 
            +
                    DEBUGGER__.info "sleep IO.select"
         | 
| 129 | 
            +
                    r = IO.select([@sock])
         | 
| 130 | 
            +
                    DEBUGGER__.info "wakeup IO.select"
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    line = @session.process_group.sync do
         | 
| 133 | 
            +
                      unless IO.select([@sock], nil, nil, 0)
         | 
| 134 | 
            +
                        DEBUGGER__.info "UI_Server can not read"
         | 
| 135 | 
            +
                        break :can_not_read
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
                      @sock.gets&.chomp.tap{|line|
         | 
| 138 | 
            +
                        DEBUGGER__.info "UI_Server received: #{line}"
         | 
| 139 | 
            +
                      }
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    next if line == :can_not_read
         | 
| 143 | 
            +
             | 
| 114 144 | 
             
                    case line
         | 
| 115 145 | 
             
                    when /\Apause/
         | 
| 116 146 | 
             
                      pause
         | 
| 117 | 
            -
                    when /\Acommand ?(.+)/
         | 
| 118 | 
            -
                       | 
| 119 | 
            -
             | 
| 120 | 
            -
                       | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 147 | 
            +
                    when /\Acommand (\d+) (\d+) ?(.+)/
         | 
| 148 | 
            +
                      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                      if $1.to_i == Process.pid
         | 
| 151 | 
            +
                        @width = $2.to_i
         | 
| 152 | 
            +
                        @q_msg << $3
         | 
| 153 | 
            +
                      else
         | 
| 154 | 
            +
                        raise "pid:#{Process.pid} but get #{line}"
         | 
| 155 | 
            +
                      end
         | 
| 156 | 
            +
                    when /\Aanswer (\d+) (.*)/
         | 
| 157 | 
            +
                      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                      if $1.to_i == Process.pid
         | 
| 160 | 
            +
                        @q_ans << $2
         | 
| 161 | 
            +
                      else
         | 
| 162 | 
            +
                        raise "pid:#{Process.pid} but get #{line}"
         | 
| 163 | 
            +
                      end
         | 
| 123 164 | 
             
                    else
         | 
| 124 165 | 
             
                      STDERR.puts "unsupported: #{line}"
         | 
| 125 166 | 
             
                      exit!
         | 
| @@ -135,6 +176,21 @@ module DEBUGGER__ | |
| 135 176 | 
             
                  @width
         | 
| 136 177 | 
             
                end
         | 
| 137 178 |  | 
| 179 | 
            +
                def sigurg_overridden? prev_handler
         | 
| 180 | 
            +
                  case prev_handler
         | 
| 181 | 
            +
                  when "SYSTEM_DEFAULT"
         | 
| 182 | 
            +
                    false
         | 
| 183 | 
            +
                  when Proc
         | 
| 184 | 
            +
                    if prev_handler.source_location[0] == __FILE__
         | 
| 185 | 
            +
                      false
         | 
| 186 | 
            +
                    else
         | 
| 187 | 
            +
                      true
         | 
| 188 | 
            +
                    end
         | 
| 189 | 
            +
                  else
         | 
| 190 | 
            +
                    true
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 138 194 | 
             
                def setup_interrupt
         | 
| 139 195 | 
             
                  prev_handler = trap(:SIGURG) do
         | 
| 140 196 | 
             
                    # $stderr.puts "trapped SIGINT"
         | 
| @@ -148,8 +204,8 @@ module DEBUGGER__ | |
| 148 204 | 
             
                    end
         | 
| 149 205 | 
             
                  end
         | 
| 150 206 |  | 
| 151 | 
            -
                  if prev_handler | 
| 152 | 
            -
                    DEBUGGER__.warn "SIGURG handler is  | 
| 207 | 
            +
                  if sigurg_overridden?(prev_handler)
         | 
| 208 | 
            +
                    DEBUGGER__.warn "SIGURG handler is overridden by the debugger."
         | 
| 153 209 | 
             
                  end
         | 
| 154 210 | 
             
                  yield
         | 
| 155 211 | 
             
                ensure
         | 
| @@ -191,7 +247,7 @@ module DEBUGGER__ | |
| 191 247 |  | 
| 192 248 | 
             
                def ask prompt
         | 
| 193 249 | 
             
                  sock do |s|
         | 
| 194 | 
            -
                    s.puts "ask #{prompt}"
         | 
| 250 | 
            +
                    s.puts "ask #{Process.pid} #{prompt}"
         | 
| 195 251 | 
             
                    @q_ans.pop
         | 
| 196 252 | 
             
                  end
         | 
| 197 253 | 
             
                end
         | 
| @@ -220,13 +276,23 @@ module DEBUGGER__ | |
| 220 276 |  | 
| 221 277 | 
             
                def readline prompt
         | 
| 222 278 | 
             
                  input = (sock do |s|
         | 
| 223 | 
            -
                     | 
| 279 | 
            +
                    if @repl
         | 
| 280 | 
            +
                      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
         | 
| 281 | 
            +
                      line = "input #{Process.pid}"
         | 
| 282 | 
            +
                      DEBUGGER__.info "send: #{line}"
         | 
| 283 | 
            +
                      s.puts line
         | 
| 284 | 
            +
                    end
         | 
| 224 285 | 
             
                    sleep 0.01 until @q_msg
         | 
| 286 | 
            +
                    @q_msg.pop.tap{|msg|
         | 
| 287 | 
            +
                      DEBUGGER__.info "readline: #{msg.inspect}"
         | 
| 288 | 
            +
                    }
         | 
| 289 | 
            +
                  end || 'continue')
         | 
| 225 290 |  | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
| 229 | 
            -
             | 
| 291 | 
            +
                  if input.is_a?(String)
         | 
| 292 | 
            +
                    input.strip
         | 
| 293 | 
            +
                  else
         | 
| 294 | 
            +
                    input
         | 
| 295 | 
            +
                  end
         | 
| 230 296 | 
             
                end
         | 
| 231 297 |  | 
| 232 298 | 
             
                def pause
         | 
| @@ -240,10 +306,15 @@ module DEBUGGER__ | |
| 240 306 | 
             
                    s.puts "quit"
         | 
| 241 307 | 
             
                  end
         | 
| 242 308 | 
             
                end
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                def after_fork_parent
         | 
| 311 | 
            +
                  # do nothing
         | 
| 312 | 
            +
                end
         | 
| 243 313 | 
             
              end
         | 
| 244 314 |  | 
| 245 315 | 
             
              class UI_TcpServer < UI_ServerBase
         | 
| 246 316 | 
             
                def initialize host: nil, port: nil
         | 
| 317 | 
            +
                  @addr = nil
         | 
| 247 318 | 
             
                  @host = host || CONFIG[:host] || '127.0.0.1'
         | 
| 248 319 | 
             
                  @port = port || begin
         | 
| 249 320 | 
             
                    port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
         | 
| @@ -263,7 +334,24 @@ module DEBUGGER__ | |
| 263 334 |  | 
| 264 335 | 
             
                  begin
         | 
| 265 336 | 
             
                    Socket.tcp_server_sockets @host, @port do |socks|
         | 
| 266 | 
            -
                       | 
| 337 | 
            +
                      addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
         | 
| 338 | 
            +
                      rdbg = File.expand_path('../../exe/rdbg', __dir__)
         | 
| 339 | 
            +
             | 
| 340 | 
            +
                      DEBUGGER__.warn "Debugger can attach via TCP/IP (#{addr})"
         | 
| 341 | 
            +
                      DEBUGGER__.info <<~EOS
         | 
| 342 | 
            +
                      With rdbg, use the following command line:
         | 
| 343 | 
            +
                      #
         | 
| 344 | 
            +
                      #   #{rdbg} --attach #{addr.split(':').join(' ')}
         | 
| 345 | 
            +
                      #
         | 
| 346 | 
            +
                      EOS
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                      DEBUGGER__.warn <<~EOS if CONFIG[:open_frontend] == 'chrome'
         | 
| 349 | 
            +
                      With Chrome browser, type the following URL in the address-bar:
         | 
| 350 | 
            +
                      
         | 
| 351 | 
            +
                         devtools://devtools/bundled/inspector.html?ws=#{addr}
         | 
| 352 | 
            +
                      
         | 
| 353 | 
            +
                      EOS
         | 
| 354 | 
            +
             | 
| 267 355 | 
             
                      Socket.accept_loop(socks) do |sock, client|
         | 
| 268 356 | 
             
                        @client_addr = client
         | 
| 269 357 | 
             
                        yield @sock_for_fork = sock
         | 
| @@ -298,6 +386,64 @@ module DEBUGGER__ | |
| 298 386 | 
             
                  super()
         | 
| 299 387 | 
             
                end
         | 
| 300 388 |  | 
| 389 | 
            +
                def vscode_setup
         | 
| 390 | 
            +
                  require 'tmpdir'
         | 
| 391 | 
            +
                  require 'json'
         | 
| 392 | 
            +
                  require 'fileutils'
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                  dir = Dir.mktmpdir("ruby-debug-vscode-")
         | 
| 395 | 
            +
                  at_exit{
         | 
| 396 | 
            +
                    FileUtils.rm_rf dir
         | 
| 397 | 
            +
                  }
         | 
| 398 | 
            +
                  Dir.chdir(dir) do
         | 
| 399 | 
            +
                    Dir.mkdir('.vscode')
         | 
| 400 | 
            +
                    open('README.rb', 'w'){|f|
         | 
| 401 | 
            +
                      f.puts <<~MSG
         | 
| 402 | 
            +
                      # Wait for starting the attaching to the Ruby process
         | 
| 403 | 
            +
                      # This file will be removed at the end of the debuggee process.
         | 
| 404 | 
            +
                      #
         | 
| 405 | 
            +
                      # Note that vscode-rdbg extension is needed. Please install if you don't have.
         | 
| 406 | 
            +
                      MSG
         | 
| 407 | 
            +
                    }
         | 
| 408 | 
            +
                    open('.vscode/launch.json', 'w'){|f|
         | 
| 409 | 
            +
                      f.puts JSON.pretty_generate({
         | 
| 410 | 
            +
                        version: '0.2.0',
         | 
| 411 | 
            +
                        configurations: [
         | 
| 412 | 
            +
                        {
         | 
| 413 | 
            +
                          type: "rdbg",
         | 
| 414 | 
            +
                          name: "Attach with rdbg",
         | 
| 415 | 
            +
                          request: "attach",
         | 
| 416 | 
            +
                          rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
         | 
| 417 | 
            +
                          debugPort: @sock_path,
         | 
| 418 | 
            +
                          autoAttach: true,
         | 
| 419 | 
            +
                        }
         | 
| 420 | 
            +
                        ]
         | 
| 421 | 
            +
                      })
         | 
| 422 | 
            +
                    }
         | 
| 423 | 
            +
                  end
         | 
| 424 | 
            +
             | 
| 425 | 
            +
                  cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
         | 
| 426 | 
            +
                  cmdline = cmds.join(' ')
         | 
| 427 | 
            +
                  ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
         | 
| 428 | 
            +
             | 
| 429 | 
            +
                  STDERR.puts "Launching: #{cmdline}"
         | 
| 430 | 
            +
                  env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
         | 
| 431 | 
            +
             | 
| 432 | 
            +
                  unless system(env, *cmds)
         | 
| 433 | 
            +
                    DEBUGGER__.warn <<~MESSAGE
         | 
| 434 | 
            +
                    Can not invoke the command.
         | 
| 435 | 
            +
                    Use the command-line on your terminal (with modification if you need).
         | 
| 436 | 
            +
             | 
| 437 | 
            +
                      #{cmdline}
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                    If your application is running on a SSH remote host, please try:
         | 
| 440 | 
            +
             | 
| 441 | 
            +
                      #{ssh_cmdline}
         | 
| 442 | 
            +
             | 
| 443 | 
            +
                    MESSAGE
         | 
| 444 | 
            +
                  end
         | 
| 445 | 
            +
                end
         | 
| 446 | 
            +
             | 
| 301 447 | 
             
                def accept
         | 
| 302 448 | 
             
                  super # for fork
         | 
| 303 449 |  | 
| @@ -310,14 +456,29 @@ module DEBUGGER__ | |
| 310 456 | 
             
                  end
         | 
| 311 457 |  | 
| 312 458 | 
             
                  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
         | 
| 313 | 
            -
                   | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 459 | 
            +
                  vscode_setup if CONFIG[:open_frontend] == 'vscode'
         | 
| 460 | 
            +
             | 
| 461 | 
            +
                  begin
         | 
| 462 | 
            +
                    Socket.unix_server_loop @sock_path do |sock, client|
         | 
| 463 | 
            +
                      @sock_for_fork = sock
         | 
| 464 | 
            +
                      @client_addr = client
         | 
| 465 | 
            +
             | 
| 466 | 
            +
                      yield sock
         | 
| 467 | 
            +
                    ensure
         | 
| 468 | 
            +
                      sock.close
         | 
| 469 | 
            +
                      @sock_for_fork = nil
         | 
| 470 | 
            +
                    end
         | 
| 471 | 
            +
                  rescue Errno::ECONNREFUSED => _e
         | 
| 472 | 
            +
                    ::DEBUGGER__.warn "#{_e.message} (socket path: #{@sock_path})"
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                    if @sock_path.start_with? Config.unix_domain_socket_tmpdir
         | 
| 475 | 
            +
                      # try on homedir
         | 
| 476 | 
            +
                      @sock_path = Config.create_unix_domain_socket_name(unix_domain_socket_homedir)
         | 
| 477 | 
            +
                      ::DEBUGGER__.warn "retry with #{@sock_path}"
         | 
| 478 | 
            +
                      retry
         | 
| 479 | 
            +
                    else
         | 
| 480 | 
            +
                      raise
         | 
| 481 | 
            +
                    end
         | 
| 321 482 | 
             
                  end
         | 
| 322 483 | 
             
                end
         | 
| 323 484 | 
             
              end
         |