debug 1.0.0.beta5 → 1.0.0.rc1

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.
data/lib/debug/console.rb CHANGED
@@ -1,49 +1,87 @@
1
- require_relative 'session'
2
- return unless defined?(DEBUGGER__)
1
+ # frozen_string_literal: true
2
+ module DEBUGGER__
3
+ class Console
4
+ begin
5
+ raise LoadError if CONFIG[:no_reline]
6
+ require 'reline'
3
7
 
4
- require 'io/console/size'
8
+ # reline 0.2.7 or later is required.
9
+ raise LoadError if Reline::VERSION < '0.2.6'
5
10
 
6
- module DEBUGGER__
7
- class UI_Console < UI_Base
8
- def initialize
9
- end
11
+ require_relative 'color'
12
+ include Color
10
13
 
11
- def remote?
12
- false
13
- end
14
+ # 0.2.7 has SIGWINCH issue on non-main thread
15
+ class ::Reline::LineEditor
16
+ m = Module.new do
17
+ def reset(prompt = '', encoding:)
18
+ super
19
+ Signal.trap(:SIGWINCH, nil)
20
+ end
21
+ end
22
+ prepend m
23
+ end
14
24
 
15
- def width
16
- IO.console_size[1]
17
- end
25
+ def readline_setup prompt
26
+ commands = DEBUGGER__.commands
18
27
 
19
- def quit n
20
- exit n
21
- end
28
+ Reline.completion_proc = -> given do
29
+ buff = Reline.line_buffer
30
+ Reline.completion_append_character= ' '
22
31
 
23
- def ask prompt
24
- setup_interrupt do
25
- print prompt
26
- (gets || '').strip
32
+ if /\s/ =~ buff # second parameters
33
+ given = File.expand_path(given + 'a').sub(/a\z/, '')
34
+ files = Dir.glob(given + '*')
35
+ if files.size == 1 && File.directory?(files.first)
36
+ Reline.completion_append_character= '/'
37
+ end
38
+ files
39
+ else
40
+ commands.keys.grep(/\A#{given}/)
41
+ end
42
+ end
43
+
44
+ Reline.output_modifier_proc = -> buff, **kw do
45
+ c, rest = get_command buff
46
+
47
+ case
48
+ when commands.keys.include?(c = c.strip)
49
+ # [:DIM, :CYAN, :BLUE, :CLEAR, :UNDERLINE, :REVERSE, :RED, :GREEN, :MAGENTA, :BOLD, :YELLOW]
50
+ cmd = colorize(c.strip, [:CYAN, :UNDERLINE])
51
+
52
+ if commands[c] == c
53
+ rprompt = colorize(" # command", [:DIM])
54
+ else
55
+ rprompt = colorize(" # #{commands[c]} command", [:DIM])
56
+ end
57
+
58
+ rest = (rest ? colorize_code(rest) : '') + rprompt
59
+ cmd + rest
60
+ when !rest && /\A\s*[a-z]*\z/ =~ c
61
+ buff
62
+ else
63
+ colorize_code(buff.chomp) + colorize(" # ruby", [:DIM])
64
+ end
65
+ end
27
66
  end
28
- end
29
67
 
30
- def puts str = nil
31
- case str
32
- when Array
33
- str.each{|line|
34
- $stdout.puts line.chomp
35
- }
36
- when String
37
- str.each_line{|line|
38
- $stdout.puts line.chomp
39
- }
40
- when nil
41
- $stdout.puts
68
+ private def get_command line
69
+ case line.chomp
70
+ when /\A(\s*[a-z]+)(\s.*)?\z$/
71
+ return $1, $2
72
+ else
73
+ line.chomp
74
+ end
75
+ end
76
+
77
+ def readline prompt
78
+ readline_setup prompt
79
+ Reline.readmultiline(prompt, true){ true }
42
80
  end
43
- end
44
81
 
82
+ rescue LoadError
45
83
  begin
46
- require 'readline'
84
+ require 'readline.so'
47
85
 
48
86
  def readline_setup
49
87
  Readline.completion_proc = proc{|given|
@@ -58,38 +96,22 @@ module DEBUGGER__
58
96
  end
59
97
  files
60
98
  else
61
- DEBUGGER__.commands.grep(/\A#{given}/)
99
+ DEBUGGER__.commands.keys.grep(/\A#{given}/)
62
100
  end
63
101
  }
64
102
  end
65
103
 
66
- def readline_body
104
+ def readline prompt
67
105
  readline_setup
68
- Readline.readline("\n(rdbg) ", true)
106
+ Readline.readline(prompt, true)
69
107
  end
108
+
70
109
  rescue LoadError
71
- def readline_body
72
- print "\n(rdbg) "
110
+ def readline prompt
111
+ print prompt
73
112
  gets
74
113
  end
75
114
  end
76
-
77
- def readline
78
- setup_interrupt do
79
- (readline_body || 'quit').strip
80
- end
81
- end
82
-
83
- def setup_interrupt
84
- current_thread = Thread.current # should be session_server thread
85
-
86
- prev_handler = trap(:INT){
87
- current_thread.raise Interrupt
88
- }
89
-
90
- yield
91
- ensure
92
- trap(:INT, prev_handler)
93
115
  end
94
116
  end
95
117
  end
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module DEBUGGER__
3
4
  FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
5
  :has_return_value, :return_value,
5
6
  :has_raised_exception, :raised_exception,
6
- :show_line)
7
+ :show_line,
8
+ :_local_variables, :_callee # for recorder
9
+ )
7
10
 
8
11
  # extend FrameInfo with debug.so
9
12
  if File.exist? File.join(__dir__, 'debug.so')
10
13
  require_relative 'debug.so'
11
14
  else
12
- require "debug/debug"
15
+ require_relative 'debug'
13
16
  end
14
17
 
15
18
  class FrameInfo
@@ -24,10 +27,10 @@ module DEBUGGER__
24
27
  end
25
28
 
26
29
  def pretty_path
27
- use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
30
+ use_short_path = CONFIG[:use_short_path]
28
31
 
29
32
  case
30
- when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
33
+ when use_short_path && path.start_with?(dir = CONFIG["rubylibdir"] + '/')
31
34
  path.sub(dir, '$(rubylibdir)/')
32
35
  when use_short_path && Gem.path.any? do |gp|
33
36
  path.start_with?(dir = gp + '/gems/')
@@ -61,7 +64,7 @@ module DEBUGGER__
61
64
  end
62
65
 
63
66
  def frame_type
64
- if binding && iseq
67
+ if self.local_variables && iseq
65
68
  if iseq.type == :block
66
69
  :block
67
70
  elsif callee
@@ -101,11 +104,11 @@ module DEBUGGER__
101
104
  end
102
105
 
103
106
  def callee
104
- @callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
107
+ self._callee ||= self.binding&.eval('__callee__')
105
108
  end
106
109
 
107
110
  def return_str
108
- if binding && iseq && has_return_value
111
+ if self.binding && iseq && has_return_value
109
112
  DEBUGGER__.short_inspect(return_value)
110
113
  end
111
114
  end
@@ -114,6 +117,33 @@ module DEBUGGER__
114
117
  "#{pretty_path}:#{location.lineno}"
115
118
  end
116
119
 
120
+ private def make_binding
121
+ __newb__ = self.self.instance_eval('binding')
122
+ self.local_variables.each{|var, val|
123
+ __newb__.local_variable_set(var, val)
124
+ }
125
+ __newb__
126
+ end
127
+
128
+ def eval_binding
129
+ if b = self.binding
130
+ b
131
+ elsif self.local_variables
132
+ make_binding
133
+ end
134
+ end
135
+
136
+ def local_variables
137
+ if lvars = self._local_variables
138
+ lvars
139
+ elsif b = self.binding
140
+ lvars = b.local_variables.map{|var|
141
+ [var, b.local_variable_get(var)]
142
+ }.to_h
143
+ self._local_variables = lvars
144
+ end
145
+ end
146
+
117
147
  private
118
148
 
119
149
  def get_singleton_class obj
@@ -122,11 +152,15 @@ module DEBUGGER__
122
152
  nil
123
153
  end
124
154
 
155
+ private def local_variable_get var
156
+ local_variables[var]
157
+ end
158
+
125
159
  def parameters_info(argc)
126
160
  vars = iseq.locals[0...argc]
127
161
  vars.map{|var|
128
162
  begin
129
- { name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
163
+ { name: var, value: DEBUGGER__.short_inspect(local_variable_get(var)) }
130
164
  rescue NameError, TypeError
131
165
  nil
132
166
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console/size'
4
+ require_relative 'console'
5
+
6
+ module DEBUGGER__
7
+ class UI_LocalConsole < UI_Base
8
+ def initialize
9
+ @console = Console.new
10
+
11
+ unless CONFIG[:no_sigint_hook]
12
+ @prev_handler = trap(:SIGINT){
13
+ if SESSION.active?
14
+ ThreadClient.current.on_trap :SIGINT
15
+ end
16
+ }
17
+ end
18
+ end
19
+
20
+ def close
21
+ if @prev_handler
22
+ trap(:SIGINT, @prev_handler)
23
+ end
24
+ end
25
+
26
+ def remote?
27
+ false
28
+ end
29
+
30
+ def activate on_fork: false
31
+ # Do nothing
32
+ end
33
+
34
+ def deactivate
35
+ # Do nothing
36
+ end
37
+
38
+ def width
39
+ if (w = IO.console_size[1]) == 0 # for tests PTY
40
+ 80
41
+ else
42
+ w
43
+ end
44
+ end
45
+
46
+ def quit n
47
+ exit n
48
+ end
49
+
50
+ def ask prompt
51
+ setup_interrupt do
52
+ print prompt
53
+ ($stdin.gets || '').strip
54
+ end
55
+ end
56
+
57
+ def puts str = nil
58
+ case str
59
+ when Array
60
+ str.each{|line|
61
+ $stdout.puts line.chomp
62
+ }
63
+ when String
64
+ str.each_line{|line|
65
+ $stdout.puts line.chomp
66
+ }
67
+ when nil
68
+ $stdout.puts
69
+ end
70
+ end
71
+
72
+ def readline prompt = '(rdbg)'
73
+ setup_interrupt do
74
+ (@console.readline(prompt) || 'quit').strip
75
+ end
76
+ end
77
+
78
+ def setup_interrupt
79
+ current_thread = Thread.current # should be session_server thread
80
+
81
+ prev_handler = trap(:INT){
82
+ current_thread.raise Interrupt
83
+ }
84
+
85
+ yield
86
+ ensure
87
+ trap(:INT, prev_handler)
88
+ end
89
+ end
90
+ end
91
+
data/lib/debug/open.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Open the door for the debugger to connect.
3
4
  # Users can connect to debuggee program with "rdbg --attach" option.
@@ -6,7 +7,7 @@
6
7
  # Otherwise, UNIX domain socket is used.
7
8
  #
8
9
 
9
- require_relative 'server'
10
+ require_relative 'session'
10
11
  return unless defined?(DEBUGGER__)
11
12
 
12
13
  DEBUGGER__.open
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Open the door for the debugger to connect.
4
+ # Unlike debug/open, it does not stop at the beginning of the program.
5
+ # Users can connect to debuggee program with "rdbg --attach" option or
6
+ # VSCode attach type.
7
+ #
8
+ # If RUBY_DEBUG_PORT envval is provided (digits), open TCP/IP port.
9
+ # Otherwise, UNIX domain socket is used.
10
+ #
11
+
12
+ require_relative 'session'
13
+ return unless defined?(DEBUGGER__)
14
+
15
+ DEBUGGER__.open(nonstop: true)
data/lib/debug/server.rb CHANGED
@@ -1,29 +1,49 @@
1
- require 'socket'
2
-
3
- require_relative 'session'
4
- return unless defined?(DEBUGGER__)
1
+ # frozen_string_literal: true
5
2
 
3
+ require 'socket'
6
4
  require_relative 'config'
7
5
  require_relative 'version'
8
6
 
9
7
  module DEBUGGER__
10
8
  class UI_ServerBase < UI_Base
11
9
  def initialize
12
- @sock = nil
10
+ @sock = @sock_for_fork = nil
13
11
  @accept_m = Mutex.new
14
12
  @accept_cv = ConditionVariable.new
15
13
  @client_addr = nil
16
- @q_msg = Queue.new
17
- @q_ans = Queue.new
14
+ @q_msg = nil
15
+ @q_ans = nil
18
16
  @unsent_messages = []
19
17
  @width = 80
20
18
 
19
+ activate
20
+ end
21
+
22
+ class Terminate < StandardError
23
+ end
24
+
25
+ def deactivate
26
+ @reader_thread.raise Terminate
27
+ end
28
+
29
+ def accept
30
+ if @sock_for_fork
31
+ begin
32
+ yield @sock_for_fork, already_connected: true
33
+ ensure
34
+ @sock_for_fork.close
35
+ @sock_for_fork = nil
36
+ end
37
+ end
38
+ end
39
+
40
+ def activate on_fork: false
21
41
  @reader_thread = Thread.new do
22
42
  # An error on this thread should break the system.
23
43
  Thread.current.abort_on_exception = true
24
44
 
25
- accept do |server|
26
- DEBUGGER__.message "Connected."
45
+ accept do |server, already_connected: false|
46
+ DEBUGGER__.warn "Connected."
27
47
 
28
48
  @accept_m.synchronize{
29
49
  @sock = server
@@ -36,20 +56,31 @@ module DEBUGGER__
36
56
  @sock.puts m
37
57
  }
38
58
  @unsent_messages.clear
39
- }
59
+
60
+ @q_msg = Queue.new
61
+ @q_ans = Queue.new
62
+ } unless already_connected
40
63
 
41
64
  setup_interrupt do
65
+ pause unless already_connected
42
66
  process
43
67
  end
44
68
 
69
+ rescue Terminate
70
+ raise # should catch at outer scope
45
71
  rescue => e
46
- DEBUGGER__.message "ReaderThreadError: #{e}"
72
+ DEBUGGER__.warn "ReaderThreadError: #{e}"
47
73
  ensure
48
- DEBUGGER__.message "Disconnected."
74
+ DEBUGGER__.warn "Disconnected."
49
75
  @sock = nil
50
76
  @q_msg.close
77
+ @q_msg = nil
51
78
  @q_ans.close
52
- end
79
+ @q_ans = nil
80
+ end # accept
81
+
82
+ rescue Terminate
83
+ # ignore
53
84
  end
54
85
  end
55
86
 
@@ -81,11 +112,6 @@ module DEBUGGER__
81
112
  end
82
113
 
83
114
  def process
84
- @q_msg = Queue.new
85
- @q_ans = Queue.new
86
-
87
- pause
88
-
89
115
  while line = @sock.gets
90
116
  case line
91
117
  when /\Apause/
@@ -129,10 +155,6 @@ module DEBUGGER__
129
155
  trap(:SIGINT, prev_handler)
130
156
  end
131
157
 
132
- def accept
133
- raise "NOT IMPLEMENTED ERROR"
134
- end
135
-
136
158
  attr_reader :reader_thread
137
159
 
138
160
  class NoRemoteError < Exception; end
@@ -141,12 +163,20 @@ module DEBUGGER__
141
163
  if s = @sock # already connection
142
164
  # ok
143
165
  elsif skip == true # skip process
144
- return yield nil
166
+ no_sock = true
167
+ r = @accept_m.synchronize do
168
+ if @sock
169
+ no_sock = false
170
+ else
171
+ yield nil
172
+ end
173
+ end
174
+ return r if no_sock
145
175
  else # wait for connection
146
176
  until s = @sock
147
177
  @accept_m.synchronize{
148
178
  unless @sock
149
- DEBUGGER__.message "wait for debuger connection..."
179
+ DEBUGGER__.warn "wait for debugger connection..."
150
180
  @accept_cv.wait(@accept_m)
151
181
  end
152
182
  }
@@ -187,11 +217,15 @@ module DEBUGGER__
187
217
  end
188
218
  end
189
219
 
190
- def readline
191
- (sock do |s|
220
+ def readline prompt
221
+ input = (sock do |s|
192
222
  s.puts "input"
223
+ sleep 0.01 until @q_msg
224
+
193
225
  @q_msg.pop
194
226
  end || 'continue').strip
227
+
228
+ input
195
229
  end
196
230
 
197
231
  def pause
@@ -209,9 +243,9 @@ module DEBUGGER__
209
243
 
210
244
  class UI_TcpServer < UI_ServerBase
211
245
  def initialize host: nil, port: nil
212
- @host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
246
+ @host = host || CONFIG[:host] || '127.0.0.1'
213
247
  @port = port || begin
214
- port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
248
+ port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
215
249
  if /\A\d+\z/ !~ port_str
216
250
  raise "Specify digits for port number"
217
251
  else
@@ -223,17 +257,34 @@ module DEBUGGER__
223
257
  end
224
258
 
225
259
  def accept
226
- Socket.tcp_server_sockets @host, @port do |socks|
227
- ::DEBUGGER__.message "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
228
- Socket.accept_loop(socks) do |sock, client|
229
- @client_addr = client
230
- yield sock
260
+ retry_cnt = 0
261
+ super # for fork
262
+
263
+ begin
264
+ Socket.tcp_server_sockets @host, @port do |socks|
265
+ ::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
266
+ Socket.accept_loop(socks) do |sock, client|
267
+ @client_addr = client
268
+ yield @sock_for_fork = sock
269
+ end
231
270
  end
271
+ rescue Errno::EADDRINUSE
272
+ if retry_cnt < 10
273
+ retry_cnt += 1
274
+ sleep 0.1
275
+ retry
276
+ else
277
+ raise
278
+ end
279
+ rescue Terminate
280
+ # OK
281
+ rescue => e
282
+ $stderr.puts e.inspect, e.message
283
+ pp e.backtrace
284
+ exit
232
285
  end
233
- rescue => e
234
- $stderr.puts e.message
235
- pp e.backtrace
236
- exit
286
+ ensure
287
+ @sock_for_fork = nil
237
288
  end
238
289
  end
239
290
 
@@ -241,30 +292,32 @@ module DEBUGGER__
241
292
  def initialize sock_dir: nil, sock_path: nil
242
293
  @sock_path = sock_path
243
294
  @sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
295
+ @sock_for_fork = nil
244
296
 
245
297
  super()
246
298
  end
247
299
 
248
300
  def accept
301
+ super # for fork
302
+
249
303
  case
250
304
  when @sock_path
251
- when sp = ::DEBUGGER__::CONFIG[:sock_path]
305
+ when sp = CONFIG[:sock_path]
252
306
  @sock_path = sp
253
307
  else
254
308
  @sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
255
309
  end
256
310
 
257
- ::DEBUGGER__.message "Debugger can attach via UNIX domain socket (#{@sock_path})"
311
+ ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
258
312
  Socket.unix_server_loop @sock_path do |sock, client|
313
+ @sock_for_fork = sock
259
314
  @client_addr = client
315
+
260
316
  yield sock
261
317
  ensure
262
318
  sock.close
319
+ @sock_for_fork = nil
263
320
  end
264
321
  end
265
322
  end
266
-
267
- def self.message msg
268
- $stderr.puts "DEBUGGER: #{msg}"
269
- end
270
323
  end