debug 1.4.0 → 1.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +210 -6
- data/Gemfile +2 -0
- data/LICENSE.txt +0 -0
- data/README.md +161 -85
- data/Rakefile +33 -10
- data/TODO.md +8 -8
- data/debug.gemspec +9 -7
- data/exe/rdbg +23 -4
- data/ext/debug/debug.c +111 -21
- data/ext/debug/extconf.rb +23 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +102 -74
- data/lib/debug/client.rb +46 -12
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +129 -36
- data/lib/debug/console.rb +46 -40
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +40 -25
- data/lib/debug/irb_integration.rb +37 -0
- data/lib/debug/local.rb +17 -11
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +3 -2
- data/lib/debug/server.rb +126 -56
- data/lib/debug/server_cdp.rb +673 -248
- data/lib/debug/server_dap.rb +497 -261
- data/lib/debug/session.rb +899 -441
- data/lib/debug/source_repository.rb +122 -49
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +460 -155
- data/lib/debug/tracer.rb +10 -16
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +106 -56
- metadata +14 -24
- 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/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/breakpoint.rb
CHANGED
@@ -6,11 +6,15 @@ module DEBUGGER__
|
|
6
6
|
class Breakpoint
|
7
7
|
include SkipPathHelper
|
8
8
|
|
9
|
-
attr_reader :key
|
9
|
+
attr_reader :key, :skip_src
|
10
10
|
|
11
|
-
def initialize do_enable
|
11
|
+
def initialize cond, command, path, do_enable: true
|
12
12
|
@deleted = false
|
13
13
|
|
14
|
+
@cond = cond
|
15
|
+
@command = command
|
16
|
+
@path = path
|
17
|
+
|
14
18
|
setup
|
15
19
|
enable if do_enable
|
16
20
|
end
|
@@ -82,8 +86,11 @@ module DEBUGGER__
|
|
82
86
|
end
|
83
87
|
|
84
88
|
def skip_path?(path)
|
85
|
-
|
89
|
+
case @path
|
90
|
+
when Regexp
|
86
91
|
!path.match?(@path)
|
92
|
+
when String
|
93
|
+
!path.include?(@path)
|
87
94
|
else
|
88
95
|
super
|
89
96
|
end
|
@@ -108,7 +115,7 @@ module DEBUGGER__
|
|
108
115
|
@oneshot = oneshot
|
109
116
|
@key = [:iseq, @iseq.path, @iseq.first_lineno].freeze
|
110
117
|
|
111
|
-
super()
|
118
|
+
super(nil, nil, nil)
|
112
119
|
end
|
113
120
|
|
114
121
|
def setup
|
@@ -124,25 +131,31 @@ module DEBUGGER__
|
|
124
131
|
end
|
125
132
|
|
126
133
|
class LineBreakpoint < Breakpoint
|
127
|
-
attr_reader :path, :line, :iseq
|
134
|
+
attr_reader :path, :line, :iseq, :cond, :oneshot, :hook_call, :command
|
128
135
|
|
129
|
-
def
|
130
|
-
|
136
|
+
def self.copy bp, root_iseq
|
137
|
+
nbp = LineBreakpoint.new bp.path, bp.line,
|
138
|
+
cond: bp.cond, oneshot: bp.oneshot, hook_call: bp.hook_call,
|
139
|
+
command: bp.command, skip_activate: true
|
140
|
+
nbp.try_activate root_iseq
|
141
|
+
nbp
|
142
|
+
end
|
143
|
+
|
144
|
+
def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil, skip_activate: false, skip_src: false
|
131
145
|
@line = line
|
132
|
-
@cond = cond
|
133
146
|
@oneshot = oneshot
|
134
147
|
@hook_call = hook_call
|
135
|
-
@
|
148
|
+
@skip_src = skip_src
|
136
149
|
@pending = false
|
137
150
|
|
138
151
|
@iseq = nil
|
139
152
|
@type = nil
|
140
153
|
|
141
|
-
@key = [
|
154
|
+
@key = [path, @line].freeze
|
142
155
|
|
143
|
-
super()
|
156
|
+
super(cond, command, path)
|
144
157
|
|
145
|
-
try_activate
|
158
|
+
try_activate unless skip_activate
|
146
159
|
@pending = !@iseq
|
147
160
|
end
|
148
161
|
|
@@ -184,8 +197,10 @@ module DEBUGGER__
|
|
184
197
|
enable
|
185
198
|
|
186
199
|
if @pending && !@oneshot
|
187
|
-
DEBUGGER__.
|
200
|
+
DEBUGGER__.info "#{self} is activated."
|
188
201
|
end
|
202
|
+
|
203
|
+
@pending = false
|
189
204
|
end
|
190
205
|
|
191
206
|
def activate_exact iseq, events, line
|
@@ -202,7 +217,7 @@ module DEBUGGER__
|
|
202
217
|
when events.include?(:RUBY_EVENT_END)
|
203
218
|
activate(iseq, :end, line)
|
204
219
|
else
|
205
|
-
# not
|
220
|
+
# not activated
|
206
221
|
end
|
207
222
|
end
|
208
223
|
|
@@ -212,42 +227,53 @@ module DEBUGGER__
|
|
212
227
|
|
213
228
|
NearestISeq = Struct.new(:iseq, :line, :events)
|
214
229
|
|
215
|
-
def
|
230
|
+
def iterate_iseq root_iseq
|
231
|
+
if root_iseq
|
232
|
+
is = [root_iseq]
|
233
|
+
while iseq = is.pop
|
234
|
+
yield iseq
|
235
|
+
iseq.each_child do |child_iseq|
|
236
|
+
is << child_iseq
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
ObjectSpace.each_iseq do |iseq|
|
241
|
+
if DEBUGGER__.compare_path((iseq.absolute_path || iseq.path), self.path) &&
|
242
|
+
iseq.first_lineno <= self.line &&
|
243
|
+
iseq.type != :ensure # ensure iseq is copied (duplicated)
|
244
|
+
yield iseq
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def try_activate root_iseq = nil
|
216
251
|
nearest = nil # NearestISeq
|
252
|
+
iterate_iseq root_iseq do |iseq|
|
253
|
+
iseq.traceable_lines_norec(line_events = {})
|
254
|
+
lines = line_events.keys.sort
|
255
|
+
|
256
|
+
if !lines.empty? && lines.last >= line
|
257
|
+
nline = lines.bsearch{|l| line <= l}
|
258
|
+
events = line_events[nline]
|
259
|
+
|
260
|
+
next if events == [:RUBY_EVENT_B_CALL]
|
261
|
+
|
262
|
+
if @hook_call &&
|
263
|
+
events.include?(:RUBY_EVENT_CALL) &&
|
264
|
+
self.line == iseq.first_lineno
|
265
|
+
nline = iseq.first_lineno
|
266
|
+
end
|
217
267
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
lines = line_events.keys.sort
|
225
|
-
|
226
|
-
if !lines.empty? && lines.last >= line
|
227
|
-
nline = lines.bsearch{|l| line <= l}
|
228
|
-
events = line_events[nline]
|
229
|
-
|
230
|
-
next if events == [:RUBY_EVENT_B_CALL]
|
231
|
-
|
232
|
-
if @hook_call &&
|
233
|
-
events.include?(:RUBY_EVENT_CALL) &&
|
234
|
-
self.line == iseq.first_lineno
|
235
|
-
nline = iseq.first_lineno
|
236
|
-
end
|
237
|
-
|
238
|
-
if !nearest || ((line - nline).abs < (line - nearest.line).abs)
|
239
|
-
nearest = NearestISeq.new(iseq, nline, events)
|
240
|
-
else
|
241
|
-
if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
|
242
|
-
if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
|
243
|
-
(events.include?(:RUBY_EVENT_CALL))
|
244
|
-
nearest = NearestISeq.new(iseq, nline, events)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
268
|
+
if !nearest || ((line - nline).abs < (line - nearest.line).abs)
|
269
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
270
|
+
elsif @hook_call &&
|
271
|
+
nearest.line == iseq.first_line &&
|
272
|
+
events.include?(:RUBY_EVENT_CALL)
|
273
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
248
274
|
end
|
249
275
|
end
|
250
|
-
|
276
|
+
end
|
251
277
|
|
252
278
|
if nearest
|
253
279
|
activate_exact nearest.iseq, nearest.events, nearest.line
|
@@ -267,6 +293,10 @@ module DEBUGGER__
|
|
267
293
|
def inspect
|
268
294
|
"<#{self.class.name} #{self.to_s}>"
|
269
295
|
end
|
296
|
+
|
297
|
+
def path_is? path
|
298
|
+
DEBUGGER__.compare_path(@path, path)
|
299
|
+
end
|
270
300
|
end
|
271
301
|
|
272
302
|
class CatchBreakpoint < Breakpoint
|
@@ -277,11 +307,7 @@ module DEBUGGER__
|
|
277
307
|
@key = [:catch, @pat].freeze
|
278
308
|
@last_exc = nil
|
279
309
|
|
280
|
-
|
281
|
-
@command = command
|
282
|
-
@path = path
|
283
|
-
|
284
|
-
super()
|
310
|
+
super(cond, command, path)
|
285
311
|
end
|
286
312
|
|
287
313
|
def setup
|
@@ -314,29 +340,41 @@ module DEBUGGER__
|
|
314
340
|
end
|
315
341
|
|
316
342
|
class CheckBreakpoint < Breakpoint
|
317
|
-
def initialize
|
318
|
-
@
|
319
|
-
@key = [:check, @expr].freeze
|
320
|
-
@path = path
|
343
|
+
def initialize cond:, command: nil, path: nil
|
344
|
+
@key = [:check, cond].freeze
|
321
345
|
|
322
|
-
super()
|
346
|
+
super(cond, command, path)
|
323
347
|
end
|
324
348
|
|
325
349
|
def setup
|
326
350
|
@tp = TracePoint.new(:line){|tp|
|
327
|
-
next if
|
328
|
-
next if tp.path.start_with? '<internal:'
|
351
|
+
next if SESSION.in_subsession? # TODO: Ractor support
|
329
352
|
next if ThreadClient.current.management?
|
330
353
|
next if skip_path?(tp.path)
|
331
354
|
|
332
|
-
if safe_eval
|
355
|
+
if need_suspend? safe_eval(tp.binding, @cond)
|
333
356
|
suspend
|
334
357
|
end
|
335
358
|
}
|
336
359
|
end
|
337
360
|
|
361
|
+
private def need_suspend? cond_result
|
362
|
+
map = ThreadClient.current.check_bp_fulfillment_map
|
363
|
+
if cond_result
|
364
|
+
if map[self]
|
365
|
+
false
|
366
|
+
else
|
367
|
+
map[self] = true
|
368
|
+
end
|
369
|
+
else
|
370
|
+
map[self] = false
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
338
374
|
def to_s
|
339
|
-
"#{generate_label("Check")}
|
375
|
+
s = "#{generate_label("Check")}"
|
376
|
+
s += super
|
377
|
+
s
|
340
378
|
end
|
341
379
|
end
|
342
380
|
|
@@ -348,10 +386,7 @@ module DEBUGGER__
|
|
348
386
|
|
349
387
|
@current = current
|
350
388
|
|
351
|
-
|
352
|
-
@command = command
|
353
|
-
@path = path
|
354
|
-
super()
|
389
|
+
super(cond, command, path)
|
355
390
|
end
|
356
391
|
|
357
392
|
def watch_eval(tp)
|
@@ -374,9 +409,6 @@ module DEBUGGER__
|
|
374
409
|
|
375
410
|
def setup
|
376
411
|
@tp = TracePoint.new(:line, :return, :b_return){|tp|
|
377
|
-
next if tp.path.start_with? __dir__
|
378
|
-
next if tp.path.start_with? '<internal:'
|
379
|
-
|
380
412
|
watch_eval(tp)
|
381
413
|
}
|
382
414
|
end
|
@@ -393,7 +425,7 @@ module DEBUGGER__
|
|
393
425
|
end
|
394
426
|
|
395
427
|
class MethodBreakpoint < Breakpoint
|
396
|
-
attr_reader :sig_method_name, :method
|
428
|
+
attr_reader :sig_method_name, :method, :klass
|
397
429
|
|
398
430
|
def initialize b, klass_name, op, method_name, cond: nil, command: nil, path: nil
|
399
431
|
@sig_klass_name = klass_name
|
@@ -404,13 +436,10 @@ module DEBUGGER__
|
|
404
436
|
|
405
437
|
@klass = nil
|
406
438
|
@method = nil
|
407
|
-
@cond = cond
|
408
439
|
@cond_class = nil
|
409
|
-
@command = command
|
410
|
-
@path = path
|
411
440
|
@key = "#{klass_name}#{op}#{method_name}".freeze
|
412
441
|
|
413
|
-
super(false)
|
442
|
+
super(cond, command, path, do_enable: false)
|
414
443
|
end
|
415
444
|
|
416
445
|
def setup
|
@@ -419,7 +448,6 @@ module DEBUGGER__
|
|
419
448
|
next if @cond_class && !tp.self.kind_of?(@cond_class)
|
420
449
|
|
421
450
|
caller_location = caller_locations(2, 1).first.to_s
|
422
|
-
next if caller_location.start_with?(__dir__)
|
423
451
|
next if skip_path?(caller_location)
|
424
452
|
|
425
453
|
suspend
|
@@ -476,7 +504,7 @@ module DEBUGGER__
|
|
476
504
|
retried = false
|
477
505
|
|
478
506
|
@tp.enable(target: @method)
|
479
|
-
DEBUGGER__.
|
507
|
+
DEBUGGER__.info "#{self} is activated." if added
|
480
508
|
|
481
509
|
if @sig_op == '#'
|
482
510
|
@cond_class = @klass if @method.owner != @klass
|
data/lib/debug/client.rb
CHANGED
@@ -18,9 +18,16 @@ module DEBUGGER__
|
|
18
18
|
case name
|
19
19
|
when 'gen-sockpath'
|
20
20
|
puts DEBUGGER__.create_unix_domain_socket_name
|
21
|
+
when 'gen-portpath'
|
22
|
+
port_path = File.join(DEBUGGER__.unix_domain_socket_dir, 'tcp_port')
|
23
|
+
File.unlink port_path if File.exist?(port_path)
|
24
|
+
puts port_path
|
21
25
|
when 'list-socks'
|
22
26
|
cleanup_unix_domain_sockets
|
23
27
|
puts list_connections
|
28
|
+
when 'list-socks-verbose'
|
29
|
+
cleanup_unix_domain_sockets
|
30
|
+
puts list_connections verbose: true
|
24
31
|
when 'setup-autoload'
|
25
32
|
setup_autoload
|
26
33
|
else
|
@@ -38,7 +45,7 @@ module DEBUGGER__
|
|
38
45
|
when /csh/
|
39
46
|
:csh
|
40
47
|
when /zsh/
|
41
|
-
:
|
48
|
+
:zsh
|
42
49
|
when /dash/
|
43
50
|
:dash
|
44
51
|
else
|
@@ -76,7 +83,7 @@ module DEBUGGER__
|
|
76
83
|
|
77
84
|
def cleanup_unix_domain_sockets
|
78
85
|
Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*') do |file|
|
79
|
-
if
|
86
|
+
if File.socket?(file) && (/-(\d+)-\d+$/ =~ file || /-(\d+)$/ =~ file)
|
80
87
|
begin
|
81
88
|
Process.kill(0, $1.to_i)
|
82
89
|
rescue Errno::EPERM
|
@@ -87,8 +94,24 @@ module DEBUGGER__
|
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
90
|
-
def list_connections
|
91
|
-
Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
|
97
|
+
def list_connections verbose: false
|
98
|
+
socks = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*').find_all do |path|
|
99
|
+
File.socket?(path)
|
100
|
+
end
|
101
|
+
|
102
|
+
if verbose
|
103
|
+
socks = socks.map{|sock_path|
|
104
|
+
Socket.unix(sock_path){|sock|
|
105
|
+
sock.puts "info cookie: #{CONFIG[:cookie] || '-'}"
|
106
|
+
pid = sock.gets.chomp
|
107
|
+
_dbg = sock.gets.chomp
|
108
|
+
_unm = sock.gets.chomp
|
109
|
+
[sock_path, pid]
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
socks
|
92
115
|
end
|
93
116
|
end
|
94
117
|
|
@@ -115,7 +138,10 @@ module DEBUGGER__
|
|
115
138
|
@width = IO.console_size[1]
|
116
139
|
@width = 80 if @width == 0
|
117
140
|
|
118
|
-
send "version: #{VERSION}
|
141
|
+
send "version: #{VERSION} " +
|
142
|
+
"width: #{@width} " +
|
143
|
+
"cookie: #{CONFIG[:cookie] || '-'} " +
|
144
|
+
"nonstop: #{CONFIG[:nonstop] ? 'true' : 'false'}"
|
119
145
|
end
|
120
146
|
|
121
147
|
def deactivate
|
@@ -148,9 +174,10 @@ module DEBUGGER__
|
|
148
174
|
when 1
|
149
175
|
@s = Socket.unix(files.first)
|
150
176
|
else
|
177
|
+
files = Client.list_connections verbose: true
|
151
178
|
$stderr.puts "Please select a debug session:"
|
152
|
-
files.each{|f|
|
153
|
-
$stderr.puts " #{File.basename(f)}"
|
179
|
+
files.each{|(f, desc)|
|
180
|
+
$stderr.puts " #{File.basename(f)} (#{desc})"
|
154
181
|
}
|
155
182
|
exit
|
156
183
|
end
|
@@ -167,6 +194,8 @@ module DEBUGGER__
|
|
167
194
|
end
|
168
195
|
|
169
196
|
def connect
|
197
|
+
pre_commands = (CONFIG[:commands] || '').split(';;')
|
198
|
+
|
170
199
|
trap(:SIGINT){
|
171
200
|
send "pause"
|
172
201
|
}
|
@@ -175,7 +204,7 @@ module DEBUGGER__
|
|
175
204
|
trap(:SIGWINCH){
|
176
205
|
@width = IO.console_size[1]
|
177
206
|
}
|
178
|
-
rescue ArgumentError
|
207
|
+
rescue ArgumentError
|
179
208
|
@width = 80
|
180
209
|
end
|
181
210
|
|
@@ -193,7 +222,12 @@ module DEBUGGER__
|
|
193
222
|
prev_trap = trap(:SIGINT, 'DEFAULT')
|
194
223
|
|
195
224
|
begin
|
196
|
-
|
225
|
+
if pre_commands.empty?
|
226
|
+
line = readline
|
227
|
+
else
|
228
|
+
line = pre_commands.shift
|
229
|
+
puts "(rdbg:remote:command) #{line}"
|
230
|
+
end
|
197
231
|
rescue Interrupt
|
198
232
|
retry
|
199
233
|
ensure
|
@@ -206,7 +240,7 @@ module DEBUGGER__
|
|
206
240
|
when /^ask (\d+) (.*)/
|
207
241
|
pid = $1
|
208
242
|
print $2
|
209
|
-
send "answer #{pid} #{gets || ''}"
|
243
|
+
send "answer #{pid} #{$stdin.gets || ''}"
|
210
244
|
|
211
245
|
when /^quit/
|
212
246
|
raise 'quit'
|
@@ -215,8 +249,8 @@ module DEBUGGER__
|
|
215
249
|
puts "(unknown) #{line.inspect}"
|
216
250
|
end
|
217
251
|
end
|
218
|
-
rescue
|
219
|
-
STDERR.puts "disconnected (#{
|
252
|
+
rescue => e
|
253
|
+
STDERR.puts "disconnected (#{e})"
|
220
254
|
exit
|
221
255
|
ensure
|
222
256
|
deactivate
|
data/lib/debug/color.rb
CHANGED
File without changes
|