debug 1.3.4 → 1.6.0
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 +234 -9
- data/Gemfile +1 -0
- data/README.md +81 -31
- data/Rakefile +28 -10
- data/debug.gemspec +7 -5
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +80 -15
- data/ext/debug/extconf.rb +22 -0
- data/lib/debug/breakpoint.rb +141 -67
- data/lib/debug/client.rb +77 -20
- data/lib/debug/color.rb +29 -19
- data/lib/debug/config.rb +61 -27
- data/lib/debug/console.rb +59 -18
- data/lib/debug/frame_info.rb +41 -40
- data/lib/debug/local.rb +1 -1
- data/lib/debug/prelude.rb +2 -2
- data/lib/debug/server.rb +136 -103
- data/lib/debug/server_cdp.rb +880 -162
- data/lib/debug/server_dap.rb +445 -164
- data/lib/debug/session.rb +540 -269
- data/lib/debug/source_repository.rb +103 -52
- data/lib/debug/thread_client.rb +306 -138
- data/lib/debug/tracer.rb +8 -13
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +44 -16
- metadata +6 -15
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -22
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/session.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
return if ENV['RUBY_DEBUG_ENABLE'] == '0'
|
4
|
+
|
3
5
|
# skip to load debugger for bundle exec
|
4
6
|
|
5
7
|
if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
6
8
|
trace_var(:$0) do |file|
|
7
9
|
trace_var(:$0, nil)
|
8
|
-
if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
|
10
|
+
if /-r (#{Regexp.escape(__dir__)}\S+)/ =~ ENV['RUBYOPT']
|
9
11
|
lib = $1
|
10
12
|
$LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
|
11
13
|
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
|
@@ -17,6 +19,14 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
17
19
|
return
|
18
20
|
end
|
19
21
|
|
22
|
+
# restore RUBYOPT
|
23
|
+
if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
|
24
|
+
(rubyopt = ENV['RUBYOPT']) &&
|
25
|
+
rubyopt.start_with?(added_opt)
|
26
|
+
ENV['RUBYOPT'] = rubyopt.delete_prefix(rubyopt)
|
27
|
+
ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
|
28
|
+
end
|
29
|
+
|
20
30
|
require_relative 'frame_info'
|
21
31
|
require_relative 'config'
|
22
32
|
require_relative 'thread_client'
|
@@ -29,7 +39,8 @@ $LOADED_FEATURES << 'debug.rb'
|
|
29
39
|
$LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
|
30
40
|
require 'debug' # invalidate the $LOADED_FEATURE cache
|
31
41
|
|
32
|
-
require 'json' if ENV['
|
42
|
+
require 'json' if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
43
|
+
require 'pp'
|
33
44
|
|
34
45
|
class RubyVM::InstructionSequence
|
35
46
|
def traceable_lines_norec lines
|
@@ -54,23 +65,22 @@ class RubyVM::InstructionSequence
|
|
54
65
|
|
55
66
|
def type
|
56
67
|
self.to_a[9]
|
57
|
-
end
|
68
|
+
end unless method_defined?(:type)
|
58
69
|
|
59
|
-
def
|
60
|
-
self.to_a
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
70
|
+
def parameters_symbols
|
71
|
+
ary = self.to_a
|
72
|
+
argc = ary[4][:arg_size]
|
73
|
+
locals = ary.to_a[10]
|
74
|
+
locals[0...argc]
|
75
|
+
end unless method_defined?(:parameters_symbols)
|
66
76
|
|
67
77
|
def last_line
|
68
78
|
self.to_a[4][:code_location][2]
|
69
|
-
end
|
79
|
+
end unless method_defined?(:last_line)
|
70
80
|
|
71
81
|
def first_line
|
72
82
|
self.to_a[4][:code_location][0]
|
73
|
-
end
|
83
|
+
end unless method_defined?(:first_line)
|
74
84
|
end
|
75
85
|
|
76
86
|
module DEBUGGER__
|
@@ -80,8 +90,10 @@ module DEBUGGER__
|
|
80
90
|
class Session
|
81
91
|
attr_reader :intercepted_sigint_cmd, :process_group
|
82
92
|
|
83
|
-
|
84
|
-
|
93
|
+
include Color
|
94
|
+
|
95
|
+
def initialize
|
96
|
+
@ui = nil
|
85
97
|
@sr = SourceRepository.new
|
86
98
|
@bps = {} # bp.key => bp
|
87
99
|
# [file, line] => LineBreakpoint
|
@@ -99,17 +111,17 @@ module DEBUGGER__
|
|
99
111
|
@preset_command = nil
|
100
112
|
@postmortem_hook = nil
|
101
113
|
@postmortem = false
|
102
|
-
@thread_stopper = nil
|
103
114
|
@intercept_trap_sigint = false
|
104
115
|
@intercepted_sigint_cmd = 'DEFAULT'
|
105
116
|
@process_group = ProcessGroup.new
|
106
|
-
@
|
117
|
+
@subsession_stack = []
|
107
118
|
|
108
|
-
@frame_map = {} # {id => [threadId, frame_depth]}
|
119
|
+
@frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
|
109
120
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
110
121
|
@src_map = {} # {id => src}
|
111
122
|
|
112
|
-
@
|
123
|
+
@scr_id_map = {} # for CDP
|
124
|
+
@obj_map = {} # { object_id => ... } for CDP
|
113
125
|
|
114
126
|
@tp_thread_begin = nil
|
115
127
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
@@ -117,8 +129,7 @@ module DEBUGGER__
|
|
117
129
|
}
|
118
130
|
@tp_load_script.enable
|
119
131
|
|
120
|
-
|
121
|
-
|
132
|
+
@thread_stopper = thread_stopper
|
122
133
|
self.postmortem = CONFIG[:postmortem]
|
123
134
|
end
|
124
135
|
|
@@ -130,15 +141,13 @@ module DEBUGGER__
|
|
130
141
|
@bps.has_key? [file, line]
|
131
142
|
end
|
132
143
|
|
133
|
-
def activate on_fork: false
|
144
|
+
def activate ui = nil, on_fork: false
|
145
|
+
@ui = ui if ui
|
146
|
+
|
134
147
|
@tp_thread_begin&.disable
|
135
148
|
@tp_thread_begin = nil
|
136
149
|
|
137
|
-
|
138
|
-
@ui.activate self, on_fork: true
|
139
|
-
else
|
140
|
-
@ui.activate self, on_fork: false
|
141
|
-
end
|
150
|
+
@ui.activate self, on_fork: on_fork
|
142
151
|
|
143
152
|
q = Queue.new
|
144
153
|
@session_server = Thread.new do
|
@@ -147,15 +156,15 @@ module DEBUGGER__
|
|
147
156
|
|
148
157
|
# Thread management
|
149
158
|
setup_threads
|
150
|
-
thc =
|
151
|
-
thc.
|
159
|
+
thc = get_thread_client Thread.current
|
160
|
+
thc.mark_as_management
|
152
161
|
|
153
|
-
if @ui.respond_to?(:reader_thread) && thc =
|
154
|
-
thc.
|
162
|
+
if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
|
163
|
+
thc.mark_as_management
|
155
164
|
end
|
156
165
|
|
157
166
|
@tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
|
158
|
-
|
167
|
+
get_thread_client
|
159
168
|
end
|
160
169
|
@tp_thread_begin.enable
|
161
170
|
|
@@ -168,12 +177,12 @@ module DEBUGGER__
|
|
168
177
|
end
|
169
178
|
|
170
179
|
def deactivate
|
171
|
-
|
172
|
-
@thread_stopper.disable
|
180
|
+
get_thread_client.deactivate
|
181
|
+
@thread_stopper.disable
|
173
182
|
@tp_load_script.disable
|
174
183
|
@tp_thread_begin.disable
|
175
|
-
@bps.
|
176
|
-
@th_clients.
|
184
|
+
@bps.each_value{|bp| bp.disable}
|
185
|
+
@th_clients.each_value{|thc| thc.close}
|
177
186
|
@tracers.values.each{|t| t.disable}
|
178
187
|
@q_evt.close
|
179
188
|
@ui&.deactivate
|
@@ -197,9 +206,14 @@ module DEBUGGER__
|
|
197
206
|
deactivate
|
198
207
|
end
|
199
208
|
|
209
|
+
def request_tc(req)
|
210
|
+
@tc << req
|
211
|
+
end
|
212
|
+
|
200
213
|
def process_event evt
|
201
214
|
# variable `@internal_info` is only used for test
|
202
|
-
tc, output, ev, @internal_info, *ev_args = evt
|
215
|
+
@tc, output, ev, @internal_info, *ev_args = evt
|
216
|
+
|
203
217
|
output.each{|str| @ui.puts str} if ev != :suspend
|
204
218
|
|
205
219
|
case ev
|
@@ -211,20 +225,19 @@ module DEBUGGER__
|
|
211
225
|
q << true
|
212
226
|
|
213
227
|
when :init
|
214
|
-
|
215
|
-
|
228
|
+
enter_subsession
|
229
|
+
wait_command_loop
|
216
230
|
when :load
|
217
231
|
iseq, src = ev_args
|
218
232
|
on_load iseq, src
|
219
|
-
|
220
|
-
tc << :continue
|
233
|
+
request_tc :continue
|
221
234
|
|
222
235
|
when :trace
|
223
236
|
trace_id, msg = ev_args
|
224
237
|
if t = @tracers.values.find{|t| t.object_id == trace_id}
|
225
238
|
t.puts msg
|
226
239
|
end
|
227
|
-
|
240
|
+
request_tc :continue
|
228
241
|
|
229
242
|
when :suspend
|
230
243
|
enter_subsession if ev_args.first != :replay
|
@@ -233,26 +246,26 @@ module DEBUGGER__
|
|
233
246
|
case ev_args.first
|
234
247
|
when :breakpoint
|
235
248
|
bp, i = bp_index ev_args[1]
|
236
|
-
|
249
|
+
clean_bps unless bp
|
250
|
+
@ui.event :suspend_bp, i, bp, @tc.id
|
237
251
|
when :trap
|
238
|
-
@ui.event :suspend_trap, sig = ev_args[1], tc.id
|
252
|
+
@ui.event :suspend_trap, sig = ev_args[1], @tc.id
|
239
253
|
|
240
254
|
if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
|
241
|
-
@ui.puts "#{@intercepted_sigint_cmd.inspect} is
|
255
|
+
@ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
|
242
256
|
@ui.puts "`sigint` command execute it."
|
243
257
|
end
|
244
258
|
else
|
245
|
-
@ui.event :suspended, tc.id
|
259
|
+
@ui.event :suspended, @tc.id
|
246
260
|
end
|
247
261
|
|
248
262
|
if @displays.empty?
|
249
|
-
wait_command_loop
|
263
|
+
wait_command_loop
|
250
264
|
else
|
251
|
-
|
265
|
+
request_tc [:eval, :display, @displays]
|
252
266
|
end
|
253
|
-
|
254
267
|
when :result
|
255
|
-
raise "[BUG] not in subsession"
|
268
|
+
raise "[BUG] not in subsession" if @subsession_stack.empty?
|
256
269
|
|
257
270
|
case ev_args.first
|
258
271
|
when :try_display
|
@@ -281,14 +294,14 @@ module DEBUGGER__
|
|
281
294
|
# ignore
|
282
295
|
end
|
283
296
|
|
284
|
-
wait_command_loop
|
297
|
+
wait_command_loop
|
285
298
|
|
286
299
|
when :dap_result
|
287
300
|
dap_event ev_args # server.rb
|
288
|
-
wait_command_loop
|
301
|
+
wait_command_loop
|
289
302
|
when :cdp_result
|
290
303
|
cdp_event ev_args
|
291
|
-
wait_command_loop
|
304
|
+
wait_command_loop
|
292
305
|
end
|
293
306
|
end
|
294
307
|
|
@@ -321,9 +334,7 @@ module DEBUGGER__
|
|
321
334
|
"DEBUGGER__::SESSION"
|
322
335
|
end
|
323
336
|
|
324
|
-
def wait_command_loop
|
325
|
-
@tc = tc
|
326
|
-
|
337
|
+
def wait_command_loop
|
327
338
|
loop do
|
328
339
|
case wait_command
|
329
340
|
when :retry
|
@@ -364,7 +375,7 @@ module DEBUGGER__
|
|
364
375
|
@ui.puts "(rdbg:#{@preset_command.source}) #{line}"
|
365
376
|
end
|
366
377
|
else
|
367
|
-
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['
|
378
|
+
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
368
379
|
line = @ui.readline prompt
|
369
380
|
end
|
370
381
|
|
@@ -418,10 +429,15 @@ module DEBUGGER__
|
|
418
429
|
# * `fin[ish]`
|
419
430
|
# * Finish this frame. Resume the program until the current frame is finished.
|
420
431
|
# * `fin[ish] <n>`
|
421
|
-
# * Finish frames
|
432
|
+
# * Finish `<n>`th frames.
|
422
433
|
when 'fin', 'finish'
|
423
434
|
cancel_auto_continue
|
424
435
|
check_postmortem
|
436
|
+
|
437
|
+
if arg&.to_i == 0
|
438
|
+
raise 'finish command with 0 does not make sense.'
|
439
|
+
end
|
440
|
+
|
425
441
|
step_command :finish, arg
|
426
442
|
|
427
443
|
# * `c[ontinue]`
|
@@ -447,7 +463,7 @@ module DEBUGGER__
|
|
447
463
|
leave_subsession nil
|
448
464
|
|
449
465
|
# * `kill`
|
450
|
-
# * Stop the debuggee process with `
|
466
|
+
# * Stop the debuggee process with `Kernel#exit!`.
|
451
467
|
when 'kill'
|
452
468
|
if ask 'Really kill?'
|
453
469
|
exit! (arg || 1).to_i
|
@@ -461,7 +477,7 @@ module DEBUGGER__
|
|
461
477
|
exit! (arg || 1).to_i
|
462
478
|
|
463
479
|
# * `sigint`
|
464
|
-
# * Execute SIGINT handler
|
480
|
+
# * Execute SIGINT handler registered by the debuggee.
|
465
481
|
# * Note that this command should be used just after stop by `SIGINT`.
|
466
482
|
when 'sigint'
|
467
483
|
begin
|
@@ -500,6 +516,8 @@ module DEBUGGER__
|
|
500
516
|
# * break and run `<command>` before stopping.
|
501
517
|
# * `b[reak] ... do: <command>`
|
502
518
|
# * break and run `<command>`, and continue.
|
519
|
+
# * `b[reak] ... path: <path>`
|
520
|
+
# * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
|
503
521
|
# * `b[reak] if: <expr>`
|
504
522
|
# * break if: `<expr>` is true at any lines.
|
505
523
|
# * Note that this feature is super slow.
|
@@ -520,34 +538,16 @@ module DEBUGGER__
|
|
520
538
|
end
|
521
539
|
end
|
522
540
|
|
523
|
-
# skip
|
524
|
-
when 'bv'
|
525
|
-
check_postmortem
|
526
|
-
require 'json'
|
527
|
-
|
528
|
-
h = Hash.new{|h, k| h[k] = []}
|
529
|
-
@bps.each{|key, bp|
|
530
|
-
if LineBreakpoint === bp
|
531
|
-
h[bp.path] << {lnum: bp.line}
|
532
|
-
end
|
533
|
-
}
|
534
|
-
if h.empty?
|
535
|
-
# TODO: clean?
|
536
|
-
else
|
537
|
-
open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
|
538
|
-
end
|
539
|
-
|
540
|
-
vimsrc = File.join(__dir__, 'bp.vim')
|
541
|
-
system("vim -R -S #{vimsrc} #{@tc.location.path}")
|
542
|
-
|
543
|
-
if File.exist?(".rdb_breakpoints.json")
|
544
|
-
pp JSON.load(File.read(".rdb_breakpoints.json"))
|
545
|
-
end
|
546
|
-
|
547
|
-
return :retry
|
548
|
-
|
549
541
|
# * `catch <Error>`
|
550
542
|
# * Set breakpoint on raising `<Error>`.
|
543
|
+
# * `catch ... if: <expr>`
|
544
|
+
# * stops only if `<expr>` is true as well.
|
545
|
+
# * `catch ... pre: <command>`
|
546
|
+
# * runs `<command>` before stopping.
|
547
|
+
# * `catch ... do: <command>`
|
548
|
+
# * stops and run `<command>`, and continue.
|
549
|
+
# * `catch ... path: <path>`
|
550
|
+
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
|
551
551
|
when 'catch'
|
552
552
|
check_postmortem
|
553
553
|
|
@@ -562,11 +562,19 @@ module DEBUGGER__
|
|
562
562
|
# * `watch @ivar`
|
563
563
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
564
564
|
# * Note that this feature is super slow.
|
565
|
+
# * `watch ... if: <expr>`
|
566
|
+
# * stops only if `<expr>` is true as well.
|
567
|
+
# * `watch ... pre: <command>`
|
568
|
+
# * runs `<command>` before stopping.
|
569
|
+
# * `watch ... do: <command>`
|
570
|
+
# * stops and run `<command>`, and continue.
|
571
|
+
# * `watch ... path: <path>`
|
572
|
+
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
|
565
573
|
when 'wat', 'watch'
|
566
574
|
check_postmortem
|
567
575
|
|
568
576
|
if arg && arg.match?(/\A@\w+/)
|
569
|
-
|
577
|
+
repl_add_watch_breakpoint(arg)
|
570
578
|
else
|
571
579
|
show_bps
|
572
580
|
return :retry
|
@@ -607,15 +615,15 @@ module DEBUGGER__
|
|
607
615
|
when 'bt', 'backtrace'
|
608
616
|
case arg
|
609
617
|
when /\A(\d+)\z/
|
610
|
-
|
618
|
+
request_tc [:show, :backtrace, arg.to_i, nil]
|
611
619
|
when /\A\/(.*)\/\z/
|
612
620
|
pattern = $1
|
613
|
-
|
621
|
+
request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
|
614
622
|
when /\A(\d+)\s+\/(.*)\/\z/
|
615
623
|
max, pattern = $1, $2
|
616
|
-
|
624
|
+
request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
617
625
|
else
|
618
|
-
|
626
|
+
request_tc [:show, :backtrace, nil, nil]
|
619
627
|
end
|
620
628
|
|
621
629
|
# * `l[ist]`
|
@@ -628,13 +636,13 @@ module DEBUGGER__
|
|
628
636
|
when 'l', 'list'
|
629
637
|
case arg ? arg.strip : nil
|
630
638
|
when /\A(\d+)\z/
|
631
|
-
|
639
|
+
request_tc [:show, :list, {start_line: arg.to_i - 1}]
|
632
640
|
when /\A-\z/
|
633
|
-
|
641
|
+
request_tc [:show, :list, {dir: -1}]
|
634
642
|
when /\A(\d+)-(\d+)\z/
|
635
|
-
|
643
|
+
request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
|
636
644
|
when nil
|
637
|
-
|
645
|
+
request_tc [:show, :list]
|
638
646
|
else
|
639
647
|
@ui.puts "Can not handle list argument: #{arg}"
|
640
648
|
return :retry
|
@@ -658,7 +666,7 @@ module DEBUGGER__
|
|
658
666
|
return :retry
|
659
667
|
end
|
660
668
|
|
661
|
-
|
669
|
+
request_tc [:show, :edit, arg]
|
662
670
|
|
663
671
|
# * `i[nfo]`
|
664
672
|
# * Show information about current frame (local/instance variables and defined constants).
|
@@ -671,8 +679,8 @@ module DEBUGGER__
|
|
671
679
|
# * Show information about accessible constants except toplevel constants.
|
672
680
|
# * `i[nfo] g[lobal[s]]`
|
673
681
|
# * Show information about global variables
|
674
|
-
# * `i[nfo] ...
|
675
|
-
# * Filter the output with
|
682
|
+
# * `i[nfo] ... /regexp/`
|
683
|
+
# * Filter the output with `/regexp/`.
|
676
684
|
# * `i[nfo] th[read[s]]`
|
677
685
|
# * Show all threads (same as `th[read]`).
|
678
686
|
when 'i', 'info'
|
@@ -685,15 +693,15 @@ module DEBUGGER__
|
|
685
693
|
|
686
694
|
case sub
|
687
695
|
when nil
|
688
|
-
|
696
|
+
request_tc [:show, :default, pat] # something useful
|
689
697
|
when 'l', /^locals?/
|
690
|
-
|
698
|
+
request_tc [:show, :locals, pat]
|
691
699
|
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
692
|
-
|
700
|
+
request_tc [:show, :ivars, pat]
|
693
701
|
when 'c', /^consts?/i, /^constants?/i
|
694
|
-
|
702
|
+
request_tc [:show, :consts, pat]
|
695
703
|
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
696
|
-
|
704
|
+
request_tc [:show, :globals, pat]
|
697
705
|
when 'th', /threads?/
|
698
706
|
thread_list
|
699
707
|
return :retry
|
@@ -709,7 +717,7 @@ module DEBUGGER__
|
|
709
717
|
# * Show you available methods and instance variables of the given object.
|
710
718
|
# * If the object is a class/module, it also lists its constants.
|
711
719
|
when 'outline', 'o', 'ls'
|
712
|
-
|
720
|
+
request_tc [:show, :outline, arg]
|
713
721
|
|
714
722
|
# * `display`
|
715
723
|
# * Show display setting.
|
@@ -718,9 +726,9 @@ module DEBUGGER__
|
|
718
726
|
when 'display'
|
719
727
|
if arg && !arg.empty?
|
720
728
|
@displays << arg
|
721
|
-
|
729
|
+
request_tc [:eval, :try_display, @displays]
|
722
730
|
else
|
723
|
-
|
731
|
+
request_tc [:eval, :display, @displays]
|
724
732
|
end
|
725
733
|
|
726
734
|
# * `undisplay`
|
@@ -733,7 +741,7 @@ module DEBUGGER__
|
|
733
741
|
if @displays[n = $1.to_i]
|
734
742
|
@displays.delete_at n
|
735
743
|
end
|
736
|
-
|
744
|
+
request_tc [:eval, :display, @displays]
|
737
745
|
when nil
|
738
746
|
if ask "clear all?", 'N'
|
739
747
|
@displays.clear
|
@@ -748,29 +756,29 @@ module DEBUGGER__
|
|
748
756
|
# * `f[rame] <framenum>`
|
749
757
|
# * Specify a current frame. Evaluation are run on specified frame.
|
750
758
|
when 'frame', 'f'
|
751
|
-
|
759
|
+
request_tc [:frame, :set, arg]
|
752
760
|
|
753
761
|
# * `up`
|
754
762
|
# * Specify the upper frame.
|
755
763
|
when 'up'
|
756
|
-
|
764
|
+
request_tc [:frame, :up]
|
757
765
|
|
758
766
|
# * `down`
|
759
767
|
# * Specify the lower frame.
|
760
768
|
when 'down'
|
761
|
-
|
769
|
+
request_tc [:frame, :down]
|
762
770
|
|
763
771
|
### Evaluate
|
764
772
|
|
765
773
|
# * `p <expr>`
|
766
774
|
# * Evaluate like `p <expr>` on the current frame.
|
767
775
|
when 'p'
|
768
|
-
|
776
|
+
request_tc [:eval, :p, arg.to_s]
|
769
777
|
|
770
778
|
# * `pp <expr>`
|
771
779
|
# * Evaluate like `pp <expr>` on the current frame.
|
772
780
|
when 'pp'
|
773
|
-
|
781
|
+
request_tc [:eval, :pp, arg.to_s]
|
774
782
|
|
775
783
|
# * `eval <expr>`
|
776
784
|
# * Evaluate `<expr>` on the current frame.
|
@@ -780,7 +788,7 @@ module DEBUGGER__
|
|
780
788
|
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
781
789
|
return :retry
|
782
790
|
else
|
783
|
-
|
791
|
+
request_tc [:eval, :call, arg]
|
784
792
|
end
|
785
793
|
|
786
794
|
# * `irb`
|
@@ -790,7 +798,7 @@ module DEBUGGER__
|
|
790
798
|
@ui.puts "not supported on the remote console."
|
791
799
|
return :retry
|
792
800
|
end
|
793
|
-
|
801
|
+
request_tc [:eval, :irb]
|
794
802
|
|
795
803
|
# don't repeat irb command
|
796
804
|
@repl_prev_line = nil
|
@@ -806,8 +814,8 @@ module DEBUGGER__
|
|
806
814
|
# * Add an exception tracer. It indicates raising exceptions.
|
807
815
|
# * `trace object <expr>`
|
808
816
|
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
809
|
-
# * `trace ...
|
810
|
-
# * Indicates only matched events to
|
817
|
+
# * `trace ... /regexp/`
|
818
|
+
# * Indicates only matched events to `/regexp/`.
|
811
819
|
# * `trace ... into: <file>`
|
812
820
|
# * Save trace information into: `<file>`.
|
813
821
|
# * `trace off <num>`
|
@@ -847,7 +855,7 @@ module DEBUGGER__
|
|
847
855
|
return :retry
|
848
856
|
|
849
857
|
when /\Aobject\s+(.+)/
|
850
|
-
|
858
|
+
request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
851
859
|
|
852
860
|
when /\Aoff\s+(\d+)\z/
|
853
861
|
if t = @tracers.values[$1.to_i]
|
@@ -885,7 +893,7 @@ module DEBUGGER__
|
|
885
893
|
when 'record'
|
886
894
|
case arg
|
887
895
|
when nil, 'on', 'off'
|
888
|
-
|
896
|
+
request_tc [:record, arg&.to_sym]
|
889
897
|
else
|
890
898
|
@ui.puts "unknown command: #{arg}"
|
891
899
|
return :retry
|
@@ -902,7 +910,7 @@ module DEBUGGER__
|
|
902
910
|
when nil, 'list', 'l'
|
903
911
|
thread_list
|
904
912
|
when /(\d+)/
|
905
|
-
|
913
|
+
switch_thread $1.to_i
|
906
914
|
else
|
907
915
|
@ui.puts "unknown thread command: #{arg}"
|
908
916
|
end
|
@@ -950,7 +958,7 @@ module DEBUGGER__
|
|
950
958
|
when 'open'
|
951
959
|
case arg&.downcase
|
952
960
|
when '', nil
|
953
|
-
|
961
|
+
repl_open
|
954
962
|
when 'vscode'
|
955
963
|
repl_open_vscode
|
956
964
|
when /\A(.+):(\d+)\z/
|
@@ -975,16 +983,12 @@ module DEBUGGER__
|
|
975
983
|
# * `h[elp] <command>`
|
976
984
|
# * Show help for the given command.
|
977
985
|
when 'h', 'help', '?'
|
978
|
-
|
979
|
-
show_help arg
|
980
|
-
else
|
981
|
-
@ui.puts DEBUGGER__.help
|
982
|
-
end
|
986
|
+
show_help arg
|
983
987
|
return :retry
|
984
988
|
|
985
989
|
### END
|
986
990
|
else
|
987
|
-
|
991
|
+
request_tc [:eval, :pp, line]
|
988
992
|
=begin
|
989
993
|
@repl_prev_line = nil
|
990
994
|
@ui.puts "unknown command: #{line}"
|
@@ -1016,8 +1020,8 @@ module DEBUGGER__
|
|
1016
1020
|
def repl_open_setup
|
1017
1021
|
@tp_thread_begin.disable
|
1018
1022
|
@ui.activate self
|
1019
|
-
if @ui.respond_to?(:reader_thread) && thc =
|
1020
|
-
thc.
|
1023
|
+
if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
|
1024
|
+
thc.mark_as_management
|
1021
1025
|
end
|
1022
1026
|
@tp_thread_begin.enable
|
1023
1027
|
end
|
@@ -1027,21 +1031,21 @@ module DEBUGGER__
|
|
1027
1031
|
repl_open_setup
|
1028
1032
|
end
|
1029
1033
|
|
1030
|
-
def
|
1031
|
-
DEBUGGER__.
|
1034
|
+
def repl_open
|
1035
|
+
DEBUGGER__.open nonstop: true
|
1032
1036
|
repl_open_setup
|
1033
1037
|
end
|
1034
1038
|
|
1035
1039
|
def repl_open_vscode
|
1036
1040
|
CONFIG[:open_frontend] = 'vscode'
|
1037
|
-
|
1041
|
+
repl_open
|
1038
1042
|
end
|
1039
1043
|
|
1040
1044
|
def step_command type, arg
|
1041
1045
|
case arg
|
1042
1046
|
when nil, /\A\d+\z/
|
1043
1047
|
if type == :in && @tc.recorder&.replaying?
|
1044
|
-
|
1048
|
+
request_tc [:step, type, arg&.to_i]
|
1045
1049
|
else
|
1046
1050
|
leave_subsession [:step, type, arg&.to_i]
|
1047
1051
|
end
|
@@ -1050,7 +1054,7 @@ module DEBUGGER__
|
|
1050
1054
|
@ui.puts "only `step #{arg}` is supported."
|
1051
1055
|
:retry
|
1052
1056
|
else
|
1053
|
-
|
1057
|
+
request_tc [:step, arg.to_sym]
|
1054
1058
|
end
|
1055
1059
|
else
|
1056
1060
|
@ui.puts "Unknown option: #{arg}"
|
@@ -1060,11 +1064,18 @@ module DEBUGGER__
|
|
1060
1064
|
|
1061
1065
|
def config_show key
|
1062
1066
|
key = key.to_sym
|
1063
|
-
|
1067
|
+
config_detail = CONFIG_SET[key]
|
1068
|
+
|
1069
|
+
if config_detail
|
1064
1070
|
v = CONFIG[key]
|
1065
|
-
kv = "#{key} = #{v.
|
1066
|
-
desc =
|
1067
|
-
|
1071
|
+
kv = "#{key} = #{v.inspect}"
|
1072
|
+
desc = config_detail[1]
|
1073
|
+
|
1074
|
+
if config_default = config_detail[3]
|
1075
|
+
desc += " (default: #{config_default})"
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
line = "%-34s \# %s" % [kv, desc]
|
1068
1079
|
if line.size > SESSION.width
|
1069
1080
|
@ui.puts "\# #{desc}\n#{kv}"
|
1070
1081
|
else
|
@@ -1114,7 +1125,7 @@ module DEBUGGER__
|
|
1114
1125
|
config_set $1, $2, append: true
|
1115
1126
|
|
1116
1127
|
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
1117
|
-
config_set $1, $2
|
1128
|
+
config_set $1, $2, append: true
|
1118
1129
|
|
1119
1130
|
when /\A(\w+)\z/
|
1120
1131
|
config_show $1
|
@@ -1131,16 +1142,50 @@ module DEBUGGER__
|
|
1131
1142
|
end
|
1132
1143
|
end
|
1133
1144
|
|
1134
|
-
def show_help arg
|
1135
|
-
DEBUGGER__.
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1145
|
+
def show_help arg = nil
|
1146
|
+
instructions = (DEBUGGER__.commands.keys + DEBUGGER__.commands.values).uniq
|
1147
|
+
print_instructions = proc do |desc|
|
1148
|
+
desc.split("\n").each do |line|
|
1149
|
+
next if line.start_with?(" ") # workaround for step back
|
1150
|
+
formatted_line = line.gsub(/[\[\]\*]/, "").strip
|
1151
|
+
instructions.each do |inst|
|
1152
|
+
if formatted_line.start_with?("`#{inst}")
|
1153
|
+
desc.sub!(line, colorize(line, [:CYAN, :BOLD]))
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
@ui.puts desc
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
print_category = proc do |cat|
|
1161
|
+
@ui.puts "\n"
|
1162
|
+
@ui.puts colorize("### #{cat}", [:GREEN, :BOLD])
|
1163
|
+
@ui.puts "\n"
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
DEBUGGER__.helps.each { |cat, cs|
|
1167
|
+
# categories
|
1168
|
+
if arg.nil?
|
1169
|
+
print_category.call(cat)
|
1170
|
+
else
|
1171
|
+
cs.each { |ws, _|
|
1172
|
+
if ws.include?(arg)
|
1173
|
+
print_category.call(cat)
|
1174
|
+
break
|
1175
|
+
end
|
1176
|
+
}
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
# instructions
|
1180
|
+
cs.each { |ws, desc|
|
1181
|
+
if arg.nil? || ws.include?(arg)
|
1182
|
+
print_instructions.call(desc.dup)
|
1183
|
+
return if arg
|
1140
1184
|
end
|
1141
1185
|
}
|
1142
1186
|
}
|
1143
|
-
|
1187
|
+
|
1188
|
+
@ui.puts "not found: #{arg}" if arg
|
1144
1189
|
end
|
1145
1190
|
|
1146
1191
|
def ask msg, default = 'Y'
|
@@ -1195,14 +1240,23 @@ module DEBUGGER__
|
|
1195
1240
|
}
|
1196
1241
|
end
|
1197
1242
|
|
1243
|
+
def clean_bps
|
1244
|
+
@bps.delete_if{|_k, bp|
|
1245
|
+
bp.deleted?
|
1246
|
+
}
|
1247
|
+
end
|
1248
|
+
|
1198
1249
|
def add_bp bp
|
1199
1250
|
# don't repeat commands that add breakpoints
|
1200
1251
|
@repl_prev_line = nil
|
1201
1252
|
|
1202
1253
|
if @bps.has_key? bp.key
|
1203
|
-
|
1254
|
+
if bp.duplicable?
|
1255
|
+
bp
|
1256
|
+
else
|
1204
1257
|
@ui.puts "duplicated breakpoint: #{bp}"
|
1205
1258
|
bp.disable
|
1259
|
+
nil
|
1206
1260
|
end
|
1207
1261
|
else
|
1208
1262
|
@bps[bp.key] = bp
|
@@ -1225,7 +1279,7 @@ module DEBUGGER__
|
|
1225
1279
|
end
|
1226
1280
|
end
|
1227
1281
|
|
1228
|
-
BREAK_KEYWORDS = %w(if: do: pre:).freeze
|
1282
|
+
BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
|
1229
1283
|
|
1230
1284
|
def parse_break arg
|
1231
1285
|
mode = :sig
|
@@ -1238,13 +1292,20 @@ module DEBUGGER__
|
|
1238
1292
|
end
|
1239
1293
|
}
|
1240
1294
|
expr.default_proc = nil
|
1241
|
-
expr.transform_values{|v| v.join(' ')}
|
1295
|
+
expr = expr.transform_values{|v| v.join(' ')}
|
1296
|
+
|
1297
|
+
if (path = expr[:path]) && path =~ /\A\/(.*)\/\z/
|
1298
|
+
expr[:path] = Regexp.compile($1)
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
expr
|
1242
1302
|
end
|
1243
1303
|
|
1244
1304
|
def repl_add_breakpoint arg
|
1245
1305
|
expr = parse_break arg.strip
|
1246
1306
|
cond = expr[:if]
|
1247
1307
|
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1308
|
+
path = expr[:path]
|
1248
1309
|
|
1249
1310
|
case expr[:sig]
|
1250
1311
|
when /\A(\d+)\z/
|
@@ -1252,10 +1313,10 @@ module DEBUGGER__
|
|
1252
1313
|
when /\A(.+)[:\s+](\d+)\z/
|
1253
1314
|
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
1254
1315
|
when /\A(.+)([\.\#])(.+)\z/
|
1255
|
-
|
1316
|
+
request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
|
1256
1317
|
return :noretry
|
1257
1318
|
when nil
|
1258
|
-
add_check_breakpoint cond
|
1319
|
+
add_check_breakpoint cond, path, cmd
|
1259
1320
|
else
|
1260
1321
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
1261
1322
|
@ui.puts
|
@@ -1267,18 +1328,28 @@ module DEBUGGER__
|
|
1267
1328
|
expr = parse_break arg.strip
|
1268
1329
|
cond = expr[:if]
|
1269
1330
|
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1331
|
+
path = expr[:path]
|
1270
1332
|
|
1271
|
-
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
|
1333
|
+
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
|
1272
1334
|
add_bp bp
|
1273
1335
|
end
|
1274
1336
|
|
1275
|
-
def
|
1276
|
-
|
1337
|
+
def repl_add_watch_breakpoint arg
|
1338
|
+
expr = parse_break arg.strip
|
1339
|
+
cond = expr[:if]
|
1340
|
+
cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1341
|
+
path = Regexp.compile(expr[:path]) if expr[:path]
|
1342
|
+
|
1343
|
+
request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
def add_catch_breakpoint pat, cond: nil
|
1347
|
+
bp = CatchBreakpoint.new(pat, cond: cond)
|
1277
1348
|
add_bp bp
|
1278
1349
|
end
|
1279
1350
|
|
1280
|
-
def add_check_breakpoint
|
1281
|
-
bp = CheckBreakpoint.new(
|
1351
|
+
def add_check_breakpoint cond, path, command
|
1352
|
+
bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
|
1282
1353
|
add_bp bp
|
1283
1354
|
end
|
1284
1355
|
|
@@ -1291,6 +1362,39 @@ module DEBUGGER__
|
|
1291
1362
|
@ui.puts e.message
|
1292
1363
|
end
|
1293
1364
|
|
1365
|
+
def clear_breakpoints(&condition)
|
1366
|
+
@bps.delete_if do |k, bp|
|
1367
|
+
if condition.call(k, bp)
|
1368
|
+
bp.delete
|
1369
|
+
true
|
1370
|
+
end
|
1371
|
+
end
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
def clear_line_breakpoints path
|
1375
|
+
path = resolve_path(path)
|
1376
|
+
clear_breakpoints do |k, bp|
|
1377
|
+
bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
|
1378
|
+
end
|
1379
|
+
rescue Errno::ENOENT
|
1380
|
+
# just ignore
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
def clear_catch_breakpoints *exception_names
|
1384
|
+
clear_breakpoints do |k, bp|
|
1385
|
+
bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1])
|
1386
|
+
end
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
def clear_all_breakpoints
|
1390
|
+
clear_breakpoints{true}
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
def add_iseq_breakpoint iseq, **kw
|
1394
|
+
bp = ISeqBreakpoint.new(iseq, [:line], **kw)
|
1395
|
+
add_bp bp
|
1396
|
+
end
|
1397
|
+
|
1294
1398
|
# tracers
|
1295
1399
|
|
1296
1400
|
def add_tracer tracer
|
@@ -1344,7 +1448,7 @@ module DEBUGGER__
|
|
1344
1448
|
thcs
|
1345
1449
|
end
|
1346
1450
|
|
1347
|
-
def
|
1451
|
+
def switch_thread n
|
1348
1452
|
thcs, _unmanaged_ths = update_thread_list
|
1349
1453
|
|
1350
1454
|
if tc = thcs[n]
|
@@ -1365,7 +1469,7 @@ module DEBUGGER__
|
|
1365
1469
|
if tc = prev_clients[th]
|
1366
1470
|
@th_clients[th] = tc
|
1367
1471
|
else
|
1368
|
-
|
1472
|
+
create_thread_client(th)
|
1369
1473
|
end
|
1370
1474
|
}
|
1371
1475
|
end
|
@@ -1374,17 +1478,17 @@ module DEBUGGER__
|
|
1374
1478
|
if @th_clients.has_key? th
|
1375
1479
|
# TODO: NG?
|
1376
1480
|
else
|
1377
|
-
|
1481
|
+
create_thread_client th
|
1378
1482
|
end
|
1379
1483
|
end
|
1380
1484
|
|
1381
|
-
private def
|
1485
|
+
private def create_thread_client th
|
1382
1486
|
# TODO: Ractor support
|
1383
1487
|
raise "Only session_server can create thread_client" unless Thread.current == @session_server
|
1384
1488
|
@th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
|
1385
1489
|
end
|
1386
1490
|
|
1387
|
-
private def ask_thread_client th
|
1491
|
+
private def ask_thread_client th
|
1388
1492
|
# TODO: Ractor support
|
1389
1493
|
q2 = Queue.new
|
1390
1494
|
# tc, output, ev, @internal_info, *ev_args = evt
|
@@ -1395,30 +1499,18 @@ module DEBUGGER__
|
|
1395
1499
|
end
|
1396
1500
|
|
1397
1501
|
# can be called by other threads
|
1398
|
-
def
|
1502
|
+
def get_thread_client th = Thread.current
|
1399
1503
|
if @th_clients.has_key? th
|
1400
1504
|
@th_clients[th]
|
1401
1505
|
else
|
1402
1506
|
if Thread.current == @session_server
|
1403
|
-
|
1507
|
+
create_thread_client th
|
1404
1508
|
else
|
1405
1509
|
ask_thread_client th
|
1406
1510
|
end
|
1407
1511
|
end
|
1408
1512
|
end
|
1409
1513
|
|
1410
|
-
private def thread_stopper
|
1411
|
-
@thread_stopper ||= TracePoint.new(:line) do
|
1412
|
-
# run on each thread
|
1413
|
-
tc = ThreadClient.current
|
1414
|
-
next if tc.management?
|
1415
|
-
next unless tc.running?
|
1416
|
-
next if tc == @tc
|
1417
|
-
|
1418
|
-
tc.on_pause
|
1419
|
-
end
|
1420
|
-
end
|
1421
|
-
|
1422
1514
|
private def running_thread_clients_count
|
1423
1515
|
@th_clients.count{|th, tc|
|
1424
1516
|
next if tc.management?
|
@@ -1435,15 +1527,27 @@ module DEBUGGER__
|
|
1435
1527
|
}.compact
|
1436
1528
|
end
|
1437
1529
|
|
1530
|
+
private def thread_stopper
|
1531
|
+
TracePoint.new(:line) do
|
1532
|
+
# run on each thread
|
1533
|
+
tc = ThreadClient.current
|
1534
|
+
next if tc.management?
|
1535
|
+
next unless tc.running?
|
1536
|
+
next if tc == @tc
|
1537
|
+
|
1538
|
+
tc.on_pause
|
1539
|
+
end
|
1540
|
+
end
|
1541
|
+
|
1438
1542
|
private def stop_all_threads
|
1439
1543
|
return if running_thread_clients_count == 0
|
1440
1544
|
|
1441
|
-
stopper = thread_stopper
|
1545
|
+
stopper = @thread_stopper
|
1442
1546
|
stopper.enable unless stopper.enabled?
|
1443
1547
|
end
|
1444
1548
|
|
1445
1549
|
private def restart_all_threads
|
1446
|
-
stopper = thread_stopper
|
1550
|
+
stopper = @thread_stopper
|
1447
1551
|
stopper.disable if stopper.enabled?
|
1448
1552
|
|
1449
1553
|
waiting_thread_clients.each{|tc|
|
@@ -1453,42 +1557,66 @@ module DEBUGGER__
|
|
1453
1557
|
end
|
1454
1558
|
|
1455
1559
|
private def enter_subsession
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1560
|
+
if !@subsession_stack.empty?
|
1561
|
+
DEBUGGER__.info "Enter subsession (nested #{@subsession_stack.size})"
|
1562
|
+
else
|
1563
|
+
DEBUGGER__.info "Enter subsession"
|
1564
|
+
stop_all_threads
|
1565
|
+
@process_group.lock
|
1566
|
+
end
|
1567
|
+
|
1568
|
+
@subsession_stack << true
|
1461
1569
|
end
|
1462
1570
|
|
1463
1571
|
private def leave_subsession type
|
1464
|
-
|
1465
|
-
@
|
1466
|
-
|
1467
|
-
|
1572
|
+
raise '[BUG] leave_subsession: not entered' if @subsession_stack.empty?
|
1573
|
+
@subsession_stack.pop
|
1574
|
+
|
1575
|
+
if @subsession_stack.empty?
|
1576
|
+
DEBUGGER__.info "Leave subsession"
|
1577
|
+
@process_group.unlock
|
1578
|
+
restart_all_threads
|
1579
|
+
else
|
1580
|
+
DEBUGGER__.info "Leave subsession (nested #{@subsession_stack.size})"
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
request_tc type if type
|
1468
1584
|
@tc = nil
|
1469
|
-
@subsession = false
|
1470
1585
|
rescue Exception => e
|
1471
|
-
STDERR.puts [e, e.backtrace].
|
1586
|
+
STDERR.puts PP.pp([e, e.backtrace], ''.dup)
|
1472
1587
|
raise
|
1473
1588
|
end
|
1474
1589
|
|
1475
1590
|
def in_subsession?
|
1476
|
-
|
1591
|
+
!@subsession_stack.empty?
|
1477
1592
|
end
|
1478
1593
|
|
1479
1594
|
## event
|
1480
1595
|
|
1481
1596
|
def on_load iseq, src
|
1482
1597
|
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
1483
|
-
|
1598
|
+
|
1599
|
+
file_path, reloaded = @sr.add(iseq, src)
|
1600
|
+
@ui.event :load, file_path, reloaded
|
1484
1601
|
|
1485
1602
|
pending_line_breakpoints = @bps.find_all do |key, bp|
|
1486
1603
|
LineBreakpoint === bp && !bp.iseq
|
1487
1604
|
end
|
1488
1605
|
|
1489
1606
|
pending_line_breakpoints.each do |_key, bp|
|
1490
|
-
if bp.path
|
1491
|
-
bp.try_activate
|
1607
|
+
if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
|
1608
|
+
bp.try_activate iseq
|
1609
|
+
end
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
if reloaded
|
1613
|
+
@bps.find_all do |key, bp|
|
1614
|
+
LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
|
1615
|
+
end.each do |_key, bp|
|
1616
|
+
@bps.delete bp.key # to allow duplicate
|
1617
|
+
if nbp = LineBreakpoint.copy(bp, iseq)
|
1618
|
+
add_bp nbp
|
1619
|
+
end
|
1492
1620
|
end
|
1493
1621
|
end
|
1494
1622
|
end
|
@@ -1515,7 +1643,7 @@ module DEBUGGER__
|
|
1515
1643
|
b = tp.binding
|
1516
1644
|
if var_name = b.local_variables.first
|
1517
1645
|
mid = b.local_variable_get(var_name)
|
1518
|
-
|
1646
|
+
resolved = true
|
1519
1647
|
|
1520
1648
|
@bps.each{|k, bp|
|
1521
1649
|
case bp
|
@@ -1526,15 +1654,53 @@ module DEBUGGER__
|
|
1526
1654
|
end
|
1527
1655
|
end
|
1528
1656
|
|
1529
|
-
|
1657
|
+
resolved = false if !bp.enabled?
|
1530
1658
|
end
|
1531
1659
|
}
|
1532
|
-
|
1533
|
-
|
1660
|
+
|
1661
|
+
if resolved
|
1662
|
+
Session.deactivate_method_added_trackers
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
case mid
|
1666
|
+
when :method_added, :singleton_method_added
|
1667
|
+
Session.create_method_added_tracker(tp.self, mid)
|
1668
|
+
Session.create_method_added_tracker unless resolved
|
1534
1669
|
end
|
1535
1670
|
end
|
1536
1671
|
end
|
1537
1672
|
|
1673
|
+
class ::Module
|
1674
|
+
undef method_added
|
1675
|
+
def method_added mid; end
|
1676
|
+
def singleton_method_added mid; end
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
|
1680
|
+
m = mod.__send__(method_accessor, method_added_id)
|
1681
|
+
METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
|
1682
|
+
SESSION.method_added tp
|
1683
|
+
end
|
1684
|
+
end
|
1685
|
+
|
1686
|
+
def self.activate_method_added_trackers
|
1687
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1688
|
+
tp.enable(target: m) unless tp.enabled?
|
1689
|
+
rescue ArgumentError
|
1690
|
+
DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
|
1691
|
+
end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
def self.deactivate_method_added_trackers
|
1695
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1696
|
+
tp.disable if tp.enabled?
|
1697
|
+
end
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
METHOD_ADDED_TRACKERS = Hash.new
|
1701
|
+
create_method_added_tracker Module, :method_added, :instance_method
|
1702
|
+
create_method_added_tracker Module, :singleton_method_added, :instance_method
|
1703
|
+
|
1538
1704
|
def width
|
1539
1705
|
@ui.width
|
1540
1706
|
end
|
@@ -1550,7 +1716,7 @@ module DEBUGGER__
|
|
1550
1716
|
|
1551
1717
|
frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
|
1552
1718
|
@postmortem = true
|
1553
|
-
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
1719
|
+
ThreadClient.current.suspend :postmortem, postmortem_frames: frames, postmortem_exc: exc
|
1554
1720
|
ensure
|
1555
1721
|
@postmortem = false
|
1556
1722
|
end
|
@@ -1810,6 +1976,9 @@ module DEBUGGER__
|
|
1810
1976
|
puts "\nStop by #{args.first}"
|
1811
1977
|
end
|
1812
1978
|
end
|
1979
|
+
|
1980
|
+
def flush
|
1981
|
+
end
|
1813
1982
|
end
|
1814
1983
|
|
1815
1984
|
# manual configuration methods
|
@@ -1822,11 +1991,11 @@ module DEBUGGER__
|
|
1822
1991
|
::DEBUGGER__::SESSION.add_catch_breakpoint pat
|
1823
1992
|
end
|
1824
1993
|
|
1825
|
-
# String for
|
1994
|
+
# String for requiring location
|
1826
1995
|
# nil for -r
|
1827
1996
|
def self.require_location
|
1828
1997
|
locs = caller_locations
|
1829
|
-
dir_prefix = /#{__dir__}/
|
1998
|
+
dir_prefix = /#{Regexp.escape(__dir__)}/
|
1830
1999
|
|
1831
2000
|
locs.each do |loc|
|
1832
2001
|
case loc.absolute_path
|
@@ -1846,7 +2015,7 @@ module DEBUGGER__
|
|
1846
2015
|
|
1847
2016
|
unless defined? SESSION
|
1848
2017
|
require_relative 'local'
|
1849
|
-
initialize_session UI_LocalConsole.new
|
2018
|
+
initialize_session{ UI_LocalConsole.new }
|
1850
2019
|
end
|
1851
2020
|
|
1852
2021
|
setup_initial_suspend unless nonstop
|
@@ -1854,8 +2023,9 @@ module DEBUGGER__
|
|
1854
2023
|
|
1855
2024
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1856
2025
|
CONFIG.set_config(**kw)
|
2026
|
+
require_relative 'server'
|
1857
2027
|
|
1858
|
-
if port || CONFIG[:open_frontend] == 'chrome'
|
2028
|
+
if port || CONFIG[:open_frontend] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
|
1859
2029
|
open_tcp host: host, port: (port || 0), nonstop: nonstop
|
1860
2030
|
else
|
1861
2031
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
@@ -1869,7 +2039,7 @@ module DEBUGGER__
|
|
1869
2039
|
if defined? SESSION
|
1870
2040
|
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
1871
2041
|
else
|
1872
|
-
initialize_session UI_TcpServer.new(host: host, port: port)
|
2042
|
+
initialize_session{ UI_TcpServer.new(host: host, port: port) }
|
1873
2043
|
end
|
1874
2044
|
|
1875
2045
|
setup_initial_suspend unless nonstop
|
@@ -1882,7 +2052,7 @@ module DEBUGGER__
|
|
1882
2052
|
if defined? SESSION
|
1883
2053
|
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1884
2054
|
else
|
1885
|
-
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
2055
|
+
initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
|
1886
2056
|
end
|
1887
2057
|
|
1888
2058
|
setup_initial_suspend unless nonstop
|
@@ -1909,9 +2079,10 @@ module DEBUGGER__
|
|
1909
2079
|
end
|
1910
2080
|
|
1911
2081
|
class << self
|
1912
|
-
define_method :initialize_session do |
|
2082
|
+
define_method :initialize_session do |&init_ui|
|
1913
2083
|
DEBUGGER__.info "Session start (pid: #{Process.pid})"
|
1914
|
-
::DEBUGGER__.const_set(:SESSION, Session.new
|
2084
|
+
::DEBUGGER__.const_set(:SESSION, Session.new)
|
2085
|
+
SESSION.activate init_ui.call
|
1915
2086
|
load_rc
|
1916
2087
|
end
|
1917
2088
|
end
|
@@ -1943,30 +2114,53 @@ module DEBUGGER__
|
|
1943
2114
|
end
|
1944
2115
|
end
|
1945
2116
|
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
def singleton_method_added mid; end
|
1950
|
-
end
|
2117
|
+
# Inspector
|
2118
|
+
|
2119
|
+
SHORT_INSPECT_LENGTH = 40
|
1951
2120
|
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
2121
|
+
class LimitedPP
|
2122
|
+
def self.pp(obj, max=80)
|
2123
|
+
out = self.new(max)
|
2124
|
+
catch out do
|
2125
|
+
PP.singleline_pp(obj, out)
|
2126
|
+
end
|
2127
|
+
out.buf
|
1957
2128
|
end
|
1958
|
-
end
|
1959
2129
|
|
1960
|
-
|
2130
|
+
attr_reader :buf
|
1961
2131
|
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
2132
|
+
def initialize max
|
2133
|
+
@max = max
|
2134
|
+
@cnt = 0
|
2135
|
+
@buf = String.new
|
2136
|
+
end
|
2137
|
+
|
2138
|
+
def <<(other)
|
2139
|
+
@buf << other
|
2140
|
+
|
2141
|
+
if @buf.size >= @max
|
2142
|
+
@buf = @buf[0..@max] + '...'
|
2143
|
+
throw self
|
2144
|
+
end
|
2145
|
+
end
|
2146
|
+
end
|
2147
|
+
|
2148
|
+
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
|
2149
|
+
if short
|
2150
|
+
LimitedPP.pp(obj, max_length)
|
1967
2151
|
else
|
1968
|
-
|
2152
|
+
obj.inspect
|
1969
2153
|
end
|
2154
|
+
rescue NoMethodError => e
|
2155
|
+
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
|
2156
|
+
if obj == (r = e.receiver)
|
2157
|
+
"<\##{klass.name}#{oid} does not have \#inspect>"
|
2158
|
+
else
|
2159
|
+
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
|
2160
|
+
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
|
2161
|
+
end
|
2162
|
+
rescue Exception => e
|
2163
|
+
"<#inspect raises #{e.inspect}>"
|
1970
2164
|
end
|
1971
2165
|
|
1972
2166
|
def self.warn msg
|
@@ -1977,18 +2171,27 @@ module DEBUGGER__
|
|
1977
2171
|
log :INFO, msg
|
1978
2172
|
end
|
1979
2173
|
|
1980
|
-
def self.
|
1981
|
-
@logfile = STDERR unless defined? @logfile
|
1982
|
-
|
2174
|
+
def self.check_loglevel level
|
1983
2175
|
lv = LOG_LEVELS[level]
|
1984
|
-
config_lv = LOG_LEVELS[CONFIG[:log_level]
|
2176
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level]]
|
2177
|
+
lv <= config_lv
|
2178
|
+
end
|
1985
2179
|
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
2180
|
+
def self.debug(&b)
|
2181
|
+
if check_loglevel :DEBUG
|
2182
|
+
log :DEBUG, b.call
|
1989
2183
|
end
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
def self.log level, msg
|
2187
|
+
if check_loglevel level
|
2188
|
+
@logfile = STDERR unless defined? @logfile
|
2189
|
+
|
2190
|
+
if defined? SESSION
|
2191
|
+
pi = SESSION.process_info
|
2192
|
+
process_info = pi ? "[#{pi}]" : nil
|
2193
|
+
end
|
1990
2194
|
|
1991
|
-
if lv <= config_lv
|
1992
2195
|
if level == :WARN
|
1993
2196
|
# :WARN on debugger is general information
|
1994
2197
|
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
@@ -2000,17 +2203,77 @@ module DEBUGGER__
|
|
2000
2203
|
end
|
2001
2204
|
end
|
2002
2205
|
|
2206
|
+
def self.step_in &b
|
2207
|
+
if defined?(SESSION) && SESSION.active?
|
2208
|
+
SESSION.add_iseq_breakpoint RubyVM::InstructionSequence.of(b), oneshot: true
|
2209
|
+
end
|
2210
|
+
|
2211
|
+
yield
|
2212
|
+
end
|
2213
|
+
|
2214
|
+
if File.identical?(__FILE__.upcase, __FILE__.downcase)
|
2215
|
+
# For case insensitive file system (like Windows)
|
2216
|
+
# Note that this check is not enough because case sensitive/insensitive is
|
2217
|
+
# depend on the file system. So this check is only roughly estimation.
|
2218
|
+
|
2219
|
+
def self.compare_path(a, b)
|
2220
|
+
a.downcase == b.downcase
|
2221
|
+
end
|
2222
|
+
else
|
2223
|
+
def self.compare_path(a, b)
|
2224
|
+
a == b
|
2225
|
+
end
|
2226
|
+
end
|
2227
|
+
|
2003
2228
|
module ForkInterceptor
|
2004
|
-
|
2005
|
-
|
2229
|
+
if Process.respond_to? :_fork
|
2230
|
+
def _fork
|
2231
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2232
|
+
|
2233
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2006
2234
|
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2235
|
+
super.tap do |pid|
|
2236
|
+
if pid != 0
|
2237
|
+
# after fork: parent
|
2238
|
+
parent_hook.call pid
|
2239
|
+
else
|
2240
|
+
# after fork: child
|
2241
|
+
child_hook.call
|
2242
|
+
end
|
2243
|
+
end
|
2244
|
+
end
|
2245
|
+
else
|
2246
|
+
def fork(&given_block)
|
2247
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2248
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2249
|
+
|
2250
|
+
if given_block
|
2251
|
+
new_block = proc {
|
2252
|
+
# after fork: child
|
2253
|
+
child_hook.call
|
2254
|
+
given_block.call
|
2255
|
+
}
|
2256
|
+
super(&new_block).tap{|pid| parent_hook.call(pid)}
|
2010
2257
|
else
|
2011
|
-
|
2258
|
+
super.tap do |pid|
|
2259
|
+
if pid
|
2260
|
+
# after fork: parent
|
2261
|
+
parent_hook.call pid
|
2262
|
+
else
|
2263
|
+
# after fork: child
|
2264
|
+
child_hook.call
|
2265
|
+
end
|
2266
|
+
end
|
2012
2267
|
end
|
2013
2268
|
end
|
2269
|
+
end
|
2270
|
+
|
2271
|
+
private def __fork_setup_for_debugger
|
2272
|
+
fork_mode = CONFIG[:fork_mode]
|
2273
|
+
|
2274
|
+
if fork_mode == :both && CONFIG[:parent_on_fork]
|
2275
|
+
fork_mode = :parent
|
2276
|
+
end
|
2014
2277
|
|
2015
2278
|
parent_pid = Process.pid
|
2016
2279
|
|
@@ -2050,26 +2313,7 @@ module DEBUGGER__
|
|
2050
2313
|
}
|
2051
2314
|
end
|
2052
2315
|
|
2053
|
-
|
2054
|
-
new_block = proc {
|
2055
|
-
# after fork: child
|
2056
|
-
child_hook.call
|
2057
|
-
given_block.call
|
2058
|
-
}
|
2059
|
-
pid = super(&new_block)
|
2060
|
-
parent_hook.call(pid)
|
2061
|
-
pid
|
2062
|
-
else
|
2063
|
-
if pid = super
|
2064
|
-
# after fork: parent
|
2065
|
-
parent_hook.call pid
|
2066
|
-
else
|
2067
|
-
# after fork: child
|
2068
|
-
child_hook.call
|
2069
|
-
end
|
2070
|
-
|
2071
|
-
pid
|
2072
|
-
end
|
2316
|
+
return parent_hook, child_hook
|
2073
2317
|
end
|
2074
2318
|
end
|
2075
2319
|
|
@@ -2086,28 +2330,46 @@ module DEBUGGER__
|
|
2086
2330
|
end
|
2087
2331
|
end
|
2088
2332
|
|
2089
|
-
if
|
2333
|
+
if Process.respond_to? :_fork
|
2334
|
+
module ::Process
|
2335
|
+
class << self
|
2336
|
+
prepend ForkInterceptor
|
2337
|
+
end
|
2338
|
+
end
|
2339
|
+
|
2340
|
+
# trap
|
2090
2341
|
module ::Kernel
|
2091
|
-
prepend ForkInterceptor
|
2092
2342
|
prepend TrapInterceptor
|
2093
2343
|
end
|
2344
|
+
module ::Signal
|
2345
|
+
class << self
|
2346
|
+
prepend TrapInterceptor
|
2347
|
+
end
|
2348
|
+
end
|
2094
2349
|
else
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2350
|
+
if RUBY_VERSION >= '3.0.0'
|
2351
|
+
module ::Kernel
|
2352
|
+
prepend ForkInterceptor
|
2353
|
+
prepend TrapInterceptor
|
2354
|
+
end
|
2355
|
+
else
|
2356
|
+
class ::Object
|
2357
|
+
include ForkInterceptor
|
2358
|
+
include TrapInterceptor
|
2359
|
+
end
|
2098
2360
|
end
|
2099
|
-
end
|
2100
2361
|
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2362
|
+
module ::Kernel
|
2363
|
+
class << self
|
2364
|
+
prepend ForkInterceptor
|
2365
|
+
prepend TrapInterceptor
|
2366
|
+
end
|
2105
2367
|
end
|
2106
|
-
end
|
2107
2368
|
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2369
|
+
module ::Process
|
2370
|
+
class << self
|
2371
|
+
prepend ForkInterceptor
|
2372
|
+
end
|
2111
2373
|
end
|
2112
2374
|
end
|
2113
2375
|
|
@@ -2137,3 +2399,12 @@ class Binding
|
|
2137
2399
|
alias break debugger
|
2138
2400
|
alias b debugger
|
2139
2401
|
end
|
2402
|
+
|
2403
|
+
# for Ruby 2.6 compatibility
|
2404
|
+
unless method(:p).unbind.respond_to? :bind_call
|
2405
|
+
class UnboundMethod
|
2406
|
+
def bind_call(obj, *args)
|
2407
|
+
self.bind(obj).call(*args)
|
2408
|
+
end
|
2409
|
+
end
|
2410
|
+
end
|