debug 1.4.0 → 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
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|