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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +234 -9
- data/Gemfile +1 -0
- data/README.md +81 -31
- data/Rakefile +28 -10
- data/debug.gemspec +7 -5
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +80 -15
- data/ext/debug/extconf.rb +22 -0
- data/lib/debug/breakpoint.rb +141 -67
- data/lib/debug/client.rb +77 -20
- data/lib/debug/color.rb +29 -19
- data/lib/debug/config.rb +61 -27
- data/lib/debug/console.rb +59 -18
- data/lib/debug/frame_info.rb +41 -40
- data/lib/debug/local.rb +1 -1
- data/lib/debug/prelude.rb +2 -2
- data/lib/debug/server.rb +136 -103
- data/lib/debug/server_cdp.rb +880 -162
- data/lib/debug/server_dap.rb +445 -164
- data/lib/debug/session.rb +540 -269
- data/lib/debug/source_repository.rb +103 -52
- data/lib/debug/thread_client.rb +306 -138
- data/lib/debug/tracer.rb +8 -13
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +44 -16
- metadata +6 -15
- 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/bp.vim +0 -68
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
|
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
|
-
|
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 /^
|
94
|
-
|
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 (
|
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
|
-
|
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
|
-
|
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
|
-
@
|
122
|
-
@
|
168
|
+
@ws_server = UI_CDP::WebSocketServer.new(@sock)
|
169
|
+
@ws_server.handshake
|
123
170
|
else
|
124
|
-
raise "
|
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(
|
254
|
+
prev_handler = trap(TRAP_SIGNAL) do
|
198
255
|
# $stderr.puts "trapped SIGINT"
|
199
|
-
ThreadClient.current.on_trap
|
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(
|
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(
|
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
|
-
@
|
320
|
-
@host = host || CONFIG[:host]
|
321
|
-
@
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
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
|
-
|
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 #{
|
433
|
+
# #{rdbg} --attach #{@local_addr.ip_address} #{@local_addr.ip_port}
|
347
434
|
#
|
348
435
|
EOS
|
349
436
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
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|
|