debug 1.4.0 → 1.9.2
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 +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
|