ed-precompiled_debug 1.11.0-x86_64-linux
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 +7 -0
- data/CONTRIBUTING.md +573 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +996 -0
- data/Rakefile +57 -0
- data/TODO.md +23 -0
- data/debug.gemspec +33 -0
- data/exe/rdbg +53 -0
- data/ext/debug/debug.c +228 -0
- data/ext/debug/extconf.rb +27 -0
- data/ext/debug/iseq_collector.c +93 -0
- data/lib/debug/3.0/debug.so +0 -0
- data/lib/debug/3.1/debug.so +0 -0
- data/lib/debug/3.2/debug.so +0 -0
- data/lib/debug/3.3/debug.so +0 -0
- data/lib/debug/3.4/debug.so +0 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +556 -0
- data/lib/debug/client.rb +263 -0
- data/lib/debug/color.rb +123 -0
- data/lib/debug/config.rb +592 -0
- data/lib/debug/console.rb +224 -0
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/debug.so +0 -0
- data/lib/debug/frame_info.rb +191 -0
- data/lib/debug/irb_integration.rb +37 -0
- data/lib/debug/local.rb +115 -0
- data/lib/debug/open.rb +13 -0
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/prelude.rb +50 -0
- data/lib/debug/server.rb +534 -0
- data/lib/debug/server_cdp.rb +1348 -0
- data/lib/debug/server_dap.rb +1108 -0
- data/lib/debug/session.rb +2667 -0
- data/lib/debug/source_repository.rb +150 -0
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +1457 -0
- data/lib/debug/tracer.rb +241 -0
- data/lib/debug/version.rb +5 -0
- data/lib/debug.rb +9 -0
- data/misc/README.md.erb +660 -0
- metadata +118 -0
@@ -0,0 +1,556 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'color'
|
4
|
+
|
5
|
+
module DEBUGGER__
|
6
|
+
class Breakpoint
|
7
|
+
include SkipPathHelper
|
8
|
+
|
9
|
+
attr_reader :key, :skip_src
|
10
|
+
|
11
|
+
def initialize cond, command, path, do_enable: true
|
12
|
+
@deleted = false
|
13
|
+
|
14
|
+
@cond = cond
|
15
|
+
@command = command
|
16
|
+
@path = path
|
17
|
+
|
18
|
+
setup
|
19
|
+
enable if do_enable
|
20
|
+
end
|
21
|
+
|
22
|
+
def safe_eval b, expr
|
23
|
+
b.eval(expr)
|
24
|
+
rescue Exception => e
|
25
|
+
puts "[EVAL ERROR]"
|
26
|
+
puts " expr: #{expr}"
|
27
|
+
puts " err: #{e} (#{e.class})"
|
28
|
+
puts "Error caused by #{self}."
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def oneshot?
|
33
|
+
defined?(@oneshot) && @oneshot
|
34
|
+
end
|
35
|
+
|
36
|
+
def setup
|
37
|
+
raise "not implemented..."
|
38
|
+
end
|
39
|
+
|
40
|
+
def enable
|
41
|
+
@tp.enable
|
42
|
+
end
|
43
|
+
|
44
|
+
def disable
|
45
|
+
@tp&.disable
|
46
|
+
end
|
47
|
+
|
48
|
+
def enabled?
|
49
|
+
@tp.enabled?
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete
|
53
|
+
disable
|
54
|
+
@deleted = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def deleted?
|
58
|
+
@deleted
|
59
|
+
end
|
60
|
+
|
61
|
+
def suspend
|
62
|
+
if @command
|
63
|
+
provider, pre_cmds, do_cmds = @command
|
64
|
+
nonstop = true if do_cmds
|
65
|
+
cmds = [*pre_cmds&.split(';;'), *do_cmds&.split(';;')]
|
66
|
+
SESSION.add_preset_commands provider, cmds, kick: false, continue: nonstop
|
67
|
+
end
|
68
|
+
|
69
|
+
ThreadClient.current.on_breakpoint @tp, self
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
s = ''.dup
|
74
|
+
s << " if: #{@cond}" if defined?(@cond) && @cond
|
75
|
+
s << " pre: #{@command[1]}" if defined?(@command) && @command && @command[1]
|
76
|
+
s << " do: #{@command[2]}" if defined?(@command) && @command && @command[2]
|
77
|
+
s
|
78
|
+
end
|
79
|
+
|
80
|
+
def description
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def duplicable?
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
def skip_path?(path)
|
89
|
+
case @path
|
90
|
+
when Regexp
|
91
|
+
!path.match?(@path)
|
92
|
+
when String
|
93
|
+
!path.include?(@path)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
include Color
|
100
|
+
|
101
|
+
def generate_label(name)
|
102
|
+
colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if RUBY_VERSION.to_f <= 2.7
|
107
|
+
# workaround for https://bugs.ruby-lang.org/issues/17302
|
108
|
+
TracePoint.new(:line){}.enable{}
|
109
|
+
end
|
110
|
+
|
111
|
+
class ISeqBreakpoint < Breakpoint
|
112
|
+
def initialize iseq, events, oneshot: false
|
113
|
+
@events = events
|
114
|
+
@iseq = iseq
|
115
|
+
@oneshot = oneshot
|
116
|
+
@key = [:iseq, @iseq.path, @iseq.first_lineno].freeze
|
117
|
+
|
118
|
+
super(nil, nil, nil)
|
119
|
+
end
|
120
|
+
|
121
|
+
def setup
|
122
|
+
@tp = TracePoint.new(*@events) do |tp|
|
123
|
+
delete if @oneshot
|
124
|
+
suspend
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def enable
|
129
|
+
@tp.enable(target: @iseq)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class LineBreakpoint < Breakpoint
|
134
|
+
attr_reader :path, :line, :iseq, :cond, :oneshot, :hook_call, :command
|
135
|
+
|
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
|
145
|
+
@line = line
|
146
|
+
@oneshot = oneshot
|
147
|
+
@hook_call = hook_call
|
148
|
+
@skip_src = skip_src
|
149
|
+
@pending = false
|
150
|
+
|
151
|
+
@iseq = nil
|
152
|
+
@type = nil
|
153
|
+
|
154
|
+
@key = [path, @line].freeze
|
155
|
+
|
156
|
+
super(cond, command, path)
|
157
|
+
|
158
|
+
try_activate unless skip_activate
|
159
|
+
@pending = !@iseq
|
160
|
+
end
|
161
|
+
|
162
|
+
def setup
|
163
|
+
return unless @type
|
164
|
+
|
165
|
+
@tp = TracePoint.new(@type) do |tp|
|
166
|
+
if @cond
|
167
|
+
next unless safe_eval tp.binding, @cond
|
168
|
+
end
|
169
|
+
delete if @oneshot
|
170
|
+
suspend
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def enable
|
175
|
+
return unless @iseq
|
176
|
+
|
177
|
+
if @type == :line
|
178
|
+
@tp.enable(target: @iseq, target_line: @line)
|
179
|
+
else
|
180
|
+
@tp.enable(target: @iseq)
|
181
|
+
end
|
182
|
+
|
183
|
+
rescue ArgumentError
|
184
|
+
puts @iseq.disasm # for debug
|
185
|
+
raise
|
186
|
+
end
|
187
|
+
|
188
|
+
def activate iseq, event, line
|
189
|
+
@iseq = iseq
|
190
|
+
@type = event
|
191
|
+
@line = line
|
192
|
+
@path = iseq.absolute_path
|
193
|
+
|
194
|
+
@key = [@path, @line].freeze
|
195
|
+
SESSION.rehash_bps
|
196
|
+
setup
|
197
|
+
enable
|
198
|
+
|
199
|
+
if @pending && !@oneshot
|
200
|
+
DEBUGGER__.info "#{self} is activated."
|
201
|
+
end
|
202
|
+
|
203
|
+
@pending = false
|
204
|
+
end
|
205
|
+
|
206
|
+
def activate_exact iseq, events, line
|
207
|
+
case
|
208
|
+
when events.include?(:RUBY_EVENT_CALL)
|
209
|
+
# "def foo" line set bp on the beginning of method foo
|
210
|
+
activate(iseq, :call, line)
|
211
|
+
when events.include?(:RUBY_EVENT_LINE)
|
212
|
+
activate(iseq, :line, line)
|
213
|
+
when events.include?(:RUBY_EVENT_RETURN)
|
214
|
+
activate(iseq, :return, line)
|
215
|
+
when events.include?(:RUBY_EVENT_B_RETURN)
|
216
|
+
activate(iseq, :b_return, line)
|
217
|
+
when events.include?(:RUBY_EVENT_END)
|
218
|
+
activate(iseq, :end, line)
|
219
|
+
else
|
220
|
+
# not activated
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def duplicable?
|
225
|
+
@oneshot
|
226
|
+
end
|
227
|
+
|
228
|
+
NearestISeq = Struct.new(:iseq, :line, :events)
|
229
|
+
|
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
|
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
|
267
|
+
|
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)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
if nearest
|
279
|
+
activate_exact nearest.iseq, nearest.events, nearest.line
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def to_s
|
284
|
+
oneshot = @oneshot ? " (oneshot)" : ""
|
285
|
+
|
286
|
+
if @iseq
|
287
|
+
"#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
|
288
|
+
else
|
289
|
+
"#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def inspect
|
294
|
+
"<#{self.class.name} #{self.to_s}>"
|
295
|
+
end
|
296
|
+
|
297
|
+
def path_is? path
|
298
|
+
DEBUGGER__.compare_path(@path, path)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class CatchBreakpoint < Breakpoint
|
303
|
+
attr_reader :last_exc
|
304
|
+
|
305
|
+
def initialize pat, cond: nil, command: nil, path: nil
|
306
|
+
@pat = pat.freeze
|
307
|
+
@key = [:catch, @pat].freeze
|
308
|
+
@last_exc = nil
|
309
|
+
|
310
|
+
super(cond, command, path)
|
311
|
+
end
|
312
|
+
|
313
|
+
def setup
|
314
|
+
@tp = TracePoint.new(:raise){|tp|
|
315
|
+
exc = tp.raised_exception
|
316
|
+
next if SystemExit === exc
|
317
|
+
next if skip_path?(tp.path)
|
318
|
+
|
319
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
320
|
+
should_suspend = false
|
321
|
+
|
322
|
+
exc.class.ancestors.each{|cls|
|
323
|
+
if @pat === cls.name
|
324
|
+
should_suspend = true
|
325
|
+
@last_exc = exc
|
326
|
+
break
|
327
|
+
end
|
328
|
+
}
|
329
|
+
suspend if should_suspend
|
330
|
+
}
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_s
|
334
|
+
"#{generate_label("Catch")} #{@pat.inspect}"
|
335
|
+
end
|
336
|
+
|
337
|
+
def description
|
338
|
+
"#{@last_exc.inspect} is raised."
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class CheckBreakpoint < Breakpoint
|
343
|
+
def initialize cond:, command: nil, path: nil
|
344
|
+
@key = [:check, cond].freeze
|
345
|
+
|
346
|
+
super(cond, command, path)
|
347
|
+
end
|
348
|
+
|
349
|
+
def setup
|
350
|
+
@tp = TracePoint.new(:line){|tp|
|
351
|
+
next if SESSION.in_subsession? # TODO: Ractor support
|
352
|
+
next if ThreadClient.current.management?
|
353
|
+
next if skip_path?(tp.path)
|
354
|
+
|
355
|
+
if need_suspend? safe_eval(tp.binding, @cond)
|
356
|
+
suspend
|
357
|
+
end
|
358
|
+
}
|
359
|
+
end
|
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
|
+
|
374
|
+
def to_s
|
375
|
+
s = "#{generate_label("Check")}"
|
376
|
+
s += super
|
377
|
+
s
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
class WatchIVarBreakpoint < Breakpoint
|
382
|
+
def initialize ivar, object, current, cond: nil, command: nil, path: nil
|
383
|
+
@ivar = ivar.to_sym
|
384
|
+
@object = object
|
385
|
+
@key = [:watch, object.object_id, @ivar].freeze
|
386
|
+
|
387
|
+
@current = current
|
388
|
+
|
389
|
+
super(cond, command, path)
|
390
|
+
end
|
391
|
+
|
392
|
+
def watch_eval(tp)
|
393
|
+
result = @object.instance_variable_get(@ivar)
|
394
|
+
if result != @current
|
395
|
+
begin
|
396
|
+
@prev = @current
|
397
|
+
@current = result
|
398
|
+
|
399
|
+
if (@cond.nil? || @object.instance_eval(@cond)) && !skip_path?(tp.path)
|
400
|
+
suspend
|
401
|
+
end
|
402
|
+
ensure
|
403
|
+
remove_instance_variable(:@prev)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
rescue Exception
|
407
|
+
false
|
408
|
+
end
|
409
|
+
|
410
|
+
def setup
|
411
|
+
@tp = TracePoint.new(:line, :return, :b_return){|tp|
|
412
|
+
watch_eval(tp)
|
413
|
+
}
|
414
|
+
end
|
415
|
+
|
416
|
+
def to_s
|
417
|
+
value_str =
|
418
|
+
if defined?(@prev)
|
419
|
+
"#{@prev} -> #{@current}"
|
420
|
+
else
|
421
|
+
"#{@current}"
|
422
|
+
end
|
423
|
+
"#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
class MethodBreakpoint < Breakpoint
|
428
|
+
attr_reader :sig_method_name, :method, :klass
|
429
|
+
|
430
|
+
def initialize b, klass_name, op, method_name, cond: nil, command: nil, path: nil
|
431
|
+
@sig_klass_name = klass_name
|
432
|
+
@sig_op = op
|
433
|
+
@sig_method_name = method_name
|
434
|
+
@klass_eval_binding = b
|
435
|
+
@override_method = false
|
436
|
+
|
437
|
+
@klass = nil
|
438
|
+
@method = nil
|
439
|
+
@cond_class = nil
|
440
|
+
@key = "#{klass_name}#{op}#{method_name}".freeze
|
441
|
+
|
442
|
+
super(cond, command, path, do_enable: false)
|
443
|
+
end
|
444
|
+
|
445
|
+
def setup
|
446
|
+
@tp = TracePoint.new(:call){|tp|
|
447
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
448
|
+
next if @cond_class && !tp.self.kind_of?(@cond_class)
|
449
|
+
|
450
|
+
caller_location = caller_locations(2, 1).first.to_s
|
451
|
+
next if skip_path?(caller_location)
|
452
|
+
|
453
|
+
suspend
|
454
|
+
}
|
455
|
+
end
|
456
|
+
|
457
|
+
def eval_class_name
|
458
|
+
return @klass if @klass
|
459
|
+
@klass = @klass_eval_binding.eval(@sig_klass_name)
|
460
|
+
@klass_eval_binding = nil
|
461
|
+
@klass
|
462
|
+
end
|
463
|
+
|
464
|
+
def search_method
|
465
|
+
case @sig_op
|
466
|
+
when '.'
|
467
|
+
@method = @klass.method(@sig_method_name)
|
468
|
+
when '#'
|
469
|
+
@method = @klass.instance_method(@sig_method_name)
|
470
|
+
else
|
471
|
+
raise "Unknown op: #{@sig_op}"
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def enable
|
476
|
+
try_enable
|
477
|
+
end
|
478
|
+
|
479
|
+
if RUBY_VERSION.to_f <= 2.6
|
480
|
+
def override klass
|
481
|
+
sig_method_name = @sig_method_name
|
482
|
+
klass.prepend Module.new{
|
483
|
+
define_method(sig_method_name) do |*args, &block|
|
484
|
+
super(*args, &block)
|
485
|
+
end
|
486
|
+
}
|
487
|
+
end
|
488
|
+
else
|
489
|
+
def override klass
|
490
|
+
sig_method_name = @sig_method_name
|
491
|
+
klass.prepend Module.new{
|
492
|
+
define_method(sig_method_name) do |*args, **kw, &block|
|
493
|
+
super(*args, **kw, &block)
|
494
|
+
end
|
495
|
+
}
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def try_enable added: false
|
500
|
+
eval_class_name
|
501
|
+
search_method
|
502
|
+
|
503
|
+
begin
|
504
|
+
retried = false
|
505
|
+
|
506
|
+
@tp.enable(target: @method)
|
507
|
+
DEBUGGER__.info "#{self} is activated." if added
|
508
|
+
|
509
|
+
if @sig_op == '#'
|
510
|
+
@cond_class = @klass if @method.owner != @klass
|
511
|
+
else # '.'
|
512
|
+
begin
|
513
|
+
@cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
|
514
|
+
rescue TypeError
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
rescue ArgumentError
|
519
|
+
raise if retried
|
520
|
+
retried = true
|
521
|
+
|
522
|
+
# maybe C method
|
523
|
+
case @sig_op
|
524
|
+
when '.'
|
525
|
+
begin
|
526
|
+
override @klass.singleton_class
|
527
|
+
rescue TypeError
|
528
|
+
override @klass.class
|
529
|
+
end
|
530
|
+
when '#'
|
531
|
+
override @klass
|
532
|
+
end
|
533
|
+
|
534
|
+
# re-collect the method object after the above patch
|
535
|
+
search_method
|
536
|
+
@override_method = true if @method
|
537
|
+
retry
|
538
|
+
end
|
539
|
+
rescue Exception
|
540
|
+
raise unless added
|
541
|
+
end
|
542
|
+
|
543
|
+
def sig
|
544
|
+
@key
|
545
|
+
end
|
546
|
+
|
547
|
+
def to_s
|
548
|
+
if @method
|
549
|
+
loc = @method.source_location || []
|
550
|
+
"#{generate_label("Method")} #{sig} at #{loc.join(':')}"
|
551
|
+
else
|
552
|
+
"#{generate_label("Method (pending)")} #{sig}"
|
553
|
+
end + super
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|