debug 1.0.0.beta7 → 1.0.0

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,66 +1,87 @@
1
1
  # frozen_string_literal: true
2
+ module DEBUGGER__
3
+ class Console
4
+ begin
5
+ raise LoadError if CONFIG[:no_reline]
6
+ require 'reline'
2
7
 
3
- require_relative 'session'
4
- return unless defined?(DEBUGGER__)
8
+ # reline 0.2.7 or later is required.
9
+ raise LoadError if Reline::VERSION < '0.2.6'
5
10
 
6
- require 'io/console/size'
11
+ require_relative 'color'
12
+ include Color
7
13
 
8
- module DEBUGGER__
9
- class UI_Console < UI_Base
10
- def initialize
11
- unless CONFIG[:no_sigint_hook]
12
- @prev_handler = trap(:SIGINT){
13
- ThreadClient.current.on_trap :SIGINT
14
- }
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
15
23
  end
16
- end
17
24
 
18
- def close
19
- if @prev_handler
20
- trap(:SIGINT, @prev_handler)
21
- end
22
- end
25
+ def readline_setup prompt
26
+ commands = DEBUGGER__.commands
23
27
 
24
- def remote?
25
- false
26
- end
28
+ Reline.completion_proc = -> given do
29
+ buff = Reline.line_buffer
30
+ Reline.completion_append_character= ' '
27
31
 
28
- def width
29
- if (w = IO.console_size[1]) == 0 # for tests PTY
30
- 80
31
- else
32
- w
33
- end
34
- end
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
35
43
 
36
- def quit n
37
- exit n
38
- end
44
+ Reline.output_modifier_proc = -> buff, **kw do
45
+ c, rest = get_command buff
39
46
 
40
- def ask prompt
41
- setup_interrupt do
42
- print prompt
43
- ($stdin.gets || '').strip
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
44
66
  end
45
- end
46
67
 
47
- def puts str = nil
48
- case str
49
- when Array
50
- str.each{|line|
51
- $stdout.puts line.chomp
52
- }
53
- when String
54
- str.each_line{|line|
55
- $stdout.puts line.chomp
56
- }
57
- when nil
58
- $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 }
59
80
  end
60
- end
61
81
 
82
+ rescue LoadError
62
83
  begin
63
- require 'readline'
84
+ require 'readline.so'
64
85
 
65
86
  def readline_setup
66
87
  Readline.completion_proc = proc{|given|
@@ -75,38 +96,22 @@ module DEBUGGER__
75
96
  end
76
97
  files
77
98
  else
78
- DEBUGGER__.commands.grep(/\A#{given}/)
99
+ DEBUGGER__.commands.keys.grep(/\A#{given}/)
79
100
  end
80
101
  }
81
102
  end
82
103
 
83
- def readline_body
104
+ def readline prompt
84
105
  readline_setup
85
- Readline.readline("\n(rdbg) ", true)
106
+ Readline.readline(prompt, true)
86
107
  end
108
+
87
109
  rescue LoadError
88
- def readline_body
89
- print "\n(rdbg) "
110
+ def readline prompt
111
+ print prompt
90
112
  gets
91
113
  end
92
114
  end
93
-
94
- def readline
95
- setup_interrupt do
96
- (readline_body || 'quit').strip
97
- end
98
- end
99
-
100
- def setup_interrupt
101
- current_thread = Thread.current # should be session_server thread
102
-
103
- prev_handler = trap(:INT){
104
- current_thread.raise Interrupt
105
- }
106
-
107
- yield
108
- ensure
109
- trap(:INT, prev_handler)
110
115
  end
111
116
  end
112
117
  end
@@ -4,7 +4,9 @@ module DEBUGGER__
4
4
  FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
5
5
  :has_return_value, :return_value,
6
6
  :has_raised_exception, :raised_exception,
7
- :show_line)
7
+ :show_line,
8
+ :_local_variables, :_callee # for recorder
9
+ )
8
10
 
9
11
  # extend FrameInfo with debug.so
10
12
  if File.exist? File.join(__dir__, 'debug.so')
@@ -25,10 +27,10 @@ module DEBUGGER__
25
27
  end
26
28
 
27
29
  def pretty_path
28
- use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
30
+ use_short_path = CONFIG[:use_short_path]
29
31
 
30
32
  case
31
- when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
33
+ when use_short_path && path.start_with?(dir = CONFIG["rubylibdir"] + '/')
32
34
  path.sub(dir, '$(rubylibdir)/')
33
35
  when use_short_path && Gem.path.any? do |gp|
34
36
  path.start_with?(dir = gp + '/gems/')
@@ -62,7 +64,7 @@ module DEBUGGER__
62
64
  end
63
65
 
64
66
  def frame_type
65
- if binding && iseq
67
+ if self.local_variables && iseq
66
68
  if iseq.type == :block
67
69
  :block
68
70
  elsif callee
@@ -102,11 +104,11 @@ module DEBUGGER__
102
104
  end
103
105
 
104
106
  def callee
105
- @callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
107
+ self._callee ||= self.binding&.eval('__callee__')
106
108
  end
107
109
 
108
110
  def return_str
109
- if binding && iseq && has_return_value
111
+ if self.binding && iseq && has_return_value
110
112
  DEBUGGER__.short_inspect(return_value)
111
113
  end
112
114
  end
@@ -115,6 +117,33 @@ module DEBUGGER__
115
117
  "#{pretty_path}:#{location.lineno}"
116
118
  end
117
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
+
118
147
  private
119
148
 
120
149
  def get_singleton_class obj
@@ -123,11 +152,15 @@ module DEBUGGER__
123
152
  nil
124
153
  end
125
154
 
155
+ private def local_variable_get var
156
+ local_variables[var]
157
+ end
158
+
126
159
  def parameters_info(argc)
127
160
  vars = iseq.locals[0...argc]
128
161
  vars.map{|var|
129
162
  begin
130
- { name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
163
+ { name: var, value: DEBUGGER__.short_inspect(local_variable_get(var)) }
131
164
  rescue NameError, TypeError
132
165
  nil
133
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
@@ -7,7 +7,7 @@
7
7
  # Otherwise, UNIX domain socket is used.
8
8
  #
9
9
 
10
- require_relative 'server'
10
+ require_relative 'session'
11
11
  return unless defined?(DEBUGGER__)
12
12
 
13
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,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'socket'
4
-
5
- require_relative 'session'
6
- return unless defined?(DEBUGGER__)
7
-
8
4
  require_relative 'config'
9
5
  require_relative 'version'
10
6
 
11
7
  module DEBUGGER__
12
8
  class UI_ServerBase < UI_Base
13
9
  def initialize
14
- @sock = nil
10
+ @sock = @sock_for_fork = nil
15
11
  @accept_m = Mutex.new
16
12
  @accept_cv = ConditionVariable.new
17
13
  @client_addr = nil
@@ -20,11 +16,33 @@ module DEBUGGER__
20
16
  @unsent_messages = []
21
17
  @width = 80
22
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
23
41
  @reader_thread = Thread.new do
24
42
  # An error on this thread should break the system.
25
43
  Thread.current.abort_on_exception = true
26
44
 
27
- accept do |server|
45
+ accept do |server, already_connected: false|
28
46
  DEBUGGER__.warn "Connected."
29
47
 
30
48
  @accept_m.synchronize{
@@ -41,14 +59,17 @@ module DEBUGGER__
41
59
 
42
60
  @q_msg = Queue.new
43
61
  @q_ans = Queue.new
44
- }
62
+ } unless already_connected
45
63
 
46
64
  setup_interrupt do
65
+ pause unless already_connected
47
66
  process
48
67
  end
49
68
 
69
+ rescue Terminate
70
+ raise # should catch at outer scope
50
71
  rescue => e
51
- DEBUGGER__.warn "ReaderThreadError: #{e}", :error
72
+ DEBUGGER__.warn "ReaderThreadError: #{e}"
52
73
  ensure
53
74
  DEBUGGER__.warn "Disconnected."
54
75
  @sock = nil
@@ -56,7 +77,10 @@ module DEBUGGER__
56
77
  @q_msg = nil
57
78
  @q_ans.close
58
79
  @q_ans = nil
59
- end
80
+ end # accept
81
+
82
+ rescue Terminate
83
+ # ignore
60
84
  end
61
85
  end
62
86
 
@@ -88,8 +112,6 @@ module DEBUGGER__
88
112
  end
89
113
 
90
114
  def process
91
- pause
92
-
93
115
  while line = @sock.gets
94
116
  case line
95
117
  when /\Apause/
@@ -133,10 +155,6 @@ module DEBUGGER__
133
155
  trap(:SIGINT, prev_handler)
134
156
  end
135
157
 
136
- def accept
137
- raise "NOT IMPLEMENTED ERROR"
138
- end
139
-
140
158
  attr_reader :reader_thread
141
159
 
142
160
  class NoRemoteError < Exception; end
@@ -158,7 +176,7 @@ module DEBUGGER__
158
176
  until s = @sock
159
177
  @accept_m.synchronize{
160
178
  unless @sock
161
- DEBUGGER__.warn "wait for debuger connection..."
179
+ DEBUGGER__.warn "wait for debugger connection..."
162
180
  @accept_cv.wait(@accept_m)
163
181
  end
164
182
  }
@@ -199,12 +217,15 @@ module DEBUGGER__
199
217
  end
200
218
  end
201
219
 
202
- def readline
203
- (sock do |s|
220
+ def readline prompt
221
+ input = (sock do |s|
204
222
  s.puts "input"
205
223
  sleep 0.01 until @q_msg
224
+
206
225
  @q_msg.pop
207
226
  end || 'continue').strip
227
+
228
+ input
208
229
  end
209
230
 
210
231
  def pause
@@ -222,9 +243,9 @@ module DEBUGGER__
222
243
 
223
244
  class UI_TcpServer < UI_ServerBase
224
245
  def initialize host: nil, port: nil
225
- @host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
246
+ @host = host || CONFIG[:host] || '127.0.0.1'
226
247
  @port = port || begin
227
- 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.")
228
249
  if /\A\d+\z/ !~ port_str
229
250
  raise "Specify digits for port number"
230
251
  else
@@ -236,17 +257,34 @@ module DEBUGGER__
236
257
  end
237
258
 
238
259
  def accept
239
- Socket.tcp_server_sockets @host, @port do |socks|
240
- ::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
241
- Socket.accept_loop(socks) do |sock, client|
242
- @client_addr = client
243
- 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
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
244
278
  end
279
+ rescue Terminate
280
+ # OK
281
+ rescue => e
282
+ $stderr.puts e.inspect, e.message
283
+ pp e.backtrace
284
+ exit
245
285
  end
246
- rescue => e
247
- $stderr.puts e.message
248
- pp e.backtrace
249
- exit
286
+ ensure
287
+ @sock_for_fork = nil
250
288
  end
251
289
  end
252
290
 
@@ -254,14 +292,17 @@ module DEBUGGER__
254
292
  def initialize sock_dir: nil, sock_path: nil
255
293
  @sock_path = sock_path
256
294
  @sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
295
+ @sock_for_fork = nil
257
296
 
258
297
  super()
259
298
  end
260
299
 
261
300
  def accept
301
+ super # for fork
302
+
262
303
  case
263
304
  when @sock_path
264
- when sp = ::DEBUGGER__::CONFIG[:sock_path]
305
+ when sp = CONFIG[:sock_path]
265
306
  @sock_path = sp
266
307
  else
267
308
  @sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
@@ -269,10 +310,13 @@ module DEBUGGER__
269
310
 
270
311
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
271
312
  Socket.unix_server_loop @sock_path do |sock, client|
313
+ @sock_for_fork = sock
272
314
  @client_addr = client
315
+
273
316
  yield sock
274
317
  ensure
275
318
  sock.close
319
+ @sock_for_fork = nil
276
320
  end
277
321
  end
278
322
  end