debug 1.3.4 → 1.6.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/server.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'socket'
4
+ require 'etc'
4
5
  require_relative 'config'
5
6
  require_relative 'version'
6
7
 
@@ -19,8 +20,8 @@ module DEBUGGER__
19
20
  @session = nil
20
21
  end
21
22
 
22
- class Terminate < StandardError
23
- end
23
+ class Terminate < StandardError; end
24
+ class GreetingError < 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,10 +69,13 @@ 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
74
81
  rescue => e
@@ -76,11 +83,7 @@ module DEBUGGER__
76
83
  pp e.backtrace
77
84
  ensure
78
85
  DEBUGGER__.warn "Disconnected."
79
- @sock = nil
80
- @q_msg.close
81
- @q_msg = nil
82
- @q_ans.close
83
- @q_ans = nil
86
+ cleanup_reader if greeting_done
84
87
  end # accept
85
88
 
86
89
  rescue Terminate
@@ -88,21 +91,62 @@ module DEBUGGER__
88
91
  end
89
92
  end
90
93
 
94
+ def cleanup_reader
95
+ @sock.close if @sock
96
+ @sock = nil
97
+ @q_msg.close
98
+ @q_msg = nil
99
+ @q_ans.close
100
+ @q_ans = nil
101
+ end
102
+
103
+ def check_cookie c
104
+ cookie = CONFIG[:cookie]
105
+ if cookie && cookie != c
106
+ raise GreetingError, "Cookie mismatch (#{$2.inspect} was sent)"
107
+ end
108
+ end
109
+
110
+ def parse_option params
111
+ case params.strip
112
+ when /width:\s+(\d+)/
113
+ @width = $1.to_i
114
+ parse_option $~.post_match
115
+ when /cookie:\s+(\S+)/
116
+ check_cookie $1 if $1 != '-'
117
+ parse_option $~.post_match
118
+ when /nonstop: (true|false)/
119
+ @need_pause_at_first = false if $1 == 'true'
120
+ parse_option $~.post_match
121
+ when /(.+):(.+)/
122
+ raise GreetingError, "Unkown option: #{params}"
123
+ else
124
+ # OK
125
+ end
126
+ end
127
+
91
128
  def greeting
92
129
  case g = @sock.gets
93
- when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
94
- v, w, c = $1, $2, $3
130
+ when /^info cookie:\s+(.*)$/
131
+ check_cookie $1
132
+ @sock.puts "PID: #{Process.pid}, $0: #{$0}"
133
+ @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
134
+ @sock.puts "uname: #{Etc.uname.inspect}"
135
+ @sock.close
136
+ raise GreetingError, "HEAD request"
137
+
138
+ when /^version:\s+(\S+)\s+(.+)$/
139
+ v, params = $1, $2
140
+
95
141
  # TODO: protocol version
96
142
  if v != VERSION
97
- raise "Incompatible version (#{VERSION} client:#{$1})"
98
- end
99
-
100
- cookie = CONFIG[:cookie]
101
- if cookie && cookie != c
102
- raise "Cookie mismatch (#{$2.inspect} was sent)"
143
+ raise GreetingError, "Incompatible version (server:#{VERSION} and client:#{$1})"
103
144
  end
145
+ parse_option(params)
104
146
 
105
- @width = w.to_i
147
+ puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}"
148
+ puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
149
+ puts
106
150
 
107
151
  when /^Content-Length: (\d+)/
108
152
  require_relative 'server_dap'
@@ -110,18 +154,21 @@ module DEBUGGER__
110
154
  raise unless @sock.read(2) == "\r\n"
111
155
  self.extend(UI_DAP)
112
156
  @repl = false
157
+ @need_pause_at_first = false
113
158
  dap_setup @sock.read($1.to_i)
114
- when /^GET \/ HTTP\/1.1/
159
+
160
+ when /^GET \/.* HTTP\/1.1/
115
161
  require_relative 'server_cdp'
116
162
 
117
163
  self.extend(UI_CDP)
118
164
  @repl = false
165
+ @need_pause_at_first = false
119
166
  CONFIG.set_config no_color: true
120
167
 
121
- @web_sock = UI_CDP::WebSocket.new(@sock)
122
- @web_sock.handshake
168
+ @ws_server = UI_CDP::WebSocketServer.new(@sock)
169
+ @ws_server.handshake
123
170
  else
124
- raise "Greeting message error: #{g}"
171
+ raise GreetingError, "Unknown greeting message: #{g}"
125
172
  end
126
173
  end
127
174
 
@@ -141,6 +188,7 @@ module DEBUGGER__
141
188
  }
142
189
  end
143
190
 
191
+ return unless line
144
192
  next if line == :can_not_read
145
193
 
146
194
  case line
@@ -164,7 +212,7 @@ module DEBUGGER__
164
212
  raise "pid:#{Process.pid} but get #{line}"
165
213
  end
166
214
  else
167
- STDERR.puts "unsupported: #{line}"
215
+ STDERR.puts "unsupported: #{line.inspect}"
168
216
  exit!
169
217
  end
170
218
  end
@@ -180,7 +228,7 @@ module DEBUGGER__
180
228
 
181
229
  def sigurg_overridden? prev_handler
182
230
  case prev_handler
183
- when "SYSTEM_DEFAULT"
231
+ when "SYSTEM_DEFAULT", "DEFAULT"
184
232
  false
185
233
  when Proc
186
234
  if prev_handler.source_location[0] == __FILE__
@@ -193,10 +241,19 @@ module DEBUGGER__
193
241
  end
194
242
  end
195
243
 
244
+ begin
245
+ prev = trap(:SIGURG, nil)
246
+ trap(:SIGURG, prev)
247
+ TRAP_SIGNAL = :SIGURG
248
+ rescue ArgumentError
249
+ # maybe Windows?
250
+ TRAP_SIGNAL = :SIGINT
251
+ end
252
+
196
253
  def setup_interrupt
197
- prev_handler = trap(:SIGURG) do
254
+ prev_handler = trap(TRAP_SIGNAL) do
198
255
  # $stderr.puts "trapped SIGINT"
199
- ThreadClient.current.on_trap :SIGURG
256
+ ThreadClient.current.on_trap TRAP_SIGNAL
200
257
 
201
258
  case prev_handler
202
259
  when Proc
@@ -211,7 +268,7 @@ module DEBUGGER__
211
268
  end
212
269
  yield
213
270
  ensure
214
- trap(:SIGURG, prev_handler)
271
+ trap(TRAP_SIGNAL, prev_handler)
215
272
  end
216
273
 
217
274
  attr_reader :reader_thread
@@ -277,7 +334,9 @@ module DEBUGGER__
277
334
  end
278
335
 
279
336
  def readline prompt
280
- input = (sock do |s|
337
+ input = (sock(skip: CONFIG[:skip_bp]) do |s|
338
+ next unless s
339
+
281
340
  if @repl
282
341
  raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
283
342
  line = "input #{Process.pid}"
@@ -299,7 +358,7 @@ module DEBUGGER__
299
358
 
300
359
  def pause
301
360
  # $stderr.puts "DEBUG: pause request"
302
- Process.kill(:SIGURG, Process.pid)
361
+ Process.kill(TRAP_SIGNAL, Process.pid)
303
362
  end
304
363
 
305
364
  def quit n
@@ -312,47 +371,75 @@ module DEBUGGER__
312
371
  def after_fork_parent
313
372
  # do nothing
314
373
  end
374
+
375
+ def vscode_setup debug_port
376
+ require_relative 'server_dap'
377
+ UI_DAP.setup debug_port
378
+ end
315
379
  end
316
380
 
317
381
  class UI_TcpServer < UI_ServerBase
318
382
  def initialize host: nil, port: nil
319
- @addr = nil
320
- @host = host || CONFIG[:host] || '127.0.0.1'
321
- @port = port || begin
322
- port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
323
- if /\A\d+\z/ !~ port_str
324
- raise "Specify digits for port number"
325
- else
383
+ @local_addr = nil
384
+ @host = host || CONFIG[:host]
385
+ @port_save_file = nil
386
+ @port = begin
387
+ port_str = (port && port.to_s) || CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
388
+ case port_str
389
+ when /\A\d+\z/
326
390
  port_str.to_i
391
+ when /\A(\d+):(.+)\z/
392
+ @port_save_file = $2
393
+ $1.to_i
394
+ else
395
+ raise "Specify digits for port number"
327
396
  end
328
397
  end
329
398
 
330
399
  super()
331
400
  end
332
401
 
402
+ def chrome_setup
403
+ require_relative 'server_cdp'
404
+
405
+ unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr)
406
+ DEBUGGER__.warn <<~EOS
407
+ With Chrome browser, type the following URL in the address-bar:
408
+
409
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{SecureRandom.uuid}
410
+
411
+ EOS
412
+ end
413
+ end
414
+
333
415
  def accept
334
416
  retry_cnt = 0
335
417
  super # for fork
336
418
 
337
419
  begin
338
420
  Socket.tcp_server_sockets @host, @port do |socks|
339
- addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
421
+ @local_addr = socks.first.local_address # Change this part if `socks` are multiple.
340
422
  rdbg = File.expand_path('../../exe/rdbg', __dir__)
423
+ DEBUGGER__.warn "Debugger can attach via TCP/IP (#{@local_addr.inspect_sockaddr})"
424
+
425
+ if @port_save_file
426
+ File.write(@port_save_file, "#{socks[0].local_address.ip_port.to_s}\n")
427
+ DEBUGGER__.warn "Port is saved into #{@port_save_file}"
428
+ end
341
429
 
342
- DEBUGGER__.warn "Debugger can attach via TCP/IP (#{addr})"
343
430
  DEBUGGER__.info <<~EOS
344
431
  With rdbg, use the following command line:
345
432
  #
346
- # #{rdbg} --attach #{addr.split(':').join(' ')}
433
+ # #{rdbg} --attach #{@local_addr.ip_address} #{@local_addr.ip_port}
347
434
  #
348
435
  EOS
349
436
 
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
437
+ case CONFIG[:open_frontend]
438
+ when 'chrome'
439
+ chrome_setup
440
+ when 'vscode'
441
+ vscode_setup @local_addr.inspect_sockaddr
442
+ end
356
443
 
357
444
  Socket.accept_loop(socks) do |sock, client|
358
445
  @client_addr = client
@@ -376,6 +463,10 @@ module DEBUGGER__
376
463
  end
377
464
  ensure
378
465
  @sock_for_fork = nil
466
+
467
+ if @port_save_file && File.exist?(@port_save_file)
468
+ File.unlink(@port_save_file)
469
+ end
379
470
  end
380
471
  end
381
472
 
@@ -388,64 +479,6 @@ module DEBUGGER__
388
479
  super()
389
480
  end
390
481
 
391
- def vscode_setup
392
- require 'tmpdir'
393
- require 'json'
394
- require 'fileutils'
395
-
396
- dir = Dir.mktmpdir("ruby-debug-vscode-")
397
- at_exit{
398
- FileUtils.rm_rf dir
399
- }
400
- Dir.chdir(dir) do
401
- Dir.mkdir('.vscode')
402
- open('README.rb', 'w'){|f|
403
- f.puts <<~MSG
404
- # Wait for starting the attaching to the Ruby process
405
- # This file will be removed at the end of the debuggee process.
406
- #
407
- # Note that vscode-rdbg extension is needed. Please install if you don't have.
408
- MSG
409
- }
410
- open('.vscode/launch.json', 'w'){|f|
411
- f.puts JSON.pretty_generate({
412
- version: '0.2.0',
413
- configurations: [
414
- {
415
- type: "rdbg",
416
- name: "Attach with rdbg",
417
- request: "attach",
418
- rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
419
- debugPort: @sock_path,
420
- autoAttach: true,
421
- }
422
- ]
423
- })
424
- }
425
- end
426
-
427
- cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
428
- cmdline = cmds.join(' ')
429
- ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
430
-
431
- STDERR.puts "Launching: #{cmdline}"
432
- env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
433
-
434
- unless system(env, *cmds)
435
- DEBUGGER__.warn <<~MESSAGE
436
- Can not invoke the command.
437
- Use the command-line on your terminal (with modification if you need).
438
-
439
- #{cmdline}
440
-
441
- If your application is running on a SSH remote host, please try:
442
-
443
- #{ssh_cmdline}
444
-
445
- MESSAGE
446
- end
447
- end
448
-
449
482
  def accept
450
483
  super # for fork
451
484
 
@@ -458,7 +491,7 @@ module DEBUGGER__
458
491
  end
459
492
 
460
493
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
461
- vscode_setup if CONFIG[:open_frontend] == 'vscode'
494
+ vscode_setup @sock_path if CONFIG[:open_frontend] == 'vscode'
462
495
 
463
496
  begin
464
497
  Socket.unix_server_loop @sock_path do |sock, client|