debug 1.0.0.beta4 → 1.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +48 -0
- data/Gemfile +1 -1
- data/README.md +72 -37
- data/debug.gemspec +2 -0
- data/exe/rdbg +8 -1
- data/ext/debug/debug.c +9 -8
- data/lib/debug/breakpoint.rb +71 -24
- data/lib/debug/client.rb +49 -6
- data/lib/debug/color.rb +70 -0
- data/lib/debug/config.rb +39 -5
- data/lib/debug/console.rb +8 -1
- data/lib/debug/frame_info.rb +61 -30
- data/lib/debug/open.rb +2 -0
- data/lib/debug/run.rb +2 -0
- data/lib/debug/server.rb +72 -19
- data/lib/debug/server_dap.rb +605 -0
- data/lib/debug/session.rb +181 -86
- data/lib/debug/source_repository.rb +53 -33
- data/lib/debug/test_console.rb +0 -0
- data/lib/debug/thread_client.rb +91 -23
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +51 -28
- metadata +21 -3
data/lib/debug/client.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'io/console/size'
|
3
|
+
|
2
4
|
require_relative 'config'
|
5
|
+
require_relative 'version'
|
6
|
+
|
7
|
+
# $VERBOSE = true
|
3
8
|
|
4
9
|
module DEBUGGER__
|
5
10
|
class CommandLineOptionError < Exception; end
|
@@ -18,6 +23,8 @@ module DEBUGGER__
|
|
18
23
|
end
|
19
24
|
|
20
25
|
def initialize argv
|
26
|
+
return util(argv) if String === argv
|
27
|
+
|
21
28
|
case argv.size
|
22
29
|
when 0
|
23
30
|
connect_unix
|
@@ -36,6 +43,23 @@ module DEBUGGER__
|
|
36
43
|
else
|
37
44
|
raise CommandLineOptionError
|
38
45
|
end
|
46
|
+
|
47
|
+
@width = IO.console_size[1]
|
48
|
+
@width_changed = false
|
49
|
+
|
50
|
+
send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def util name
|
54
|
+
case name
|
55
|
+
when 'gen-sockpath'
|
56
|
+
puts DEBUGGER__.create_unix_domain_socket_name
|
57
|
+
when 'list-socks'
|
58
|
+
cleanup_unix_domain_sockets
|
59
|
+
puts list_connections
|
60
|
+
else
|
61
|
+
raise "Unknown utility: #{name}"
|
62
|
+
end
|
39
63
|
end
|
40
64
|
|
41
65
|
def cleanup_unix_domain_sockets
|
@@ -50,16 +74,20 @@ module DEBUGGER__
|
|
50
74
|
end
|
51
75
|
end
|
52
76
|
|
77
|
+
def list_connections
|
78
|
+
Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
|
79
|
+
end
|
80
|
+
|
53
81
|
def connect_unix name = nil
|
54
82
|
if name
|
55
83
|
if File.exist? name
|
56
84
|
@s = Socket.unix(name)
|
57
85
|
else
|
58
|
-
@s = Socket.unix(File.join(DEBUGGER__.
|
86
|
+
@s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_dir, name))
|
59
87
|
end
|
60
88
|
else
|
61
89
|
cleanup_unix_domain_sockets
|
62
|
-
files =
|
90
|
+
files = list_connections
|
63
91
|
case files.size
|
64
92
|
when 0
|
65
93
|
$stderr.puts "No debug session is available."
|
@@ -80,13 +108,22 @@ module DEBUGGER__
|
|
80
108
|
@s = Socket.tcp(host, port)
|
81
109
|
end
|
82
110
|
|
111
|
+
def send msg
|
112
|
+
p send: msg if $VERBOSE
|
113
|
+
@s.puts msg
|
114
|
+
end
|
115
|
+
|
83
116
|
def connect
|
84
117
|
trap(:SIGINT){
|
85
|
-
|
118
|
+
send "pause"
|
119
|
+
}
|
120
|
+
trap(:SIGWINCH){
|
121
|
+
@width = IO.console_size[1]
|
122
|
+
@width_changed = true
|
86
123
|
}
|
87
124
|
|
88
125
|
while line = @s.gets
|
89
|
-
|
126
|
+
p recv: line if $VERBOSE
|
90
127
|
case line
|
91
128
|
when /^out (.*)/
|
92
129
|
puts "#{$1}"
|
@@ -102,10 +139,16 @@ module DEBUGGER__
|
|
102
139
|
end
|
103
140
|
|
104
141
|
line = (line || 'quit').strip
|
105
|
-
|
142
|
+
|
143
|
+
if @width_changed
|
144
|
+
@width_changed = false
|
145
|
+
send "width #{@width}"
|
146
|
+
end
|
147
|
+
|
148
|
+
send "command #{line}"
|
106
149
|
when /^ask (.*)/
|
107
150
|
print $1
|
108
|
-
|
151
|
+
send "answer #{gets || ''}"
|
109
152
|
when /^quit/
|
110
153
|
raise 'quit'
|
111
154
|
else
|
data/lib/debug/color.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
begin
|
2
|
+
require 'irb/color'
|
3
|
+
require "irb/color_printer"
|
4
|
+
rescue LoadError
|
5
|
+
warn "DEBUGGER: can not load newer irb for coloring. Write 'gem \"debug\" in your Gemfile."
|
6
|
+
end
|
7
|
+
|
8
|
+
module DEBUGGER__
|
9
|
+
module Color
|
10
|
+
if defined? IRB::Color.colorize
|
11
|
+
def colorize str, color
|
12
|
+
if CONFIG[:use_colorize]
|
13
|
+
IRB::Color.colorize str, color
|
14
|
+
else
|
15
|
+
str
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
def colorize str, color
|
20
|
+
str
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if defined? IRB::ColorPrinter.pp
|
25
|
+
def color_pp obj
|
26
|
+
IRB::ColorPrinter.pp(obj, "")
|
27
|
+
end
|
28
|
+
else
|
29
|
+
def color_pp obj
|
30
|
+
obj.pretty_inspect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def colored_inspect obj
|
35
|
+
if CONFIG[:use_colorize]
|
36
|
+
color_pp obj
|
37
|
+
else
|
38
|
+
obj.pretty_inspect
|
39
|
+
end
|
40
|
+
rescue => ex
|
41
|
+
err_msg = "#{ex.inspect} rescued during inspection"
|
42
|
+
string_result = obj.to_s rescue nil
|
43
|
+
|
44
|
+
# don't colorize the string here because it's not from user's application
|
45
|
+
if string_result
|
46
|
+
%Q{"#{string_result}" from #to_s because #{err_msg}}
|
47
|
+
else
|
48
|
+
err_msg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if defined? IRB::Color.colorize_code
|
53
|
+
def colorize_code code
|
54
|
+
IRB::Color.colorize_code(code)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
def colorize_code code
|
58
|
+
code
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def colorize_cyan(str)
|
63
|
+
colorize(str, [:CYAN, :BOLD])
|
64
|
+
end
|
65
|
+
|
66
|
+
def colorize_blue(str)
|
67
|
+
colorize(str, [:BLUE, :BOLD])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/debug/config.rb
CHANGED
@@ -47,6 +47,7 @@ module DEBUGGER__
|
|
47
47
|
host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
|
48
48
|
sock_path: 'RUBY_DEBUG_SOCK_PATH', # UNIX Domain Socket remote debugging: socket path
|
49
49
|
sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
|
50
|
+
cookie: 'RUBY_DEBUG_COOKIE', # Cookie for negotiation
|
50
51
|
}.freeze
|
51
52
|
|
52
53
|
def self.config_to_env config
|
@@ -77,35 +78,46 @@ module DEBUGGER__
|
|
77
78
|
return config if !argv || argv.empty?
|
78
79
|
|
79
80
|
require 'optparse'
|
81
|
+
require_relative 'version'
|
80
82
|
|
81
83
|
opt = OptionParser.new do |o|
|
82
84
|
o.banner = "#{$0} [options] -- [debuggee options]"
|
83
85
|
o.separator ''
|
86
|
+
o.version = ::DEBUGGER__::VERSION
|
84
87
|
|
85
88
|
o.separator 'Debug console mode:'
|
86
89
|
o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
|
87
90
|
config[:nonstop] = '1'
|
88
91
|
end
|
89
92
|
|
90
|
-
o.on('-e
|
93
|
+
o.on('-e COMMAND', 'execute debug command at the beginning of the script.') do |cmd|
|
91
94
|
config[:commands] ||= ''
|
92
95
|
config[:commands] << cmd + ';;'
|
93
96
|
end
|
94
97
|
|
95
|
-
o.on('-
|
98
|
+
o.on('-x FILE', '--init-script=FILE', 'execute debug command in the FILE.') do |file|
|
99
|
+
config[:init_script] = file
|
100
|
+
end
|
101
|
+
|
102
|
+
o.separator ''
|
103
|
+
|
104
|
+
o.on('-O', '--open', 'Start remote debugging with opening the network port.',
|
96
105
|
'If TCP/IP options are not given,',
|
97
106
|
'a UNIX domain socket will be used.') do
|
98
107
|
config[:remote] = true
|
99
108
|
end
|
100
|
-
o.on('--sock-path=
|
109
|
+
o.on('--sock-path=SOCK_PATH', 'UNIX Doman socket path') do |path|
|
101
110
|
config[:sock_path] = path
|
102
111
|
end
|
103
|
-
o.on('--port=
|
112
|
+
o.on('--port=PORT', 'Listening TCP/IP port') do |port|
|
104
113
|
config[:port] = port
|
105
114
|
end
|
106
|
-
o.on('--host=
|
115
|
+
o.on('--host=HOST', 'Listening TCP/IP host') do |host|
|
107
116
|
config[:host] = host
|
108
117
|
end
|
118
|
+
o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
|
119
|
+
config[:cookie] = c
|
120
|
+
end
|
109
121
|
|
110
122
|
rdbg = 'rdbg'
|
111
123
|
|
@@ -133,6 +145,28 @@ module DEBUGGER__
|
|
133
145
|
o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
|
134
146
|
o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
|
135
147
|
o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
|
148
|
+
|
149
|
+
o.separator ''
|
150
|
+
o.separator 'Other options:'
|
151
|
+
|
152
|
+
o.on("-h", "--help", "Print help") do
|
153
|
+
puts o
|
154
|
+
exit
|
155
|
+
end
|
156
|
+
|
157
|
+
o.on('-c', '--command', 'Command mode (first argument is command name)') do
|
158
|
+
config[:command] = true
|
159
|
+
end
|
160
|
+
|
161
|
+
o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
|
162
|
+
Client.new(name)
|
163
|
+
exit
|
164
|
+
end
|
165
|
+
|
166
|
+
o.separator ''
|
167
|
+
o.separator 'NOTE'
|
168
|
+
o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
|
169
|
+
o.separator ' Please use the remote debugging feature carefully.'
|
136
170
|
end
|
137
171
|
|
138
172
|
opt.parse!(argv)
|
data/lib/debug/console.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require_relative 'session'
|
2
|
+
return unless defined?(DEBUGGER__)
|
3
|
+
|
4
|
+
require 'io/console/size'
|
2
5
|
|
3
6
|
module DEBUGGER__
|
4
|
-
class UI_Console
|
7
|
+
class UI_Console < UI_Base
|
5
8
|
def initialize
|
6
9
|
end
|
7
10
|
|
@@ -9,6 +12,10 @@ module DEBUGGER__
|
|
9
12
|
false
|
10
13
|
end
|
11
14
|
|
15
|
+
def width
|
16
|
+
IO.console_size[1]
|
17
|
+
end
|
18
|
+
|
12
19
|
def quit n
|
13
20
|
exit n
|
14
21
|
end
|
data/lib/debug/frame_info.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
|
2
2
|
module DEBUGGER__
|
3
3
|
FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
|
4
|
-
:has_return_value, :return_value,
|
5
|
-
|
4
|
+
:has_return_value, :return_value,
|
5
|
+
:has_raised_exception, :raised_exception,
|
6
|
+
:show_line)
|
6
7
|
|
7
8
|
# extend FrameInfo with debug.so
|
8
9
|
if File.exist? File.join(__dir__, 'debug.so')
|
@@ -39,33 +40,73 @@ module DEBUGGER__
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
def name
|
44
|
+
# p frame_type: frame_type, self: self
|
45
|
+
case frame_type
|
46
|
+
when :block
|
47
|
+
level, block_loc, _args = block_identifier
|
48
|
+
"block in #{block_loc}#{level}"
|
49
|
+
when :method
|
50
|
+
ci, _args = method_identifier
|
51
|
+
"#{ci}"
|
52
|
+
when :c
|
53
|
+
c_identifier
|
54
|
+
when :other
|
55
|
+
other_identifier
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
42
59
|
def file_lines
|
43
|
-
SESSION.source(
|
60
|
+
SESSION.source(self.iseq)
|
44
61
|
end
|
45
62
|
|
46
|
-
def
|
63
|
+
def frame_type
|
47
64
|
if binding && iseq
|
48
65
|
if iseq.type == :block
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
location.label.sub('block'){ "block#{args_str}" }
|
55
|
-
elsif (callee = binding.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
|
56
|
-
args = parameters_info iseq.locals[0...argc]
|
57
|
-
"#{klass_sig}#{callee}(#{args})"
|
66
|
+
:block
|
67
|
+
elsif callee
|
68
|
+
:method
|
58
69
|
else
|
59
|
-
|
70
|
+
:other
|
60
71
|
end
|
61
72
|
else
|
62
|
-
|
73
|
+
:c
|
63
74
|
end
|
64
75
|
end
|
65
76
|
|
77
|
+
BLOCK_LABL_REGEXP = /\Ablock( \(\d+ levels\))* in (.+)\z/
|
78
|
+
|
79
|
+
def block_identifier
|
80
|
+
return unless frame_type == :block
|
81
|
+
args = parameters_info(iseq.argc)
|
82
|
+
_, level, block_loc = location.label.match(BLOCK_LABL_REGEXP).to_a
|
83
|
+
[level || "", block_loc, args]
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_identifier
|
87
|
+
return unless frame_type == :method
|
88
|
+
args = parameters_info(iseq.argc)
|
89
|
+
ci = "#{klass_sig}#{callee}"
|
90
|
+
[ci, args]
|
91
|
+
end
|
92
|
+
|
93
|
+
def c_identifier
|
94
|
+
return unless frame_type == :c
|
95
|
+
"[C] #{klass_sig}#{location.base_label}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def other_identifier
|
99
|
+
return unless frame_type == :other
|
100
|
+
location.label
|
101
|
+
end
|
102
|
+
|
103
|
+
def callee
|
104
|
+
@callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
|
105
|
+
end
|
106
|
+
|
66
107
|
def return_str
|
67
108
|
if binding && iseq && has_return_value
|
68
|
-
short_inspect(return_value)
|
109
|
+
DEBUGGER__.short_inspect(return_value)
|
69
110
|
end
|
70
111
|
end
|
71
112
|
|
@@ -75,31 +116,21 @@ module DEBUGGER__
|
|
75
116
|
|
76
117
|
private
|
77
118
|
|
78
|
-
SHORT_INSPECT_LENGTH = 40
|
79
|
-
|
80
|
-
def short_inspect obj
|
81
|
-
str = obj.inspect
|
82
|
-
if str.length > SHORT_INSPECT_LENGTH
|
83
|
-
str[0...SHORT_INSPECT_LENGTH] + '...'
|
84
|
-
else
|
85
|
-
str
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
119
|
def get_singleton_class obj
|
90
120
|
obj.singleton_class # TODO: don't use it
|
91
121
|
rescue TypeError
|
92
122
|
nil
|
93
123
|
end
|
94
124
|
|
95
|
-
def parameters_info
|
125
|
+
def parameters_info(argc)
|
126
|
+
vars = iseq.locals[0...argc]
|
96
127
|
vars.map{|var|
|
97
128
|
begin
|
98
|
-
|
129
|
+
{ name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
|
99
130
|
rescue NameError, TypeError
|
100
131
|
nil
|
101
132
|
end
|
102
|
-
}.compact
|
133
|
+
}.compact
|
103
134
|
end
|
104
135
|
|
105
136
|
def klass_sig
|
data/lib/debug/open.rb
CHANGED
data/lib/debug/run.rb
CHANGED
data/lib/debug/server.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'socket'
|
2
|
+
|
2
3
|
require_relative 'session'
|
4
|
+
return unless defined?(DEBUGGER__)
|
5
|
+
|
3
6
|
require_relative 'config'
|
7
|
+
require_relative 'version'
|
4
8
|
|
5
9
|
module DEBUGGER__
|
6
|
-
class UI_ServerBase
|
10
|
+
class UI_ServerBase < UI_Base
|
7
11
|
def initialize
|
8
12
|
@sock = nil
|
9
13
|
@accept_m = Mutex.new
|
@@ -12,13 +16,19 @@ module DEBUGGER__
|
|
12
16
|
@q_msg = Queue.new
|
13
17
|
@q_ans = Queue.new
|
14
18
|
@unsent_messages = []
|
19
|
+
@width = 80
|
15
20
|
|
16
21
|
@reader_thread = Thread.new do
|
22
|
+
# An error on this thread should break the system.
|
23
|
+
Thread.current.abort_on_exception = true
|
24
|
+
|
17
25
|
accept do |server|
|
18
26
|
DEBUGGER__.message "Connected."
|
19
27
|
|
20
28
|
@accept_m.synchronize{
|
21
29
|
@sock = server
|
30
|
+
greeting
|
31
|
+
|
22
32
|
@accept_cv.signal
|
23
33
|
|
24
34
|
# flush unsent messages
|
@@ -27,26 +37,13 @@ module DEBUGGER__
|
|
27
37
|
}
|
28
38
|
@unsent_messages.clear
|
29
39
|
}
|
30
|
-
@q_msg = Queue.new
|
31
|
-
@q_ans = Queue.new
|
32
40
|
|
33
41
|
setup_interrupt do
|
34
|
-
|
35
|
-
|
36
|
-
while line = @sock.gets
|
37
|
-
case line
|
38
|
-
when /\Apause/
|
39
|
-
pause
|
40
|
-
when /\Acommand ?(.+)/
|
41
|
-
@q_msg << $1
|
42
|
-
when /\Aanswer (.*)/
|
43
|
-
@q_ans << $1
|
44
|
-
else
|
45
|
-
STDERR.puts "unsupported: #{line}"
|
46
|
-
exit!
|
47
|
-
end
|
48
|
-
end
|
42
|
+
process
|
49
43
|
end
|
44
|
+
|
45
|
+
rescue => e
|
46
|
+
DEBUGGER__.message "ReaderThreadError: #{e}"
|
50
47
|
ensure
|
51
48
|
DEBUGGER__.message "Disconnected."
|
52
49
|
@sock = nil
|
@@ -56,10 +53,64 @@ module DEBUGGER__
|
|
56
53
|
end
|
57
54
|
end
|
58
55
|
|
56
|
+
def greeting
|
57
|
+
case g = @sock.gets
|
58
|
+
when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
|
59
|
+
v, w, c = $1, $2, $3
|
60
|
+
# TODO: protocol version
|
61
|
+
if v != VERSION
|
62
|
+
raise "Incompatible version (#{VERSION} client:#{$1})"
|
63
|
+
end
|
64
|
+
|
65
|
+
cookie = CONFIG[:cookie]
|
66
|
+
if cookie && cookie != c
|
67
|
+
raise "Cookie mismatch (#{$2.inspect} was sent)"
|
68
|
+
end
|
69
|
+
|
70
|
+
@width = w.to_i
|
71
|
+
|
72
|
+
when /^Content-Length: (\d+)/
|
73
|
+
require_relative 'server_dap'
|
74
|
+
|
75
|
+
raise unless @sock.read(2) == "\r\n"
|
76
|
+
self.extend(UI_DAP)
|
77
|
+
dap_setup @sock.read($1.to_i)
|
78
|
+
else
|
79
|
+
raise "Greeting message error: #{g}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def process
|
84
|
+
@q_msg = Queue.new
|
85
|
+
@q_ans = Queue.new
|
86
|
+
|
87
|
+
pause
|
88
|
+
|
89
|
+
while line = @sock.gets
|
90
|
+
case line
|
91
|
+
when /\Apause/
|
92
|
+
pause
|
93
|
+
when /\Acommand ?(.+)/
|
94
|
+
@q_msg << $1
|
95
|
+
when /\Aanswer (.*)/
|
96
|
+
@q_ans << $1
|
97
|
+
when /\Awidth (.+)/
|
98
|
+
@width = $1.to_i
|
99
|
+
else
|
100
|
+
STDERR.puts "unsupported: #{line}"
|
101
|
+
exit!
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
59
106
|
def remote?
|
60
107
|
true
|
61
108
|
end
|
62
109
|
|
110
|
+
def width
|
111
|
+
@width
|
112
|
+
end
|
113
|
+
|
63
114
|
def setup_interrupt
|
64
115
|
prev_handler = trap(:SIGINT) do
|
65
116
|
# $stderr.puts "trapped SIGINT"
|
@@ -158,7 +209,7 @@ module DEBUGGER__
|
|
158
209
|
|
159
210
|
class UI_TcpServer < UI_ServerBase
|
160
211
|
def initialize host: nil, port: nil
|
161
|
-
@host = host || ::DEBUGGER__::CONFIG[:host] || '
|
212
|
+
@host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
|
162
213
|
@port = port || begin
|
163
214
|
port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
164
215
|
if /\A\d+\z/ !~ port_str
|
@@ -207,6 +258,8 @@ module DEBUGGER__
|
|
207
258
|
Socket.unix_server_loop @sock_path do |sock, client|
|
208
259
|
@client_addr = client
|
209
260
|
yield sock
|
261
|
+
ensure
|
262
|
+
sock.close
|
210
263
|
end
|
211
264
|
end
|
212
265
|
end
|