debug 1.4.0 → 1.9.2

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +210 -6
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +0 -0
  5. data/README.md +161 -85
  6. data/Rakefile +33 -10
  7. data/TODO.md +8 -8
  8. data/debug.gemspec +9 -7
  9. data/exe/rdbg +23 -4
  10. data/ext/debug/debug.c +111 -21
  11. data/ext/debug/extconf.rb +23 -0
  12. data/ext/debug/iseq_collector.c +2 -0
  13. data/lib/debug/abbrev_command.rb +77 -0
  14. data/lib/debug/breakpoint.rb +102 -74
  15. data/lib/debug/client.rb +46 -12
  16. data/lib/debug/color.rb +0 -0
  17. data/lib/debug/config.rb +129 -36
  18. data/lib/debug/console.rb +46 -40
  19. data/lib/debug/dap_custom/traceInspector.rb +336 -0
  20. data/lib/debug/frame_info.rb +40 -25
  21. data/lib/debug/irb_integration.rb +37 -0
  22. data/lib/debug/local.rb +17 -11
  23. data/lib/debug/open.rb +0 -0
  24. data/lib/debug/open_nonstop.rb +0 -0
  25. data/lib/debug/prelude.rb +3 -2
  26. data/lib/debug/server.rb +126 -56
  27. data/lib/debug/server_cdp.rb +673 -248
  28. data/lib/debug/server_dap.rb +497 -261
  29. data/lib/debug/session.rb +899 -441
  30. data/lib/debug/source_repository.rb +122 -49
  31. data/lib/debug/start.rb +1 -1
  32. data/lib/debug/thread_client.rb +460 -155
  33. data/lib/debug/tracer.rb +10 -16
  34. data/lib/debug/version.rb +1 -1
  35. data/lib/debug.rb +7 -2
  36. data/misc/README.md.erb +106 -56
  37. metadata +14 -24
  38. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
  39. data/.github/ISSUE_TEMPLATE/custom.md +0 -10
  40. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  41. data/.github/pull_request_template.md +0 -9
  42. data/.github/workflows/ruby.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/bin/console +0 -14
  45. data/bin/gentest +0 -30
  46. data/bin/setup +0 -8
  47. data/lib/debug/bp.vim +0 -68
data/lib/debug/server.rb CHANGED
@@ -19,8 +19,9 @@ module DEBUGGER__
19
19
  @session = nil
20
20
  end
21
21
 
22
- class Terminate < StandardError
23
- end
22
+ class Terminate < StandardError; end
23
+ class GreetingError < StandardError; end
24
+ class RetryConnection < StandardError; end
24
25
 
25
26
  def deactivate
26
27
  @reader_thread.raise Terminate
@@ -47,10 +48,13 @@ module DEBUGGER__
47
48
 
48
49
  accept do |server, already_connected: false|
49
50
  DEBUGGER__.warn "Connected."
51
+ greeting_done = false
52
+ @need_pause_at_first = true
50
53
 
51
54
  @accept_m.synchronize{
52
55
  @sock = server
53
56
  greeting
57
+ greeting_done = true
54
58
 
55
59
  @accept_cv.signal
56
60
 
@@ -65,17 +69,23 @@ module DEBUGGER__
65
69
  } unless already_connected
66
70
 
67
71
  setup_interrupt do
68
- pause unless already_connected
72
+ pause if !already_connected && @need_pause_at_first
69
73
  process
70
74
  end
71
75
 
76
+ rescue GreetingError => e
77
+ DEBUGGER__.warn "GreetingError: #{e.message}"
78
+ next
72
79
  rescue Terminate
73
80
  raise # should catch at outer scope
81
+ rescue RetryConnection
82
+ next
74
83
  rescue => e
75
84
  DEBUGGER__.warn "ReaderThreadError: #{e}"
76
85
  pp e.backtrace
77
86
  ensure
78
- cleanup_reader
87
+ DEBUGGER__.warn "Disconnected."
88
+ cleanup_reader if greeting_done
79
89
  end # accept
80
90
 
81
91
  rescue Terminate
@@ -84,7 +94,7 @@ module DEBUGGER__
84
94
  end
85
95
 
86
96
  def cleanup_reader
87
- DEBUGGER__.warn "Disconnected."
97
+ @sock.close if @sock
88
98
  @sock = nil
89
99
  @q_msg.close
90
100
  @q_msg = nil
@@ -92,21 +102,58 @@ module DEBUGGER__
92
102
  @q_ans = nil
93
103
  end
94
104
 
105
+ def check_cookie c
106
+ cookie = CONFIG[:cookie]
107
+ if cookie && cookie != c
108
+ raise GreetingError, "Cookie mismatch (#{$2.inspect} was sent)"
109
+ end
110
+ end
111
+
112
+ def parse_option params
113
+ case params.strip
114
+ when /width:\s+(\d+)/
115
+ @width = $1.to_i
116
+ parse_option $~.post_match
117
+ when /cookie:\s+(\S+)/
118
+ check_cookie $1 if $1 != '-'
119
+ parse_option $~.post_match
120
+ when /nonstop: (true|false)/
121
+ @need_pause_at_first = false if $1 == 'true'
122
+ parse_option $~.post_match
123
+ when /(.+):(.+)/
124
+ raise GreetingError, "Unkown option: #{params}"
125
+ else
126
+ # OK
127
+ end
128
+ end
129
+
95
130
  def greeting
96
131
  case g = @sock.gets
97
- when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
98
- v, w, c = $1, $2, $3
132
+ when /^info cookie:\s+(.*)$/
133
+ require 'etc'
134
+
135
+ check_cookie $1
136
+ @sock.puts "PID: #{Process.pid}, $0: #{$0}, session_name: #{CONFIG[:session_name]}"
137
+ @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
138
+ @sock.puts "uname: #{Etc.uname.inspect}"
139
+ @sock.close
140
+ raise GreetingError, "HEAD request"
141
+
142
+ when /^version:\s+(\S+)\s+(.+)$/
143
+ v, params = $1, $2
144
+
99
145
  # TODO: protocol version
100
146
  if v != VERSION
101
- raise "Incompatible version (#{VERSION} client:#{$1})"
147
+ @sock.puts msg = "out DEBUGGER: Incompatible version (server:#{VERSION} and client:#{$1})"
148
+ raise GreetingError, msg
102
149
  end
150
+ parse_option(params)
103
151
 
104
- cookie = CONFIG[:cookie]
105
- if cookie && cookie != c
106
- raise "Cookie mismatch (#{$2.inspect} was sent)"
107
- end
108
-
109
- @width = w.to_i
152
+ session_name = CONFIG[:session_name]
153
+ session_name_str = ", session_name:#{session_name}" if session_name
154
+ puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}#{session_name_str}"
155
+ puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
156
+ puts
110
157
 
111
158
  when /^Content-Length: (\d+)/
112
159
  require_relative 'server_dap'
@@ -114,37 +161,38 @@ module DEBUGGER__
114
161
  raise unless @sock.read(2) == "\r\n"
115
162
  self.extend(UI_DAP)
116
163
  @repl = false
164
+ @need_pause_at_first = false
117
165
  dap_setup @sock.read($1.to_i)
118
- when /^GET \/ HTTP\/1.1/
166
+
167
+ when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
168
+ # The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.
169
+
119
170
  require_relative 'server_cdp'
120
171
 
121
172
  self.extend(UI_CDP)
122
- @repl = false
123
- CONFIG.set_config no_color: true
124
-
125
- @ws_server = UI_CDP::WebSocketServer.new(@sock)
126
- @ws_server.handshake
173
+ send_chrome_response g
127
174
  else
128
- raise "Greeting message error: #{g}"
175
+ raise GreetingError, "Unknown greeting message: #{g}"
129
176
  end
130
177
  end
131
178
 
132
179
  def process
133
180
  while true
134
- DEBUGGER__.info "sleep IO.select"
135
- r = IO.select([@sock])
136
- DEBUGGER__.info "wakeup IO.select"
181
+ DEBUGGER__.debug{ "sleep IO.select" }
182
+ _r = IO.select([@sock])
183
+ DEBUGGER__.debug{ "wakeup IO.select" }
137
184
 
138
185
  line = @session.process_group.sync do
139
186
  unless IO.select([@sock], nil, nil, 0)
140
- DEBUGGER__.info "UI_Server can not read"
187
+ DEBUGGER__.debug{ "UI_Server can not read" }
141
188
  break :can_not_read
142
189
  end
143
190
  @sock.gets&.chomp.tap{|line|
144
- DEBUGGER__.info "UI_Server received: #{line}"
191
+ DEBUGGER__.debug{ "UI_Server received: #{line}" }
145
192
  }
146
193
  end
147
194
 
195
+ return unless line
148
196
  next if line == :can_not_read
149
197
 
150
198
  case line
@@ -168,7 +216,7 @@ module DEBUGGER__
168
216
  raise "pid:#{Process.pid} but get #{line}"
169
217
  end
170
218
  else
171
- STDERR.puts "unsupported: #{line}"
219
+ STDERR.puts "unsupported: #{line.inspect}"
172
220
  exit!
173
221
  end
174
222
  end
@@ -290,16 +338,18 @@ module DEBUGGER__
290
338
  end
291
339
 
292
340
  def readline prompt
293
- input = (sock do |s|
341
+ input = (sock(skip: CONFIG[:skip_bp]) do |s|
342
+ next unless s
343
+
294
344
  if @repl
295
345
  raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
296
346
  line = "input #{Process.pid}"
297
- DEBUGGER__.info "send: #{line}"
347
+ DEBUGGER__.debug{ "send: #{line}" }
298
348
  s.puts line
299
349
  end
300
350
  sleep 0.01 until @q_msg
301
351
  @q_msg.pop.tap{|msg|
302
- DEBUGGER__.info "readline: #{msg.inspect}"
352
+ DEBUGGER__.debug{ "readline: #{msg.inspect}" }
303
353
  }
304
354
  end || 'continue')
305
355
 
@@ -315,7 +365,7 @@ module DEBUGGER__
315
365
  Process.kill(TRAP_SIGNAL, Process.pid)
316
366
  end
317
367
 
318
- def quit n
368
+ def quit n, &_b
319
369
  # ignore n
320
370
  sock do |s|
321
371
  s.puts "quit"
@@ -325,20 +375,31 @@ module DEBUGGER__
325
375
  def after_fork_parent
326
376
  # do nothing
327
377
  end
378
+
379
+ def vscode_setup debug_port
380
+ require_relative 'server_dap'
381
+ UI_DAP.setup debug_port
382
+ end
328
383
  end
329
384
 
330
385
  class UI_TcpServer < UI_ServerBase
331
386
  def initialize host: nil, port: nil
332
- @addr = nil
333
- @host = host || CONFIG[:host] || '127.0.0.1'
334
- @port = port || begin
335
- port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
336
- if /\A\d+\z/ !~ port_str
337
- raise "Specify digits for port number"
338
- else
387
+ @local_addr = nil
388
+ @host = host || CONFIG[:host]
389
+ @port_save_file = nil
390
+ @port = begin
391
+ port_str = (port && port.to_s) || CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
392
+ case port_str
393
+ when /\A\d+\z/
339
394
  port_str.to_i
395
+ when /\A(\d+):(.+)\z/
396
+ @port_save_file = $2
397
+ $1.to_i
398
+ else
399
+ raise "Specify digits for port number"
340
400
  end
341
401
  end
402
+ @uuid = nil # for CDP
342
403
 
343
404
  super()
344
405
  end
@@ -346,14 +407,14 @@ module DEBUGGER__
346
407
  def chrome_setup
347
408
  require_relative 'server_cdp'
348
409
 
349
- unless @chrome_pid = UI_CDP.setup_chrome(@addr)
350
- DEBUGGER__.warn <<~EOS if CONFIG[:open_frontend] == 'chrome'
351
- With Chrome browser, type the following URL in the address-bar:
352
-
353
- devtools://devtools/bundled/inspector.html?ws=#{@addr}
354
-
355
- EOS
356
- end
410
+ @uuid = SecureRandom.uuid
411
+ @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
412
+ DEBUGGER__.warn <<~EOS
413
+ With Chrome browser, type the following URL in the address-bar:
414
+
415
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&noJavaScriptCompletion=true&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
416
+
417
+ EOS
357
418
  end
358
419
 
359
420
  def accept
@@ -362,18 +423,28 @@ module DEBUGGER__
362
423
 
363
424
  begin
364
425
  Socket.tcp_server_sockets @host, @port do |socks|
365
- @addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
426
+ @local_addr = socks.first.local_address # Change this part if `socks` are multiple.
366
427
  rdbg = File.expand_path('../../exe/rdbg', __dir__)
428
+ DEBUGGER__.warn "Debugger can attach via TCP/IP (#{@local_addr.inspect_sockaddr})"
429
+
430
+ if @port_save_file
431
+ File.write(@port_save_file, "#{socks[0].local_address.ip_port.to_s}\n")
432
+ DEBUGGER__.warn "Port is saved into #{@port_save_file}"
433
+ end
367
434
 
368
- DEBUGGER__.warn "Debugger can attach via TCP/IP (#{@addr})"
369
435
  DEBUGGER__.info <<~EOS
370
436
  With rdbg, use the following command line:
371
437
  #
372
- # #{rdbg} --attach #{@addr.split(':').join(' ')}
438
+ # #{rdbg} --attach #{@local_addr.ip_address} #{@local_addr.ip_port}
373
439
  #
374
440
  EOS
375
441
 
376
- chrome_setup if CONFIG[:open_frontend] == 'chrome'
442
+ case CONFIG[:open]
443
+ when 'chrome'
444
+ chrome_setup
445
+ when 'vscode'
446
+ vscode_setup @local_addr.inspect_sockaddr
447
+ end
377
448
 
378
449
  Socket.accept_loop(socks) do |sock, client|
379
450
  @client_addr = client
@@ -397,6 +468,10 @@ module DEBUGGER__
397
468
  end
398
469
  ensure
399
470
  @sock_for_fork = nil
471
+
472
+ if @port_save_file && File.exist?(@port_save_file)
473
+ File.unlink(@port_save_file)
474
+ end
400
475
  end
401
476
  end
402
477
 
@@ -409,11 +484,6 @@ module DEBUGGER__
409
484
  super()
410
485
  end
411
486
 
412
- def vscode_setup
413
- require_relative 'server_dap'
414
- UI_DAP.setup @sock_path
415
- end
416
-
417
487
  def accept
418
488
  super # for fork
419
489
 
@@ -426,7 +496,7 @@ module DEBUGGER__
426
496
  end
427
497
 
428
498
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
429
- vscode_setup if CONFIG[:open_frontend] == 'vscode'
499
+ vscode_setup @sock_path if CONFIG[:open] == 'vscode'
430
500
 
431
501
  begin
432
502
  Socket.unix_server_loop @sock_path do |sock, client|