debug 1.0.0.beta5 → 1.0.0.rc1
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 +213 -20
- data/Gemfile +1 -0
- data/README.md +460 -226
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/bin/gentest +22 -0
- data/debug.gemspec +1 -0
- data/exe/rdbg +11 -18
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +106 -62
- data/lib/debug/client.rb +11 -17
- data/lib/debug/color.rb +28 -7
- data/lib/debug/config.rb +378 -144
- data/lib/debug/console.rb +79 -57
- data/lib/debug/frame_info.rb +42 -8
- data/lib/debug/local.rb +91 -0
- data/lib/debug/open.rb +2 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +96 -43
- data/lib/debug/server_dap.rb +34 -7
- data/lib/debug/session.rb +827 -341
- data/lib/debug/source_repository.rb +2 -0
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +691 -184
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +3 -1
- data/lib/debug.rb +3 -0
- data/misc/README.md.erb +341 -216
- metadata +21 -4
- data/lib/debug/run.rb +0 -4
- data/lib/debug/test_console.rb +0 -0
data/Rakefile
CHANGED
@@ -17,7 +17,8 @@ end
|
|
17
17
|
|
18
18
|
task :default => [:clobber, :compile, :test, 'README.md']
|
19
19
|
|
20
|
-
file 'README.md' => ['lib/debug/session.rb', '
|
20
|
+
file 'README.md' => ['lib/debug/session.rb', 'lib/debug/config.rb',
|
21
|
+
'exe/rdbg', 'misc/README.md.erb'] do
|
21
22
|
require_relative 'lib/debug/session'
|
22
23
|
require 'erb'
|
23
24
|
File.write 'README.md', ERB.new(File.read('misc/README.md.erb')).result
|
data/TODO.md
CHANGED
@@ -2,14 +2,11 @@
|
|
2
2
|
|
3
3
|
## Basic functionality
|
4
4
|
|
5
|
-
* VScode DAP support
|
6
5
|
* Support Ractors
|
7
6
|
* Signal (SIGINT) support
|
8
|
-
* Remote connection with openssl
|
9
7
|
|
10
8
|
## UI
|
11
9
|
|
12
|
-
* Coloring
|
13
10
|
* Interactive breakpoint setting
|
14
11
|
* irb integration
|
15
12
|
* Web browser integrated UI
|
@@ -28,6 +25,3 @@
|
|
28
25
|
|
29
26
|
## Tests
|
30
27
|
|
31
|
-
* Test framework
|
32
|
-
* Tests for commands
|
33
|
-
* Tests for remote debugging
|
data/bin/gentest
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require_relative '../test/tool/test_builder'
|
6
|
+
|
7
|
+
file_info = {}
|
8
|
+
|
9
|
+
OptionParser.new do |opt|
|
10
|
+
opt.banner = 'Usage: bin/gentest [file] [option]'
|
11
|
+
opt.on('-m METHOD', 'Method name in the test file') do |m|
|
12
|
+
file_info[:method] = m
|
13
|
+
end
|
14
|
+
opt.on('-c CLASS', 'Class name in the test file') do |c|
|
15
|
+
file_info[:class] = c
|
16
|
+
end
|
17
|
+
opt.parse!(ARGV)
|
18
|
+
end
|
19
|
+
|
20
|
+
exit if ARGV.empty?
|
21
|
+
|
22
|
+
DEBUGGER__::TestBuilder.new(ARGV, file_info[:method], file_info[:class]).start
|
data/debug.gemspec
CHANGED
data/exe/rdbg
CHANGED
@@ -1,37 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require_relative '../lib/debug/
|
4
|
-
|
5
|
-
config = DEBUGGER__.parse_argv(ARGV)
|
3
|
+
require_relative '../lib/debug/config'
|
4
|
+
config = DEBUGGER__::Config::parse_argv(ARGV)
|
6
5
|
|
7
6
|
case config[:mode]
|
8
7
|
when :start
|
9
8
|
require 'rbconfig'
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
start_mode = "debug/run"
|
15
|
-
end
|
10
|
+
libpath = File.join(File.expand_path(File.dirname(__dir__)), 'lib/debug')
|
11
|
+
start_mode = config[:remote] ? "open" : 'start'
|
12
|
+
cmd = config[:command] ? ARGV.shift : RbConfig.ruby
|
16
13
|
|
17
|
-
::DEBUGGER__.
|
14
|
+
env = ::DEBUGGER__::Config.config_to_env_hash(config)
|
15
|
+
env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
|
18
16
|
|
19
|
-
cmd
|
20
|
-
exec({'RUBYOPT' => "-I#{File.expand_path(File.dirname(__dir__))}/lib -r #{start_mode}"},
|
21
|
-
cmd, *ARGV)
|
17
|
+
exec(env, cmd, *ARGV)
|
22
18
|
|
23
19
|
when :attach
|
24
20
|
require_relative "../lib/debug/client"
|
25
|
-
|
26
|
-
config.each{|k, v|
|
27
|
-
DEBUGGER__::CONFIG[k] = v
|
28
|
-
}
|
21
|
+
::DEBUGGER__::CONFIG.update config
|
29
22
|
|
30
23
|
begin
|
31
24
|
if ARGV.empty? && config[:port]
|
32
|
-
|
25
|
+
DEBUGGER__::Client.new([config[:host], config[:port]].compact).connect
|
33
26
|
else
|
34
|
-
connect
|
27
|
+
DEBUGGER__::Client.new(ARGV).connect
|
35
28
|
end
|
36
29
|
rescue DEBUGGER__::CommandLineOptionError
|
37
30
|
puts opt.help
|
data/ext/debug/debug.c
CHANGED
@@ -22,7 +22,17 @@ static VALUE rb_cFrameInfo;
|
|
22
22
|
static VALUE
|
23
23
|
di_entry(VALUE loc, VALUE self, VALUE binding, VALUE iseq, VALUE klass, VALUE depth)
|
24
24
|
{
|
25
|
-
return rb_struct_new(rb_cFrameInfo,
|
25
|
+
return rb_struct_new(rb_cFrameInfo,
|
26
|
+
// :location, :self, :binding, :iseq, :class, :frame_depth,
|
27
|
+
loc, self, binding, iseq, klass, depth,
|
28
|
+
// :has_return_value, :return_value,
|
29
|
+
Qnil, Qnil,
|
30
|
+
// :has_raised_exception, :raised_exception,
|
31
|
+
Qnil, Qnil,
|
32
|
+
// :show_line, :local_variables
|
33
|
+
Qnil,
|
34
|
+
// :_local_variables, :_callee # for recorder
|
35
|
+
Qnil, Qnil);
|
26
36
|
}
|
27
37
|
|
28
38
|
static int
|
data/lib/debug/breakpoint.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'color'
|
2
4
|
|
3
5
|
module DEBUGGER__
|
@@ -47,42 +49,55 @@ module DEBUGGER__
|
|
47
49
|
end
|
48
50
|
|
49
51
|
def suspend
|
52
|
+
if @command
|
53
|
+
provider, pre_cmds, do_cmds = @command
|
54
|
+
nonstop = true if do_cmds
|
55
|
+
cmds = [*pre_cmds&.split(';;'), *do_cmds&.split(';;')]
|
56
|
+
SESSION.add_preset_commands provider, cmds, kick: false, continue: nonstop
|
57
|
+
end
|
58
|
+
|
50
59
|
ThreadClient.current.on_breakpoint @tp, self
|
51
60
|
end
|
52
61
|
|
53
62
|
def to_s
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
63
|
+
s = ''.dup
|
64
|
+
s << " if: #{@cond}" if defined?(@cond) && @cond
|
65
|
+
s << " pre: #{@command[1]}" if defined?(@command) && @command && @command[1]
|
66
|
+
s << " do: #{@command[2]}" if defined?(@command) && @command && @command[2]
|
67
|
+
s
|
59
68
|
end
|
60
69
|
|
61
70
|
def description
|
62
71
|
to_s
|
63
72
|
end
|
64
73
|
|
65
|
-
|
66
|
-
|
74
|
+
def duplicable?
|
75
|
+
false
|
76
|
+
end
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
78
|
+
include Color
|
79
|
+
|
80
|
+
def generate_label(name)
|
81
|
+
colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
|
71
82
|
end
|
72
83
|
end
|
73
84
|
|
74
|
-
|
75
|
-
|
76
|
-
|
85
|
+
if RUBY_VERSION.to_f <= 2.7
|
86
|
+
# workaround for https://bugs.ruby-lang.org/issues/17302
|
87
|
+
TracePoint.new(:line){}.enable{}
|
88
|
+
end
|
77
89
|
|
90
|
+
class LineBreakpoint < Breakpoint
|
78
91
|
attr_reader :path, :line, :iseq
|
79
92
|
|
80
|
-
def initialize path, line, cond: nil, oneshot: false, hook_call: true
|
93
|
+
def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
|
81
94
|
@path = path
|
82
95
|
@line = line
|
83
96
|
@cond = cond
|
84
97
|
@oneshot = oneshot
|
85
98
|
@hook_call = hook_call
|
99
|
+
@command = command
|
100
|
+
@pending = false
|
86
101
|
|
87
102
|
@iseq = nil
|
88
103
|
@type = nil
|
@@ -90,23 +105,20 @@ module DEBUGGER__
|
|
90
105
|
@key = [@path, @line].freeze
|
91
106
|
|
92
107
|
super()
|
108
|
+
|
93
109
|
try_activate
|
110
|
+
@pending = !@iseq
|
94
111
|
end
|
95
112
|
|
96
113
|
def setup
|
97
114
|
return unless @type
|
98
115
|
|
99
|
-
|
100
|
-
|
101
|
-
delete if @oneshot
|
102
|
-
suspend
|
103
|
-
end
|
104
|
-
else
|
105
|
-
@tp = TracePoint.new(@type) do |tp|
|
116
|
+
@tp = TracePoint.new(@type) do |tp|
|
117
|
+
if @cond
|
106
118
|
next unless safe_eval tp.binding, @cond
|
107
|
-
delete if @oneshot
|
108
|
-
suspend
|
109
119
|
end
|
120
|
+
delete if @oneshot
|
121
|
+
suspend
|
110
122
|
end
|
111
123
|
end
|
112
124
|
|
@@ -132,9 +144,12 @@ module DEBUGGER__
|
|
132
144
|
|
133
145
|
@key = [@path, @line].freeze
|
134
146
|
SESSION.rehash_bps
|
135
|
-
|
136
147
|
setup
|
137
148
|
enable
|
149
|
+
|
150
|
+
if @pending && !@oneshot
|
151
|
+
DEBUGGER__.warn "#{self} is activated."
|
152
|
+
end
|
138
153
|
end
|
139
154
|
|
140
155
|
def activate_exact iseq, events, line
|
@@ -155,6 +170,10 @@ module DEBUGGER__
|
|
155
170
|
end
|
156
171
|
end
|
157
172
|
|
173
|
+
def duplicable?
|
174
|
+
@oneshot
|
175
|
+
end
|
176
|
+
|
158
177
|
NearestISeq = Struct.new(:iseq, :line, :events)
|
159
178
|
|
160
179
|
def try_activate
|
@@ -203,9 +222,9 @@ module DEBUGGER__
|
|
203
222
|
oneshot = @oneshot ? " (oneshot)" : ""
|
204
223
|
|
205
224
|
if @iseq
|
206
|
-
"#{
|
225
|
+
"#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
|
207
226
|
else
|
208
|
-
"#{
|
227
|
+
"#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
|
209
228
|
end
|
210
229
|
end
|
211
230
|
|
@@ -215,21 +234,28 @@ module DEBUGGER__
|
|
215
234
|
end
|
216
235
|
|
217
236
|
class CatchBreakpoint < Breakpoint
|
218
|
-
LABEL = generate_label("Catch")
|
219
237
|
attr_reader :last_exc
|
238
|
+
include SkipPathHelper
|
220
239
|
|
221
|
-
def initialize pat
|
240
|
+
def initialize pat, cond: nil, command: nil
|
222
241
|
@pat = pat.freeze
|
223
242
|
@key = [:catch, @pat].freeze
|
224
243
|
@last_exc = nil
|
225
244
|
|
245
|
+
@cond = cond
|
246
|
+
@command = command
|
247
|
+
|
226
248
|
super()
|
227
249
|
end
|
228
250
|
|
229
251
|
def setup
|
230
252
|
@tp = TracePoint.new(:raise){|tp|
|
253
|
+
next if skip_path?(tp.path)
|
231
254
|
exc = tp.raised_exception
|
255
|
+
next if SystemExit === exc
|
256
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
232
257
|
should_suspend = false
|
258
|
+
|
233
259
|
exc.class.ancestors.each{|cls|
|
234
260
|
if @pat === cls.name
|
235
261
|
should_suspend = true
|
@@ -242,7 +268,7 @@ module DEBUGGER__
|
|
242
268
|
end
|
243
269
|
|
244
270
|
def to_s
|
245
|
-
"#{
|
271
|
+
"#{generate_label("Catch")} #{@pat.inspect}"
|
246
272
|
end
|
247
273
|
|
248
274
|
def description
|
@@ -251,8 +277,6 @@ module DEBUGGER__
|
|
251
277
|
end
|
252
278
|
|
253
279
|
class CheckBreakpoint < Breakpoint
|
254
|
-
LABEL = generate_label("Check")
|
255
|
-
|
256
280
|
def initialize expr
|
257
281
|
@expr = expr.freeze
|
258
282
|
@key = [:check, @expr].freeze
|
@@ -264,8 +288,6 @@ module DEBUGGER__
|
|
264
288
|
@tp = TracePoint.new(:line){|tp|
|
265
289
|
next if tp.path.start_with? __dir__
|
266
290
|
next if tp.path.start_with? '<internal:'
|
267
|
-
# Skip when `JSON.generate` is called during tests
|
268
|
-
next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
|
269
291
|
|
270
292
|
if safe_eval tp.binding, @expr
|
271
293
|
suspend
|
@@ -274,13 +296,11 @@ module DEBUGGER__
|
|
274
296
|
end
|
275
297
|
|
276
298
|
def to_s
|
277
|
-
"#{
|
299
|
+
"#{generate_label("Check")} #{@expr}"
|
278
300
|
end
|
279
301
|
end
|
280
302
|
|
281
303
|
class WatchIVarBreakpoint < Breakpoint
|
282
|
-
LABEL = generate_label("Watch")
|
283
|
-
|
284
304
|
def initialize ivar, object, current
|
285
305
|
@ivar = ivar.to_sym
|
286
306
|
@object = object
|
@@ -321,41 +341,38 @@ module DEBUGGER__
|
|
321
341
|
else
|
322
342
|
"#{@current}"
|
323
343
|
end
|
324
|
-
"#{
|
344
|
+
"#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
|
325
345
|
end
|
326
346
|
end
|
327
347
|
|
328
348
|
class MethodBreakpoint < Breakpoint
|
329
|
-
LABEL = generate_label("Method")
|
330
|
-
PENDING_LABEL = generate_label("Method (pending)")
|
331
|
-
|
332
349
|
attr_reader :sig_method_name, :method
|
333
350
|
|
334
|
-
def initialize b, klass_name, op, method_name, cond
|
351
|
+
def initialize b, klass_name, op, method_name, cond: nil, command: nil
|
335
352
|
@sig_klass_name = klass_name
|
336
353
|
@sig_op = op
|
337
354
|
@sig_method_name = method_name
|
338
355
|
@klass_eval_binding = b
|
356
|
+
@override_method = false
|
339
357
|
|
340
358
|
@klass = nil
|
341
359
|
@method = nil
|
342
360
|
@cond = cond
|
361
|
+
@cond_class = nil
|
362
|
+
@command = command
|
343
363
|
@key = "#{klass_name}#{op}#{method_name}".freeze
|
344
364
|
|
345
365
|
super(false)
|
346
366
|
end
|
347
367
|
|
348
368
|
def setup
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
suspend
|
357
|
-
}
|
358
|
-
end
|
369
|
+
@tp = TracePoint.new(:call){|tp|
|
370
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
371
|
+
next if @cond_class && !tp.self.kind_of?(@cond_class)
|
372
|
+
next if @override_method ? (caller_locations(2, 1).first.to_s.start_with?(__dir__)) : tp.path.start_with?(__dir__)
|
373
|
+
|
374
|
+
suspend
|
375
|
+
}
|
359
376
|
end
|
360
377
|
|
361
378
|
def eval_class_name
|
@@ -380,34 +397,61 @@ module DEBUGGER__
|
|
380
397
|
try_enable
|
381
398
|
end
|
382
399
|
|
383
|
-
|
400
|
+
if RUBY_VERSION.to_f <= 2.6
|
401
|
+
def override klass
|
402
|
+
sig_method_name = @sig_method_name
|
403
|
+
klass.prepend Module.new{
|
404
|
+
define_method(sig_method_name) do |*args, &block|
|
405
|
+
super(*args, &block)
|
406
|
+
end
|
407
|
+
}
|
408
|
+
end
|
409
|
+
else
|
410
|
+
def override klass
|
411
|
+
sig_method_name = @sig_method_name
|
412
|
+
klass.prepend Module.new{
|
413
|
+
define_method(sig_method_name) do |*args, **kw, &block|
|
414
|
+
super(*args, **kw, &block)
|
415
|
+
end
|
416
|
+
}
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def try_enable added: false
|
384
421
|
eval_class_name
|
385
422
|
search_method
|
386
423
|
|
387
424
|
begin
|
388
425
|
retried = false
|
426
|
+
|
389
427
|
@tp.enable(target: @method)
|
428
|
+
DEBUGGER__.warn "#{self} is activated." if added
|
429
|
+
|
430
|
+
if @sig_op == '#'
|
431
|
+
@cond_class = @klass if @method.owner != @klass
|
432
|
+
else # '.'
|
433
|
+
@cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
|
434
|
+
end
|
390
435
|
|
391
436
|
rescue ArgumentError
|
392
437
|
raise if retried
|
393
438
|
retried = true
|
394
|
-
sig_method_name = @sig_method_name
|
395
439
|
|
396
440
|
# maybe C method
|
397
|
-
@
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
end
|
441
|
+
case @sig_op
|
442
|
+
when '.'
|
443
|
+
override @klass.singleton_class
|
444
|
+
when '#'
|
445
|
+
override @klass
|
403
446
|
end
|
404
447
|
|
405
448
|
# re-collect the method object after the above patch
|
406
449
|
search_method
|
450
|
+
@override_method = true if @method
|
407
451
|
retry
|
408
452
|
end
|
409
453
|
rescue Exception
|
410
|
-
raise unless
|
454
|
+
raise unless added
|
411
455
|
end
|
412
456
|
|
413
457
|
def sig
|
@@ -416,9 +460,9 @@ module DEBUGGER__
|
|
416
460
|
|
417
461
|
def to_s
|
418
462
|
if @method
|
419
|
-
"#{
|
463
|
+
"#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
|
420
464
|
else
|
421
|
-
"#{
|
465
|
+
"#{generate_label("Method (pending)")} #{sig}"
|
422
466
|
end + super
|
423
467
|
end
|
424
468
|
end
|