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