debug 1.0.0.beta4 → 1.0.0.beta8
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/.github/workflows/ruby.yml +34 -0
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +239 -0
- data/Gemfile +2 -1
- data/README.md +424 -215
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/bin/gentest +22 -0
- data/debug.gemspec +2 -0
- data/exe/rdbg +14 -11
- data/ext/debug/debug.c +9 -8
- data/lib/debug.rb +3 -0
- data/lib/debug/breakpoint.rb +101 -43
- data/lib/debug/client.rb +55 -13
- data/lib/debug/color.rb +76 -0
- data/lib/debug/config.rb +130 -39
- data/lib/debug/console.rb +24 -3
- data/lib/debug/frame_info.rb +63 -31
- data/lib/debug/open.rb +4 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +90 -32
- data/lib/debug/server_dap.rb +607 -0
- data/lib/debug/session.rb +461 -174
- data/lib/debug/source_repository.rb +55 -33
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +176 -59
- data/lib/debug/version.rb +3 -1
- data/misc/README.md.erb +351 -204
- metadata +23 -4
- data/lib/debug/run.rb +0 -2
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
|
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ["lib"]
|
|
26
26
|
spec.extensions = ['ext/debug/extconf.rb']
|
|
27
|
+
|
|
28
|
+
spec.add_dependency "irb" # for its color_printer class, which was added after 1.3
|
|
27
29
|
end
|
data/exe/rdbg
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require_relative '../lib/debug/
|
|
4
|
-
|
|
3
|
+
require_relative '../lib/debug/config'
|
|
5
4
|
config = DEBUGGER__.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__.
|
|
18
|
-
|
|
14
|
+
env = ::DEBUGGER__.config_to_env_hash(config)
|
|
15
|
+
env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
|
|
16
|
+
|
|
17
|
+
exec(env, cmd, *ARGV)
|
|
19
18
|
|
|
20
19
|
when :attach
|
|
21
20
|
require_relative "../lib/debug/client"
|
|
22
21
|
|
|
22
|
+
config.each{|k, v|
|
|
23
|
+
DEBUGGER__::CONFIG[k] = v
|
|
24
|
+
}
|
|
25
|
+
|
|
23
26
|
begin
|
|
24
27
|
if ARGV.empty? && config[:port]
|
|
25
|
-
|
|
28
|
+
DEBUGGER__::Client.new([config[:host], config[:port]].compact).connect
|
|
26
29
|
else
|
|
27
|
-
connect
|
|
30
|
+
DEBUGGER__::Client.new(ARGV).connect
|
|
28
31
|
end
|
|
29
32
|
rescue DEBUGGER__::CommandLineOptionError
|
|
30
33
|
puts opt.help
|
data/ext/debug/debug.c
CHANGED
|
@@ -22,7 +22,7 @@ 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, loc, self, binding, iseq, klass, depth, Qnil, Qnil, Qnil);
|
|
25
|
+
return rb_struct_new(rb_cFrameInfo, loc, self, binding, iseq, klass, depth, Qnil, Qnil, Qnil, Qnil, Qnil);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
static int
|
|
@@ -49,6 +49,7 @@ di_body(const rb_debug_inspector_t *dc, void *ptr)
|
|
|
49
49
|
long i;
|
|
50
50
|
|
|
51
51
|
for (i=1; i<len; i++) {
|
|
52
|
+
VALUE loc, e;
|
|
52
53
|
VALUE iseq = rb_debug_inspector_frame_iseq_get(dc, i);
|
|
53
54
|
|
|
54
55
|
if (!NIL_P(iseq)) {
|
|
@@ -56,13 +57,13 @@ di_body(const rb_debug_inspector_t *dc, void *ptr)
|
|
|
56
57
|
if (!NIL_P(path) && str_start_with(path, skip_path_prefix)) continue;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
loc = RARRAY_AREF(locs, i);
|
|
61
|
+
e = di_entry(loc,
|
|
62
|
+
rb_debug_inspector_frame_self_get(dc, i),
|
|
63
|
+
rb_debug_inspector_frame_binding_get(dc, i),
|
|
64
|
+
iseq,
|
|
65
|
+
rb_debug_inspector_frame_class_get(dc, i),
|
|
66
|
+
INT2FIX(len - i));
|
|
66
67
|
rb_ary_push(ary, e);
|
|
67
68
|
}
|
|
68
69
|
|
data/lib/debug.rb
CHANGED
data/lib/debug/breakpoint.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'color'
|
|
4
|
+
|
|
1
5
|
module DEBUGGER__
|
|
2
6
|
class Breakpoint
|
|
3
7
|
attr_reader :key
|
|
@@ -45,27 +49,50 @@ module DEBUGGER__
|
|
|
45
49
|
end
|
|
46
50
|
|
|
47
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
|
+
|
|
48
59
|
ThreadClient.current.on_breakpoint @tp, self
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
def to_s
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def description
|
|
71
|
+
to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def duplicable?
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
include Color
|
|
79
|
+
|
|
80
|
+
def generate_label(name)
|
|
81
|
+
colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
|
|
57
82
|
end
|
|
58
83
|
end
|
|
59
84
|
|
|
60
85
|
class LineBreakpoint < Breakpoint
|
|
61
86
|
attr_reader :path, :line, :iseq
|
|
62
87
|
|
|
63
|
-
def initialize path, line, cond: nil, oneshot: false, hook_call: true
|
|
88
|
+
def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
|
|
64
89
|
@path = path
|
|
65
90
|
@line = line
|
|
66
91
|
@cond = cond
|
|
67
92
|
@oneshot = oneshot
|
|
68
93
|
@hook_call = hook_call
|
|
94
|
+
@command = command
|
|
95
|
+
@pending = false
|
|
69
96
|
|
|
70
97
|
@iseq = nil
|
|
71
98
|
@type = nil
|
|
@@ -73,23 +100,21 @@ module DEBUGGER__
|
|
|
73
100
|
@key = [@path, @line].freeze
|
|
74
101
|
|
|
75
102
|
super()
|
|
103
|
+
|
|
76
104
|
try_activate
|
|
105
|
+
@pending = !@iseq
|
|
77
106
|
end
|
|
78
107
|
|
|
79
108
|
def setup
|
|
80
109
|
return unless @type
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
delete if @oneshot
|
|
85
|
-
suspend
|
|
86
|
-
end
|
|
87
|
-
else
|
|
88
|
-
@tp = TracePoint.new(@type) do |tp|
|
|
111
|
+
@tp = TracePoint.new(@type) do |tp|
|
|
112
|
+
if @cond
|
|
89
113
|
next unless safe_eval tp.binding, @cond
|
|
90
|
-
delete if @oneshot
|
|
91
|
-
suspend
|
|
92
114
|
end
|
|
115
|
+
delete if @oneshot
|
|
116
|
+
|
|
117
|
+
suspend
|
|
93
118
|
end
|
|
94
119
|
end
|
|
95
120
|
|
|
@@ -115,9 +140,12 @@ module DEBUGGER__
|
|
|
115
140
|
|
|
116
141
|
@key = [@path, @line].freeze
|
|
117
142
|
SESSION.rehash_bps
|
|
118
|
-
|
|
119
143
|
setup
|
|
120
144
|
enable
|
|
145
|
+
|
|
146
|
+
if @pending && !@oneshot
|
|
147
|
+
DEBUGGER__.warn "#{self} is activated."
|
|
148
|
+
end
|
|
121
149
|
end
|
|
122
150
|
|
|
123
151
|
def activate_exact iseq, events, line
|
|
@@ -138,6 +166,10 @@ module DEBUGGER__
|
|
|
138
166
|
end
|
|
139
167
|
end
|
|
140
168
|
|
|
169
|
+
def duplicable?
|
|
170
|
+
@oneshot
|
|
171
|
+
end
|
|
172
|
+
|
|
141
173
|
NearestISeq = Struct.new(:iseq, :line, :events)
|
|
142
174
|
|
|
143
175
|
def try_activate
|
|
@@ -186,9 +218,9 @@ module DEBUGGER__
|
|
|
186
218
|
oneshot = @oneshot ? " (oneshot)" : ""
|
|
187
219
|
|
|
188
220
|
if @iseq
|
|
189
|
-
"
|
|
221
|
+
"#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
|
|
190
222
|
else
|
|
191
|
-
"
|
|
223
|
+
"#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
|
|
192
224
|
end
|
|
193
225
|
end
|
|
194
226
|
|
|
@@ -198,9 +230,12 @@ module DEBUGGER__
|
|
|
198
230
|
end
|
|
199
231
|
|
|
200
232
|
class CatchBreakpoint < Breakpoint
|
|
233
|
+
attr_reader :last_exc
|
|
234
|
+
|
|
201
235
|
def initialize pat
|
|
202
236
|
@pat = pat.freeze
|
|
203
237
|
@key = [:catch, @pat].freeze
|
|
238
|
+
@last_exc = nil
|
|
204
239
|
|
|
205
240
|
super()
|
|
206
241
|
end
|
|
@@ -208,14 +243,26 @@ module DEBUGGER__
|
|
|
208
243
|
def setup
|
|
209
244
|
@tp = TracePoint.new(:raise){|tp|
|
|
210
245
|
exc = tp.raised_exception
|
|
246
|
+
next if SystemExit === exc
|
|
247
|
+
should_suspend = false
|
|
248
|
+
|
|
211
249
|
exc.class.ancestors.each{|cls|
|
|
212
|
-
|
|
250
|
+
if @pat === cls.name
|
|
251
|
+
should_suspend = true
|
|
252
|
+
@last_exc = exc
|
|
253
|
+
break
|
|
254
|
+
end
|
|
213
255
|
}
|
|
256
|
+
suspend if should_suspend
|
|
214
257
|
}
|
|
215
258
|
end
|
|
216
259
|
|
|
217
260
|
def to_s
|
|
218
|
-
"
|
|
261
|
+
"#{generate_label("Catch")} #{@pat.inspect}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def description
|
|
265
|
+
"#{@last_exc.inspect} is raised."
|
|
219
266
|
end
|
|
220
267
|
end
|
|
221
268
|
|
|
@@ -231,6 +278,8 @@ module DEBUGGER__
|
|
|
231
278
|
@tp = TracePoint.new(:line){|tp|
|
|
232
279
|
next if tp.path.start_with? __dir__
|
|
233
280
|
next if tp.path.start_with? '<internal:'
|
|
281
|
+
# Skip when `JSON.generate` is called during tests
|
|
282
|
+
next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
|
|
234
283
|
|
|
235
284
|
if safe_eval tp.binding, @expr
|
|
236
285
|
suspend
|
|
@@ -239,21 +288,22 @@ module DEBUGGER__
|
|
|
239
288
|
end
|
|
240
289
|
|
|
241
290
|
def to_s
|
|
242
|
-
"
|
|
291
|
+
"#{generate_label("Check")} #{@expr}"
|
|
243
292
|
end
|
|
244
293
|
end
|
|
245
294
|
|
|
246
|
-
class
|
|
247
|
-
def initialize
|
|
248
|
-
@
|
|
249
|
-
@
|
|
295
|
+
class WatchIVarBreakpoint < Breakpoint
|
|
296
|
+
def initialize ivar, object, current
|
|
297
|
+
@ivar = ivar.to_sym
|
|
298
|
+
@object = object
|
|
299
|
+
@key = [:watch, @ivar].freeze
|
|
250
300
|
|
|
251
301
|
@current = current
|
|
252
302
|
super()
|
|
253
303
|
end
|
|
254
304
|
|
|
255
|
-
def watch_eval
|
|
256
|
-
result =
|
|
305
|
+
def watch_eval
|
|
306
|
+
result = @object.instance_variable_get(@ivar)
|
|
257
307
|
if result != @current
|
|
258
308
|
begin
|
|
259
309
|
@prev = @current
|
|
@@ -263,7 +313,7 @@ module DEBUGGER__
|
|
|
263
313
|
remove_instance_variable(:@prev)
|
|
264
314
|
end
|
|
265
315
|
end
|
|
266
|
-
rescue Exception
|
|
316
|
+
rescue Exception
|
|
267
317
|
false
|
|
268
318
|
end
|
|
269
319
|
|
|
@@ -272,23 +322,25 @@ module DEBUGGER__
|
|
|
272
322
|
next if tp.path.start_with? __dir__
|
|
273
323
|
next if tp.path.start_with? '<internal:'
|
|
274
324
|
|
|
275
|
-
watch_eval
|
|
325
|
+
watch_eval
|
|
276
326
|
}
|
|
277
327
|
end
|
|
278
328
|
|
|
279
329
|
def to_s
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
330
|
+
value_str =
|
|
331
|
+
if defined?(@prev)
|
|
332
|
+
"#{@prev} -> #{@current}"
|
|
333
|
+
else
|
|
334
|
+
"#{@current}"
|
|
335
|
+
end
|
|
336
|
+
"#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
|
|
285
337
|
end
|
|
286
338
|
end
|
|
287
339
|
|
|
288
340
|
class MethodBreakpoint < Breakpoint
|
|
289
341
|
attr_reader :sig_method_name, :method
|
|
290
342
|
|
|
291
|
-
def initialize b, klass_name, op, method_name, cond
|
|
343
|
+
def initialize b, klass_name, op, method_name, cond, command: nil
|
|
292
344
|
@sig_klass_name = klass_name
|
|
293
345
|
@sig_op = op
|
|
294
346
|
@sig_method_name = method_name
|
|
@@ -297,6 +349,7 @@ module DEBUGGER__
|
|
|
297
349
|
@klass = nil
|
|
298
350
|
@method = nil
|
|
299
351
|
@cond = cond
|
|
352
|
+
@command = command
|
|
300
353
|
@key = "#{klass_name}#{op}#{method_name}".freeze
|
|
301
354
|
|
|
302
355
|
super(false)
|
|
@@ -337,30 +390,35 @@ module DEBUGGER__
|
|
|
337
390
|
try_enable
|
|
338
391
|
end
|
|
339
392
|
|
|
340
|
-
def try_enable
|
|
393
|
+
def try_enable added: false
|
|
341
394
|
eval_class_name
|
|
342
395
|
search_method
|
|
343
396
|
|
|
344
397
|
begin
|
|
345
398
|
retried = false
|
|
346
399
|
@tp.enable(target: @method)
|
|
400
|
+
DEBUGGER__.warn "#{self} is activated." if added
|
|
347
401
|
|
|
348
|
-
rescue ArgumentError
|
|
402
|
+
rescue ArgumentError
|
|
349
403
|
raise if retried
|
|
350
404
|
retried = true
|
|
405
|
+
sig_method_name = @sig_method_name
|
|
351
406
|
|
|
352
407
|
# maybe C method
|
|
353
408
|
@klass.module_eval do
|
|
354
|
-
orig_name =
|
|
355
|
-
alias_method orig_name,
|
|
356
|
-
define_method(
|
|
409
|
+
orig_name = sig_method_name + '__orig__'
|
|
410
|
+
alias_method orig_name, sig_method_name
|
|
411
|
+
define_method(sig_method_name) do |*args|
|
|
357
412
|
send(orig_name, *args)
|
|
358
413
|
end
|
|
359
414
|
end
|
|
415
|
+
|
|
416
|
+
# re-collect the method object after the above patch
|
|
417
|
+
search_method
|
|
360
418
|
retry
|
|
361
419
|
end
|
|
362
420
|
rescue Exception
|
|
363
|
-
raise unless
|
|
421
|
+
raise unless added
|
|
364
422
|
end
|
|
365
423
|
|
|
366
424
|
def sig
|
|
@@ -369,9 +427,9 @@ module DEBUGGER__
|
|
|
369
427
|
|
|
370
428
|
def to_s
|
|
371
429
|
if @method
|
|
372
|
-
"
|
|
430
|
+
"#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
|
|
373
431
|
else
|
|
374
|
-
"
|
|
432
|
+
"#{generate_label("Method (pending)")} #{sig}"
|
|
375
433
|
end + super
|
|
376
434
|
end
|
|
377
435
|
end
|