debug 1.0.0.beta5 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|