debug 1.0.0.beta6 → 1.0.0.rc2
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 +108 -106
- data/Gemfile +1 -0
- data/README.md +452 -226
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/debug.gemspec +1 -0
- data/exe/rdbg +4 -7
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +96 -63
- data/lib/debug/client.rb +8 -12
- data/lib/debug/color.rb +25 -6
- data/lib/debug/config.rb +376 -154
- data/lib/debug/console.rb +75 -66
- data/lib/debug/frame_info.rb +40 -7
- data/lib/debug/local.rb +91 -0
- data/lib/debug/open.rb +1 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +74 -30
- data/lib/debug/server_dap.rb +32 -7
- data/lib/debug/session.rb +729 -319
- data/lib/debug/{run.rb → start.rb} +1 -1
- data/lib/debug/thread_client.rb +651 -156
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +338 -217
- metadata +20 -3
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/debug.gemspec
CHANGED
data/exe/rdbg
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require_relative '../lib/debug/config'
|
|
4
|
-
config = DEBUGGER__
|
|
4
|
+
config = DEBUGGER__::Config::parse_argv(ARGV)
|
|
5
5
|
|
|
6
6
|
case config[:mode]
|
|
7
7
|
when :start
|
|
8
8
|
require 'rbconfig'
|
|
9
9
|
|
|
10
10
|
libpath = File.join(File.expand_path(File.dirname(__dir__)), 'lib/debug')
|
|
11
|
-
start_mode = config[:remote] ? "open" : '
|
|
11
|
+
start_mode = config[:remote] ? "open" : 'start'
|
|
12
12
|
cmd = config[:command] ? ARGV.shift : RbConfig.ruby
|
|
13
13
|
|
|
14
|
-
env = ::DEBUGGER__.config_to_env_hash(config)
|
|
14
|
+
env = ::DEBUGGER__::Config.config_to_env_hash(config)
|
|
15
15
|
env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
|
|
16
16
|
|
|
17
17
|
exec(env, cmd, *ARGV)
|
|
18
18
|
|
|
19
19
|
when :attach
|
|
20
20
|
require_relative "../lib/debug/client"
|
|
21
|
-
|
|
22
|
-
config.each{|k, v|
|
|
23
|
-
DEBUGGER__::CONFIG[k] = v
|
|
24
|
-
}
|
|
21
|
+
::DEBUGGER__::CONFIG.update config
|
|
25
22
|
|
|
26
23
|
begin
|
|
27
24
|
if ARGV.empty? && config[:port]
|
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
|
@@ -49,15 +49,22 @@ module DEBUGGER__
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
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
|
+
|
|
52
59
|
ThreadClient.current.on_breakpoint @tp, self
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
def to_s
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
61
68
|
end
|
|
62
69
|
|
|
63
70
|
def description
|
|
@@ -68,27 +75,29 @@ module DEBUGGER__
|
|
|
68
75
|
false
|
|
69
76
|
end
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
include Color
|
|
78
|
+
include Color
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
80
|
+
def generate_label(name)
|
|
81
|
+
colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
|
|
77
82
|
end
|
|
78
83
|
end
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
89
|
|
|
90
|
+
class LineBreakpoint < Breakpoint
|
|
84
91
|
attr_reader :path, :line, :iseq
|
|
85
92
|
|
|
86
|
-
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
|
|
87
94
|
@path = path
|
|
88
95
|
@line = line
|
|
89
96
|
@cond = cond
|
|
90
97
|
@oneshot = oneshot
|
|
91
98
|
@hook_call = hook_call
|
|
99
|
+
@command = command
|
|
100
|
+
@pending = false
|
|
92
101
|
|
|
93
102
|
@iseq = nil
|
|
94
103
|
@type = nil
|
|
@@ -96,23 +105,20 @@ module DEBUGGER__
|
|
|
96
105
|
@key = [@path, @line].freeze
|
|
97
106
|
|
|
98
107
|
super()
|
|
108
|
+
|
|
99
109
|
try_activate
|
|
110
|
+
@pending = !@iseq
|
|
100
111
|
end
|
|
101
112
|
|
|
102
113
|
def setup
|
|
103
114
|
return unless @type
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
delete if @oneshot
|
|
108
|
-
suspend
|
|
109
|
-
end
|
|
110
|
-
else
|
|
111
|
-
@tp = TracePoint.new(@type) do |tp|
|
|
116
|
+
@tp = TracePoint.new(@type) do |tp|
|
|
117
|
+
if @cond
|
|
112
118
|
next unless safe_eval tp.binding, @cond
|
|
113
|
-
delete if @oneshot
|
|
114
|
-
suspend
|
|
115
119
|
end
|
|
120
|
+
delete if @oneshot
|
|
121
|
+
suspend
|
|
116
122
|
end
|
|
117
123
|
end
|
|
118
124
|
|
|
@@ -138,9 +144,12 @@ module DEBUGGER__
|
|
|
138
144
|
|
|
139
145
|
@key = [@path, @line].freeze
|
|
140
146
|
SESSION.rehash_bps
|
|
141
|
-
|
|
142
147
|
setup
|
|
143
148
|
enable
|
|
149
|
+
|
|
150
|
+
if @pending && !@oneshot
|
|
151
|
+
DEBUGGER__.warn "#{self} is activated."
|
|
152
|
+
end
|
|
144
153
|
end
|
|
145
154
|
|
|
146
155
|
def activate_exact iseq, events, line
|
|
@@ -162,7 +171,6 @@ module DEBUGGER__
|
|
|
162
171
|
end
|
|
163
172
|
|
|
164
173
|
def duplicable?
|
|
165
|
-
# only binding.bp or DEBUGGER__.console are duplicable
|
|
166
174
|
@oneshot
|
|
167
175
|
end
|
|
168
176
|
|
|
@@ -214,9 +222,9 @@ module DEBUGGER__
|
|
|
214
222
|
oneshot = @oneshot ? " (oneshot)" : ""
|
|
215
223
|
|
|
216
224
|
if @iseq
|
|
217
|
-
"#{
|
|
225
|
+
"#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
|
|
218
226
|
else
|
|
219
|
-
"#{
|
|
227
|
+
"#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
|
|
220
228
|
end
|
|
221
229
|
end
|
|
222
230
|
|
|
@@ -226,21 +234,28 @@ module DEBUGGER__
|
|
|
226
234
|
end
|
|
227
235
|
|
|
228
236
|
class CatchBreakpoint < Breakpoint
|
|
229
|
-
LABEL = generate_label("Catch")
|
|
230
237
|
attr_reader :last_exc
|
|
238
|
+
include SkipPathHelper
|
|
231
239
|
|
|
232
|
-
def initialize pat
|
|
240
|
+
def initialize pat, cond: nil, command: nil
|
|
233
241
|
@pat = pat.freeze
|
|
234
242
|
@key = [:catch, @pat].freeze
|
|
235
243
|
@last_exc = nil
|
|
236
244
|
|
|
245
|
+
@cond = cond
|
|
246
|
+
@command = command
|
|
247
|
+
|
|
237
248
|
super()
|
|
238
249
|
end
|
|
239
250
|
|
|
240
251
|
def setup
|
|
241
252
|
@tp = TracePoint.new(:raise){|tp|
|
|
253
|
+
next if skip_path?(tp.path)
|
|
242
254
|
exc = tp.raised_exception
|
|
255
|
+
next if SystemExit === exc
|
|
256
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
|
243
257
|
should_suspend = false
|
|
258
|
+
|
|
244
259
|
exc.class.ancestors.each{|cls|
|
|
245
260
|
if @pat === cls.name
|
|
246
261
|
should_suspend = true
|
|
@@ -253,7 +268,7 @@ module DEBUGGER__
|
|
|
253
268
|
end
|
|
254
269
|
|
|
255
270
|
def to_s
|
|
256
|
-
"#{
|
|
271
|
+
"#{generate_label("Catch")} #{@pat.inspect}"
|
|
257
272
|
end
|
|
258
273
|
|
|
259
274
|
def description
|
|
@@ -262,8 +277,6 @@ module DEBUGGER__
|
|
|
262
277
|
end
|
|
263
278
|
|
|
264
279
|
class CheckBreakpoint < Breakpoint
|
|
265
|
-
LABEL = generate_label("Check")
|
|
266
|
-
|
|
267
280
|
def initialize expr
|
|
268
281
|
@expr = expr.freeze
|
|
269
282
|
@key = [:check, @expr].freeze
|
|
@@ -275,8 +288,6 @@ module DEBUGGER__
|
|
|
275
288
|
@tp = TracePoint.new(:line){|tp|
|
|
276
289
|
next if tp.path.start_with? __dir__
|
|
277
290
|
next if tp.path.start_with? '<internal:'
|
|
278
|
-
# Skip when `JSON.generate` is called during tests
|
|
279
|
-
next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
|
|
280
291
|
|
|
281
292
|
if safe_eval tp.binding, @expr
|
|
282
293
|
suspend
|
|
@@ -285,13 +296,11 @@ module DEBUGGER__
|
|
|
285
296
|
end
|
|
286
297
|
|
|
287
298
|
def to_s
|
|
288
|
-
"#{
|
|
299
|
+
"#{generate_label("Check")} #{@expr}"
|
|
289
300
|
end
|
|
290
301
|
end
|
|
291
302
|
|
|
292
303
|
class WatchIVarBreakpoint < Breakpoint
|
|
293
|
-
LABEL = generate_label("Watch")
|
|
294
|
-
|
|
295
304
|
def initialize ivar, object, current
|
|
296
305
|
@ivar = ivar.to_sym
|
|
297
306
|
@object = object
|
|
@@ -332,41 +341,38 @@ module DEBUGGER__
|
|
|
332
341
|
else
|
|
333
342
|
"#{@current}"
|
|
334
343
|
end
|
|
335
|
-
"#{
|
|
344
|
+
"#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
|
|
336
345
|
end
|
|
337
346
|
end
|
|
338
347
|
|
|
339
348
|
class MethodBreakpoint < Breakpoint
|
|
340
|
-
LABEL = generate_label("Method")
|
|
341
|
-
PENDING_LABEL = generate_label("Method (pending)")
|
|
342
|
-
|
|
343
349
|
attr_reader :sig_method_name, :method
|
|
344
350
|
|
|
345
|
-
def initialize b, klass_name, op, method_name, cond
|
|
351
|
+
def initialize b, klass_name, op, method_name, cond: nil, command: nil
|
|
346
352
|
@sig_klass_name = klass_name
|
|
347
353
|
@sig_op = op
|
|
348
354
|
@sig_method_name = method_name
|
|
349
355
|
@klass_eval_binding = b
|
|
356
|
+
@override_method = false
|
|
350
357
|
|
|
351
358
|
@klass = nil
|
|
352
359
|
@method = nil
|
|
353
360
|
@cond = cond
|
|
361
|
+
@cond_class = nil
|
|
362
|
+
@command = command
|
|
354
363
|
@key = "#{klass_name}#{op}#{method_name}".freeze
|
|
355
364
|
|
|
356
365
|
super(false)
|
|
357
366
|
end
|
|
358
367
|
|
|
359
368
|
def setup
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
suspend
|
|
368
|
-
}
|
|
369
|
-
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
|
+
}
|
|
370
376
|
end
|
|
371
377
|
|
|
372
378
|
def eval_class_name
|
|
@@ -391,34 +397,61 @@ module DEBUGGER__
|
|
|
391
397
|
try_enable
|
|
392
398
|
end
|
|
393
399
|
|
|
394
|
-
|
|
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
|
|
395
421
|
eval_class_name
|
|
396
422
|
search_method
|
|
397
423
|
|
|
398
424
|
begin
|
|
399
425
|
retried = false
|
|
426
|
+
|
|
400
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
|
|
401
435
|
|
|
402
436
|
rescue ArgumentError
|
|
403
437
|
raise if retried
|
|
404
438
|
retried = true
|
|
405
|
-
sig_method_name = @sig_method_name
|
|
406
439
|
|
|
407
440
|
# maybe C method
|
|
408
|
-
@
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
end
|
|
441
|
+
case @sig_op
|
|
442
|
+
when '.'
|
|
443
|
+
override @klass.singleton_class
|
|
444
|
+
when '#'
|
|
445
|
+
override @klass
|
|
414
446
|
end
|
|
415
447
|
|
|
416
448
|
# re-collect the method object after the above patch
|
|
417
449
|
search_method
|
|
450
|
+
@override_method = true if @method
|
|
418
451
|
retry
|
|
419
452
|
end
|
|
420
453
|
rescue Exception
|
|
421
|
-
raise unless
|
|
454
|
+
raise unless added
|
|
422
455
|
end
|
|
423
456
|
|
|
424
457
|
def sig
|
|
@@ -427,9 +460,9 @@ module DEBUGGER__
|
|
|
427
460
|
|
|
428
461
|
def to_s
|
|
429
462
|
if @method
|
|
430
|
-
"#{
|
|
463
|
+
"#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
|
|
431
464
|
else
|
|
432
|
-
"#{
|
|
465
|
+
"#{generate_label("Method (pending)")} #{sig}"
|
|
433
466
|
end + super
|
|
434
467
|
end
|
|
435
468
|
end
|
data/lib/debug/client.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'io/console/size'
|
|
|
5
5
|
|
|
6
6
|
require_relative 'config'
|
|
7
7
|
require_relative 'version'
|
|
8
|
+
require_relative 'console'
|
|
8
9
|
|
|
9
10
|
# $VERBOSE = true
|
|
10
11
|
|
|
@@ -12,21 +13,11 @@ module DEBUGGER__
|
|
|
12
13
|
class CommandLineOptionError < Exception; end
|
|
13
14
|
|
|
14
15
|
class Client
|
|
15
|
-
begin
|
|
16
|
-
require 'readline'
|
|
17
|
-
def readline
|
|
18
|
-
Readline.readline("\n(rdbg:remote) ", true)
|
|
19
|
-
end
|
|
20
|
-
rescue LoadError
|
|
21
|
-
def readline
|
|
22
|
-
print "\n(rdbg:remote) "
|
|
23
|
-
gets
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
16
|
def initialize argv
|
|
28
17
|
return util(argv) if String === argv
|
|
29
18
|
|
|
19
|
+
@console = Console.new
|
|
20
|
+
|
|
30
21
|
case argv.size
|
|
31
22
|
when 0
|
|
32
23
|
connect_unix
|
|
@@ -47,11 +38,16 @@ module DEBUGGER__
|
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
@width = IO.console_size[1]
|
|
41
|
+
@width = 80 if @width == 0
|
|
50
42
|
@width_changed = false
|
|
51
43
|
|
|
52
44
|
send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
|
|
53
45
|
end
|
|
54
46
|
|
|
47
|
+
def readline
|
|
48
|
+
@console.readline "(rdbg:remote) "
|
|
49
|
+
end
|
|
50
|
+
|
|
55
51
|
def util name
|
|
56
52
|
case name
|
|
57
53
|
when 'gen-sockpath'
|