debug 1.3.3 → 1.5.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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +163 -6
- data/Gemfile +1 -0
- data/README.md +42 -15
- data/Rakefile +5 -5
- data/debug.gemspec +6 -4
- data/ext/debug/debug.c +88 -1
- data/ext/debug/extconf.rb +11 -0
- data/lib/debug/breakpoint.rb +75 -36
- data/lib/debug/client.rb +65 -18
- data/lib/debug/color.rb +29 -19
- data/lib/debug/config.rb +6 -3
- data/lib/debug/console.rb +50 -15
- data/lib/debug/frame_info.rb +14 -20
- data/lib/debug/local.rb +1 -1
- data/lib/debug/prelude.rb +2 -2
- data/lib/debug/server.rb +100 -94
- data/lib/debug/server_cdp.rb +878 -160
- data/lib/debug/server_dap.rb +277 -81
- data/lib/debug/session.rb +327 -154
- data/lib/debug/source_repository.rb +90 -52
- data/lib/debug/thread_client.rb +149 -78
- data/lib/debug/tracer.rb +4 -10
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +16 -8
- metadata +4 -12
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -22
- data/bin/setup +0 -8
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,12 @@ module DEBUGGER__
|
|
47
48
|
|
48
49
|
accept do |server, already_connected: false|
|
49
50
|
DEBUGGER__.warn "Connected."
|
51
|
+
greeting_done = false
|
50
52
|
|
51
53
|
@accept_m.synchronize{
|
52
54
|
@sock = server
|
53
55
|
greeting
|
56
|
+
greeting_done = true
|
54
57
|
|
55
58
|
@accept_cv.signal
|
56
59
|
|
@@ -69,6 +72,9 @@ module DEBUGGER__
|
|
69
72
|
process
|
70
73
|
end
|
71
74
|
|
75
|
+
rescue GreetingError => e
|
76
|
+
DEBUGGER__.warn "GreetingError: #{e.message}"
|
77
|
+
next
|
72
78
|
rescue Terminate
|
73
79
|
raise # should catch at outer scope
|
74
80
|
rescue => e
|
@@ -76,11 +82,7 @@ module DEBUGGER__
|
|
76
82
|
pp e.backtrace
|
77
83
|
ensure
|
78
84
|
DEBUGGER__.warn "Disconnected."
|
79
|
-
|
80
|
-
@q_msg.close
|
81
|
-
@q_msg = nil
|
82
|
-
@q_ans.close
|
83
|
-
@q_ans = nil
|
85
|
+
cleanup_reader if greeting_done
|
84
86
|
end # accept
|
85
87
|
|
86
88
|
rescue Terminate
|
@@ -88,19 +90,40 @@ module DEBUGGER__
|
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
93
|
+
def cleanup_reader
|
94
|
+
@sock.close if @sock
|
95
|
+
@sock = nil
|
96
|
+
@q_msg.close
|
97
|
+
@q_msg = nil
|
98
|
+
@q_ans.close
|
99
|
+
@q_ans = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_cookie c
|
103
|
+
cookie = CONFIG[:cookie]
|
104
|
+
if cookie && cookie != c
|
105
|
+
raise GreetingError, "Cookie mismatch (#{$2.inspect} was sent)"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
91
109
|
def greeting
|
92
110
|
case g = @sock.gets
|
111
|
+
when /^info cookie:\s+(.*)$/
|
112
|
+
check_cookie $1
|
113
|
+
@sock.puts "PID: #{Process.pid}, $0: #{$0}"
|
114
|
+
@sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
|
115
|
+
@sock.puts "uname: #{Etc.uname.inspect}"
|
116
|
+
@sock.close
|
117
|
+
raise GreetingError, "HEAD request"
|
118
|
+
|
93
119
|
when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
|
94
120
|
v, w, c = $1, $2, $3
|
95
121
|
# TODO: protocol version
|
96
122
|
if v != VERSION
|
97
|
-
raise "Incompatible version (
|
123
|
+
raise GreetingError, "Incompatible version (server:#{VERSION} and client:#{$1})"
|
98
124
|
end
|
99
125
|
|
100
|
-
|
101
|
-
if cookie && cookie != c
|
102
|
-
raise "Cookie mismatch (#{$2.inspect} was sent)"
|
103
|
-
end
|
126
|
+
check_cookie c
|
104
127
|
|
105
128
|
@width = w.to_i
|
106
129
|
|
@@ -111,17 +134,17 @@ module DEBUGGER__
|
|
111
134
|
self.extend(UI_DAP)
|
112
135
|
@repl = false
|
113
136
|
dap_setup @sock.read($1.to_i)
|
114
|
-
when /^GET
|
137
|
+
when /^GET \/.* HTTP\/1.1/
|
115
138
|
require_relative 'server_cdp'
|
116
139
|
|
117
140
|
self.extend(UI_CDP)
|
118
141
|
@repl = false
|
119
142
|
CONFIG.set_config no_color: true
|
120
143
|
|
121
|
-
@
|
122
|
-
@
|
144
|
+
@ws_server = UI_CDP::WebSocketServer.new(@sock)
|
145
|
+
@ws_server.handshake
|
123
146
|
else
|
124
|
-
raise "
|
147
|
+
raise GreetingError, "Unknown greeting message: #{g}"
|
125
148
|
end
|
126
149
|
end
|
127
150
|
|
@@ -180,7 +203,7 @@ module DEBUGGER__
|
|
180
203
|
|
181
204
|
def sigurg_overridden? prev_handler
|
182
205
|
case prev_handler
|
183
|
-
when "SYSTEM_DEFAULT"
|
206
|
+
when "SYSTEM_DEFAULT", "DEFAULT"
|
184
207
|
false
|
185
208
|
when Proc
|
186
209
|
if prev_handler.source_location[0] == __FILE__
|
@@ -193,10 +216,19 @@ module DEBUGGER__
|
|
193
216
|
end
|
194
217
|
end
|
195
218
|
|
219
|
+
begin
|
220
|
+
prev = trap(:SIGURG, nil)
|
221
|
+
trap(:SIGURG, prev)
|
222
|
+
TRAP_SIGNAL = :SIGURG
|
223
|
+
rescue ArgumentError
|
224
|
+
# maybe Windows?
|
225
|
+
TRAP_SIGNAL = :SIGINT
|
226
|
+
end
|
227
|
+
|
196
228
|
def setup_interrupt
|
197
|
-
prev_handler = trap(
|
229
|
+
prev_handler = trap(TRAP_SIGNAL) do
|
198
230
|
# $stderr.puts "trapped SIGINT"
|
199
|
-
ThreadClient.current.on_trap
|
231
|
+
ThreadClient.current.on_trap TRAP_SIGNAL
|
200
232
|
|
201
233
|
case prev_handler
|
202
234
|
when Proc
|
@@ -211,7 +243,7 @@ module DEBUGGER__
|
|
211
243
|
end
|
212
244
|
yield
|
213
245
|
ensure
|
214
|
-
trap(
|
246
|
+
trap(TRAP_SIGNAL, prev_handler)
|
215
247
|
end
|
216
248
|
|
217
249
|
attr_reader :reader_thread
|
@@ -299,7 +331,7 @@ module DEBUGGER__
|
|
299
331
|
|
300
332
|
def pause
|
301
333
|
# $stderr.puts "DEBUG: pause request"
|
302
|
-
Process.kill(
|
334
|
+
Process.kill(TRAP_SIGNAL, Process.pid)
|
303
335
|
end
|
304
336
|
|
305
337
|
def quit n
|
@@ -312,47 +344,75 @@ module DEBUGGER__
|
|
312
344
|
def after_fork_parent
|
313
345
|
# do nothing
|
314
346
|
end
|
347
|
+
|
348
|
+
def vscode_setup debug_port
|
349
|
+
require_relative 'server_dap'
|
350
|
+
UI_DAP.setup debug_port
|
351
|
+
end
|
315
352
|
end
|
316
353
|
|
317
354
|
class UI_TcpServer < UI_ServerBase
|
318
355
|
def initialize host: nil, port: nil
|
319
356
|
@addr = nil
|
320
357
|
@host = host || CONFIG[:host] || '127.0.0.1'
|
321
|
-
@
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
358
|
+
@port_save_file = nil
|
359
|
+
@port = begin
|
360
|
+
port_str = (port && port.to_s) || CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
361
|
+
case port_str
|
362
|
+
when /\A\d+\z/
|
326
363
|
port_str.to_i
|
364
|
+
when /\A(\d+):(.+)\z/
|
365
|
+
@port_save_file = $2
|
366
|
+
$1.to_i
|
367
|
+
else
|
368
|
+
raise "Specify digits for port number"
|
327
369
|
end
|
328
370
|
end
|
329
371
|
|
330
372
|
super()
|
331
373
|
end
|
332
374
|
|
375
|
+
def chrome_setup
|
376
|
+
require_relative 'server_cdp'
|
377
|
+
|
378
|
+
unless @chrome_pid = UI_CDP.setup_chrome(@addr)
|
379
|
+
DEBUGGER__.warn <<~EOS
|
380
|
+
With Chrome browser, type the following URL in the address-bar:
|
381
|
+
|
382
|
+
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@addr}/#{SecureRandom.uuid}
|
383
|
+
|
384
|
+
EOS
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
333
388
|
def accept
|
334
389
|
retry_cnt = 0
|
335
390
|
super # for fork
|
336
391
|
|
337
392
|
begin
|
338
393
|
Socket.tcp_server_sockets @host, @port do |socks|
|
339
|
-
addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
|
394
|
+
@addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
|
340
395
|
rdbg = File.expand_path('../../exe/rdbg', __dir__)
|
396
|
+
DEBUGGER__.warn "Debugger can attach via TCP/IP (#{@addr})"
|
397
|
+
|
398
|
+
if @port_save_file
|
399
|
+
File.write(@port_save_file, "#{socks[0].local_address.ip_port.to_s}\n")
|
400
|
+
DEBUGGER__.warn "Port is saved into #{@port_save_file}"
|
401
|
+
end
|
341
402
|
|
342
|
-
DEBUGGER__.warn "Debugger can attach via TCP/IP (#{addr})"
|
343
403
|
DEBUGGER__.info <<~EOS
|
344
404
|
With rdbg, use the following command line:
|
345
405
|
#
|
346
|
-
# #{rdbg} --attach #{addr.split(':').join(' ')}
|
406
|
+
# #{rdbg} --attach #{@addr.split(':').join(' ')}
|
347
407
|
#
|
348
408
|
EOS
|
349
409
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
410
|
+
case CONFIG[:open_frontend]
|
411
|
+
when 'chrome'
|
412
|
+
chrome_setup
|
413
|
+
when 'vscode'
|
414
|
+
vscode_setup @addr
|
415
|
+
end
|
356
416
|
|
357
417
|
Socket.accept_loop(socks) do |sock, client|
|
358
418
|
@client_addr = client
|
@@ -376,6 +436,10 @@ module DEBUGGER__
|
|
376
436
|
end
|
377
437
|
ensure
|
378
438
|
@sock_for_fork = nil
|
439
|
+
|
440
|
+
if @port_save_file && File.exist?(@port_save_file)
|
441
|
+
File.unlink(@port_save_file)
|
442
|
+
end
|
379
443
|
end
|
380
444
|
end
|
381
445
|
|
@@ -388,64 +452,6 @@ module DEBUGGER__
|
|
388
452
|
super()
|
389
453
|
end
|
390
454
|
|
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
455
|
def accept
|
450
456
|
super # for fork
|
451
457
|
|
@@ -458,7 +464,7 @@ module DEBUGGER__
|
|
458
464
|
end
|
459
465
|
|
460
466
|
::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
|
461
|
-
vscode_setup if CONFIG[:open_frontend] == 'vscode'
|
467
|
+
vscode_setup @sock_path if CONFIG[:open_frontend] == 'vscode'
|
462
468
|
|
463
469
|
begin
|
464
470
|
Socket.unix_server_loop @sock_path do |sock, client|
|