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