debug 1.4.0 → 1.9.2
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 +210 -6
- data/Gemfile +2 -0
- data/LICENSE.txt +0 -0
- data/README.md +161 -85
- data/Rakefile +33 -10
- data/TODO.md +8 -8
- data/debug.gemspec +9 -7
- data/exe/rdbg +23 -4
- data/ext/debug/debug.c +111 -21
- data/ext/debug/extconf.rb +23 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +102 -74
- data/lib/debug/client.rb +46 -12
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +129 -36
- data/lib/debug/console.rb +46 -40
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +40 -25
- data/lib/debug/irb_integration.rb +37 -0
- data/lib/debug/local.rb +17 -11
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +3 -2
- data/lib/debug/server.rb +126 -56
- data/lib/debug/server_cdp.rb +673 -248
- data/lib/debug/server_dap.rb +497 -261
- data/lib/debug/session.rb +899 -441
- data/lib/debug/source_repository.rb +122 -49
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +460 -155
- data/lib/debug/tracer.rb +10 -16
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +106 -56
- metadata +14 -24
- 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/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/session.rb
CHANGED
@@ -7,7 +7,7 @@ return if ENV['RUBY_DEBUG_ENABLE'] == '0'
|
|
7
7
|
if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
8
8
|
trace_var(:$0) do |file|
|
9
9
|
trace_var(:$0, nil)
|
10
|
-
if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
|
10
|
+
if /-r (#{Regexp.escape(__dir__)}\S+)/ =~ ENV['RUBYOPT']
|
11
11
|
lib = $1
|
12
12
|
$LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
|
13
13
|
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
|
@@ -19,6 +19,15 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
19
19
|
return
|
20
20
|
end
|
21
21
|
|
22
|
+
# restore RUBYOPT
|
23
|
+
if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
|
24
|
+
(rubyopt = ENV['RUBYOPT']) &&
|
25
|
+
rubyopt.end_with?(added_opt)
|
26
|
+
|
27
|
+
ENV['RUBYOPT'] = rubyopt.delete_suffix(added_opt)
|
28
|
+
ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
|
29
|
+
end
|
30
|
+
|
22
31
|
require_relative 'frame_info'
|
23
32
|
require_relative 'config'
|
24
33
|
require_relative 'thread_client'
|
@@ -31,7 +40,8 @@ $LOADED_FEATURES << 'debug.rb'
|
|
31
40
|
$LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
|
32
41
|
require 'debug' # invalidate the $LOADED_FEATURE cache
|
33
42
|
|
34
|
-
require 'json' if ENV['
|
43
|
+
require 'json' if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
44
|
+
require 'pp'
|
35
45
|
|
36
46
|
class RubyVM::InstructionSequence
|
37
47
|
def traceable_lines_norec lines
|
@@ -56,34 +66,37 @@ class RubyVM::InstructionSequence
|
|
56
66
|
|
57
67
|
def type
|
58
68
|
self.to_a[9]
|
59
|
-
end
|
69
|
+
end unless method_defined?(:type)
|
60
70
|
|
61
|
-
def
|
62
|
-
self.to_a
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
71
|
+
def parameters_symbols
|
72
|
+
ary = self.to_a
|
73
|
+
argc = ary[4][:arg_size]
|
74
|
+
locals = ary.to_a[10]
|
75
|
+
locals[0...argc]
|
76
|
+
end unless method_defined?(:parameters_symbols)
|
68
77
|
|
69
78
|
def last_line
|
70
79
|
self.to_a[4][:code_location][2]
|
71
|
-
end
|
80
|
+
end unless method_defined?(:last_line)
|
72
81
|
|
73
82
|
def first_line
|
74
83
|
self.to_a[4][:code_location][0]
|
75
|
-
end
|
76
|
-
end
|
84
|
+
end unless method_defined?(:first_line)
|
85
|
+
end if defined?(RubyVM::InstructionSequence)
|
77
86
|
|
78
87
|
module DEBUGGER__
|
79
|
-
|
88
|
+
PresetCommands = Struct.new(:commands, :source, :auto_continue)
|
89
|
+
SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)
|
90
|
+
|
80
91
|
class PostmortemError < RuntimeError; end
|
81
92
|
|
82
93
|
class Session
|
83
|
-
attr_reader :intercepted_sigint_cmd, :process_group
|
94
|
+
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
|
84
95
|
|
85
|
-
|
86
|
-
|
96
|
+
include Color
|
97
|
+
|
98
|
+
def initialize
|
99
|
+
@ui = nil
|
87
100
|
@sr = SourceRepository.new
|
88
101
|
@bps = {} # bp.key => bp
|
89
102
|
# [file, line] => LineBreakpoint
|
@@ -104,48 +117,69 @@ module DEBUGGER__
|
|
104
117
|
@intercept_trap_sigint = false
|
105
118
|
@intercepted_sigint_cmd = 'DEFAULT'
|
106
119
|
@process_group = ProcessGroup.new
|
107
|
-
@
|
120
|
+
@subsession_stack = []
|
121
|
+
@subsession_id = 0
|
108
122
|
|
109
123
|
@frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
|
110
124
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
111
125
|
@src_map = {} # {id => src}
|
112
126
|
|
113
|
-
@
|
127
|
+
@scr_id_map = {} # for CDP
|
114
128
|
@obj_map = {} # { object_id => ... } for CDP
|
115
129
|
|
116
130
|
@tp_thread_begin = nil
|
131
|
+
@tp_thread_end = nil
|
132
|
+
|
133
|
+
@commands = {}
|
134
|
+
@unsafe_context = false
|
135
|
+
|
136
|
+
@has_keep_script_lines = defined?(RubyVM.keep_script_lines)
|
137
|
+
|
117
138
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
118
|
-
|
139
|
+
eval_script = tp.eval_script unless @has_keep_script_lines
|
140
|
+
ThreadClient.current.on_load tp.instruction_sequence, eval_script
|
119
141
|
}
|
120
142
|
@tp_load_script.enable
|
121
143
|
|
122
144
|
@thread_stopper = thread_stopper
|
123
|
-
|
124
|
-
activate
|
125
|
-
|
126
145
|
self.postmortem = CONFIG[:postmortem]
|
146
|
+
|
147
|
+
register_default_command
|
127
148
|
end
|
128
149
|
|
129
150
|
def active?
|
130
151
|
!@q_evt.closed?
|
131
152
|
end
|
132
153
|
|
133
|
-
def
|
134
|
-
@
|
154
|
+
def remote?
|
155
|
+
@ui.remote?
|
135
156
|
end
|
136
157
|
|
137
|
-
def
|
138
|
-
@
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
@ui.activate self, on_fork: true
|
158
|
+
def stop_stepping? file, line, subsession_id = nil
|
159
|
+
if @bps.has_key? [file, line]
|
160
|
+
true
|
161
|
+
elsif subsession_id && @subsession_id != subsession_id
|
162
|
+
true
|
143
163
|
else
|
144
|
-
|
164
|
+
false
|
145
165
|
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def activate ui = nil, on_fork: false
|
169
|
+
@ui = ui if ui
|
170
|
+
|
171
|
+
@tp_thread_begin&.disable
|
172
|
+
@tp_thread_end&.disable
|
173
|
+
@tp_thread_begin = nil
|
174
|
+
@tp_thread_end = nil
|
175
|
+
@ui.activate self, on_fork: on_fork
|
146
176
|
|
147
177
|
q = Queue.new
|
178
|
+
first_q = Queue.new
|
148
179
|
@session_server = Thread.new do
|
180
|
+
# make sure `@session_server` is assigned
|
181
|
+
first_q.pop; first_q = nil
|
182
|
+
|
149
183
|
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
150
184
|
Thread.current.abort_on_exception = true
|
151
185
|
|
@@ -163,12 +197,24 @@ module DEBUGGER__
|
|
163
197
|
end
|
164
198
|
@tp_thread_begin.enable
|
165
199
|
|
200
|
+
@tp_thread_end = TracePoint.new(:thread_end) do |tp|
|
201
|
+
@th_clients.delete(Thread.current)
|
202
|
+
end
|
203
|
+
@tp_thread_end.enable
|
204
|
+
|
166
205
|
# session start
|
167
206
|
q << true
|
168
207
|
session_server_main
|
169
208
|
end
|
209
|
+
first_q << :ok
|
170
210
|
|
171
211
|
q.pop
|
212
|
+
|
213
|
+
# For activating irb:rdbg with startup config like `RUBY_DEBUG_IRB_CONSOLE=1`
|
214
|
+
# Because in that case the `Config#if_updated` callback would not be triggered
|
215
|
+
if CONFIG[:irb_console] && !CONFIG[:open]
|
216
|
+
activate_irb_integration
|
217
|
+
end
|
172
218
|
end
|
173
219
|
|
174
220
|
def deactivate
|
@@ -176,6 +222,7 @@ module DEBUGGER__
|
|
176
222
|
@thread_stopper.disable
|
177
223
|
@tp_load_script.disable
|
178
224
|
@tp_thread_begin.disable
|
225
|
+
@tp_thread_end.disable
|
179
226
|
@bps.each_value{|bp| bp.disable}
|
180
227
|
@th_clients.each_value{|thc| thc.close}
|
181
228
|
@tracers.values.each{|t| t.disable}
|
@@ -187,6 +234,16 @@ module DEBUGGER__
|
|
187
234
|
def reset_ui ui
|
188
235
|
@ui.deactivate
|
189
236
|
@ui = ui
|
237
|
+
|
238
|
+
# activate new ui
|
239
|
+
@tp_thread_begin.disable
|
240
|
+
@tp_thread_end.disable
|
241
|
+
@ui.activate self
|
242
|
+
if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
|
243
|
+
thc.mark_as_management
|
244
|
+
end
|
245
|
+
@tp_thread_begin.enable
|
246
|
+
@tp_thread_end.enable
|
190
247
|
end
|
191
248
|
|
192
249
|
def pop_event
|
@@ -201,63 +258,81 @@ module DEBUGGER__
|
|
201
258
|
deactivate
|
202
259
|
end
|
203
260
|
|
261
|
+
def request_tc(req)
|
262
|
+
@tc << req
|
263
|
+
end
|
264
|
+
|
265
|
+
def request_tc_with_restarted_threads(req)
|
266
|
+
restart_all_threads
|
267
|
+
request_tc(req)
|
268
|
+
end
|
269
|
+
|
270
|
+
def request_eval type, src
|
271
|
+
request_tc_with_restarted_threads [:eval, type, src]
|
272
|
+
end
|
273
|
+
|
204
274
|
def process_event evt
|
205
275
|
# variable `@internal_info` is only used for test
|
206
276
|
tc, output, ev, @internal_info, *ev_args = evt
|
207
|
-
output.each{|str| @ui.puts str} if ev != :suspend
|
208
277
|
|
209
|
-
|
278
|
+
output.each{|str| @ui.puts str} if ev != :suspend
|
210
279
|
|
211
|
-
|
280
|
+
# special event, tc is nil
|
281
|
+
# and we don't want to set @tc to the newly created thread's ThreadClient
|
282
|
+
if ev == :thread_begin
|
212
283
|
th = ev_args.shift
|
213
284
|
q = ev_args.shift
|
214
285
|
on_thread_begin th
|
215
286
|
q << true
|
216
287
|
|
217
|
-
|
218
|
-
|
288
|
+
return
|
289
|
+
end
|
219
290
|
|
291
|
+
@tc = tc
|
292
|
+
|
293
|
+
case ev
|
294
|
+
when :init
|
295
|
+
enter_subsession
|
296
|
+
wait_command_loop
|
220
297
|
when :load
|
221
298
|
iseq, src = ev_args
|
222
299
|
on_load iseq, src
|
223
|
-
|
224
|
-
tc << :continue
|
300
|
+
request_tc :continue
|
225
301
|
|
226
302
|
when :trace
|
227
303
|
trace_id, msg = ev_args
|
228
304
|
if t = @tracers.values.find{|t| t.object_id == trace_id}
|
229
305
|
t.puts msg
|
230
306
|
end
|
231
|
-
|
307
|
+
request_tc :continue
|
232
308
|
|
233
309
|
when :suspend
|
234
310
|
enter_subsession if ev_args.first != :replay
|
235
|
-
output.each{|str| @ui.puts str}
|
311
|
+
output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?
|
236
312
|
|
237
313
|
case ev_args.first
|
238
314
|
when :breakpoint
|
239
315
|
bp, i = bp_index ev_args[1]
|
240
316
|
clean_bps unless bp
|
241
|
-
@ui.event :suspend_bp, i, bp, tc.id
|
317
|
+
@ui.event :suspend_bp, i, bp, @tc.id
|
242
318
|
when :trap
|
243
|
-
@ui.event :suspend_trap, sig = ev_args[1], tc.id
|
319
|
+
@ui.event :suspend_trap, sig = ev_args[1], @tc.id
|
244
320
|
|
245
321
|
if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
|
246
322
|
@ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
|
247
323
|
@ui.puts "`sigint` command execute it."
|
248
324
|
end
|
249
325
|
else
|
250
|
-
@ui.event :suspended, tc.id
|
326
|
+
@ui.event :suspended, @tc.id
|
251
327
|
end
|
252
328
|
|
253
329
|
if @displays.empty?
|
254
|
-
wait_command_loop
|
330
|
+
wait_command_loop
|
255
331
|
else
|
256
|
-
|
332
|
+
request_eval :display, @displays
|
257
333
|
end
|
258
|
-
|
259
334
|
when :result
|
260
|
-
raise "[BUG] not in subsession"
|
335
|
+
raise "[BUG] not in subsession" if @subsession_stack.empty?
|
261
336
|
|
262
337
|
case ev_args.first
|
263
338
|
when :try_display
|
@@ -269,6 +344,7 @@ module DEBUGGER__
|
|
269
344
|
end
|
270
345
|
end
|
271
346
|
|
347
|
+
stop_all_threads
|
272
348
|
when :method_breakpoint, :watch_breakpoint
|
273
349
|
bp = ev_args[1]
|
274
350
|
if bp
|
@@ -282,18 +358,16 @@ module DEBUGGER__
|
|
282
358
|
obj_inspect = ev_args[2]
|
283
359
|
opt = ev_args[3]
|
284
360
|
add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
361
|
+
stop_all_threads
|
285
362
|
else
|
286
|
-
|
363
|
+
stop_all_threads
|
287
364
|
end
|
288
365
|
|
289
|
-
wait_command_loop
|
366
|
+
wait_command_loop
|
290
367
|
|
291
|
-
when :
|
292
|
-
|
293
|
-
wait_command_loop
|
294
|
-
when :cdp_result
|
295
|
-
cdp_event ev_args
|
296
|
-
wait_command_loop tc
|
368
|
+
when :protocol_result
|
369
|
+
process_protocol_result ev_args
|
370
|
+
wait_command_loop
|
297
371
|
end
|
298
372
|
end
|
299
373
|
|
@@ -308,7 +382,7 @@ module DEBUGGER__
|
|
308
382
|
if @preset_command && !@preset_command.commands.empty?
|
309
383
|
@preset_command.commands += cs
|
310
384
|
else
|
311
|
-
@preset_command =
|
385
|
+
@preset_command = PresetCommands.new(cs, name, continue)
|
312
386
|
end
|
313
387
|
|
314
388
|
ThreadClient.current.on_init name if kick
|
@@ -326,9 +400,7 @@ module DEBUGGER__
|
|
326
400
|
"DEBUGGER__::SESSION"
|
327
401
|
end
|
328
402
|
|
329
|
-
def wait_command_loop
|
330
|
-
@tc = tc
|
331
|
-
|
403
|
+
def wait_command_loop
|
332
404
|
loop do
|
333
405
|
case wait_command
|
334
406
|
when :retry
|
@@ -369,7 +441,7 @@ module DEBUGGER__
|
|
369
441
|
@ui.puts "(rdbg:#{@preset_command.source}) #{line}"
|
370
442
|
end
|
371
443
|
else
|
372
|
-
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['
|
444
|
+
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
373
445
|
line = @ui.readline prompt
|
374
446
|
end
|
375
447
|
|
@@ -383,97 +455,121 @@ module DEBUGGER__
|
|
383
455
|
end
|
384
456
|
end
|
385
457
|
|
386
|
-
def
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
else
|
391
|
-
return :retry
|
392
|
-
end
|
393
|
-
else
|
394
|
-
@repl_prev_line = line
|
395
|
-
end
|
396
|
-
|
397
|
-
/([^\s]+)(?:\s+(.+))?/ =~ line
|
398
|
-
cmd, arg = $1, $2
|
458
|
+
private def register_command *names,
|
459
|
+
repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
|
460
|
+
&b
|
461
|
+
cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
|
399
462
|
|
400
|
-
|
463
|
+
names.each{|name|
|
464
|
+
@commands[name] = cmd
|
465
|
+
}
|
466
|
+
end
|
401
467
|
|
402
|
-
|
468
|
+
def register_default_command
|
403
469
|
### Control flow
|
404
470
|
|
405
471
|
# * `s[tep]`
|
406
472
|
# * Step in. Resume the program until next breakable point.
|
407
473
|
# * `s[tep] <n>`
|
408
474
|
# * Step in, resume the program at `<n>`th breakable point.
|
409
|
-
|
410
|
-
|
411
|
-
|
475
|
+
register_command 's', 'step',
|
476
|
+
repeat: true,
|
477
|
+
cancel_auto_continue: true,
|
478
|
+
postmortem: false do |arg|
|
412
479
|
step_command :in, arg
|
480
|
+
end
|
413
481
|
|
414
482
|
# * `n[ext]`
|
415
483
|
# * Step over. Resume the program until next line.
|
416
484
|
# * `n[ext] <n>`
|
417
485
|
# * Step over, same as `step <n>`.
|
418
|
-
|
419
|
-
|
420
|
-
|
486
|
+
register_command 'n', 'next',
|
487
|
+
repeat: true,
|
488
|
+
cancel_auto_continue: true,
|
489
|
+
postmortem: false do |arg|
|
421
490
|
step_command :next, arg
|
491
|
+
end
|
422
492
|
|
423
493
|
# * `fin[ish]`
|
424
494
|
# * Finish this frame. Resume the program until the current frame is finished.
|
425
495
|
# * `fin[ish] <n>`
|
426
496
|
# * Finish `<n>`th frames.
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
497
|
+
register_command 'fin', 'finish',
|
498
|
+
repeat: true,
|
499
|
+
cancel_auto_continue: true,
|
500
|
+
postmortem: false do |arg|
|
431
501
|
if arg&.to_i == 0
|
432
502
|
raise 'finish command with 0 does not make sense.'
|
433
503
|
end
|
434
504
|
|
435
505
|
step_command :finish, arg
|
506
|
+
end
|
436
507
|
|
437
|
-
# * `
|
508
|
+
# * `u[ntil]`
|
509
|
+
# * Similar to `next` command, but only stop later lines or the end of the current frame.
|
510
|
+
# * Similar to gdb's `advance` command.
|
511
|
+
# * `u[ntil] <[file:]line>`
|
512
|
+
# * Run til the program reaches given location or the end of the current frame.
|
513
|
+
# * `u[ntil] <name>`
|
514
|
+
# * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
|
515
|
+
register_command 'u', 'until',
|
516
|
+
repeat: true,
|
517
|
+
cancel_auto_continue: true,
|
518
|
+
postmortem: false do |arg|
|
519
|
+
|
520
|
+
step_command :until, arg
|
521
|
+
end
|
522
|
+
|
523
|
+
# * `c` or `cont` or `continue`
|
438
524
|
# * Resume the program.
|
439
|
-
|
440
|
-
|
525
|
+
register_command 'c', 'cont', 'continue',
|
526
|
+
repeat: true,
|
527
|
+
cancel_auto_continue: true do |arg|
|
441
528
|
leave_subsession :continue
|
529
|
+
end
|
442
530
|
|
443
531
|
# * `q[uit]` or `Ctrl-D`
|
444
532
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
445
|
-
|
533
|
+
register_command 'q', 'quit' do |arg|
|
446
534
|
if ask 'Really quit?'
|
447
|
-
@ui.quit arg.to_i
|
535
|
+
@ui.quit arg.to_i do
|
536
|
+
request_tc :quit
|
537
|
+
end
|
448
538
|
leave_subsession :continue
|
449
539
|
else
|
450
|
-
|
540
|
+
next :retry
|
451
541
|
end
|
542
|
+
end
|
452
543
|
|
453
544
|
# * `q[uit]!`
|
454
545
|
# * Same as q[uit] but without the confirmation prompt.
|
455
|
-
|
456
|
-
@ui.quit arg.to_i
|
457
|
-
|
546
|
+
register_command 'q!', 'quit!', unsafe: false do |arg|
|
547
|
+
@ui.quit arg.to_i do
|
548
|
+
request_tc :quit
|
549
|
+
end
|
550
|
+
leave_subsession :continue
|
551
|
+
end
|
458
552
|
|
459
553
|
# * `kill`
|
460
554
|
# * Stop the debuggee process with `Kernel#exit!`.
|
461
|
-
|
555
|
+
register_command 'kill' do |arg|
|
462
556
|
if ask 'Really kill?'
|
463
557
|
exit! (arg || 1).to_i
|
464
558
|
else
|
465
|
-
|
559
|
+
next :retry
|
466
560
|
end
|
561
|
+
end
|
467
562
|
|
468
563
|
# * `kill!`
|
469
564
|
# * Same as kill but without the confirmation prompt.
|
470
|
-
|
565
|
+
register_command 'kill!', unsafe: false do |arg|
|
471
566
|
exit! (arg || 1).to_i
|
567
|
+
end
|
472
568
|
|
473
569
|
# * `sigint`
|
474
570
|
# * Execute SIGINT handler registered by the debuggee.
|
475
571
|
# * Note that this command should be used just after stop by `SIGINT`.
|
476
|
-
|
572
|
+
register_command 'sigint' do
|
477
573
|
begin
|
478
574
|
case cmd = @intercepted_sigint_cmd
|
479
575
|
when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
|
@@ -489,8 +585,9 @@ module DEBUGGER__
|
|
489
585
|
rescue Exception => e
|
490
586
|
@ui.puts "Exception: #{e}"
|
491
587
|
@ui.puts e.backtrace.map{|line| " #{e}"}
|
492
|
-
|
588
|
+
next :retry
|
493
589
|
end
|
590
|
+
end
|
494
591
|
|
495
592
|
### Breakpoint
|
496
593
|
|
@@ -510,53 +607,26 @@ module DEBUGGER__
|
|
510
607
|
# * break and run `<command>` before stopping.
|
511
608
|
# * `b[reak] ... do: <command>`
|
512
609
|
# * break and run `<command>`, and continue.
|
513
|
-
# * `b[reak] ... path: <
|
514
|
-
# * break if the
|
610
|
+
# * `b[reak] ... path: <path>`
|
611
|
+
# * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
|
515
612
|
# * `b[reak] if: <expr>`
|
516
613
|
# * break if: `<expr>` is true at any lines.
|
517
614
|
# * Note that this feature is super slow.
|
518
|
-
|
519
|
-
check_postmortem
|
520
|
-
|
615
|
+
register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
|
521
616
|
if arg == nil
|
522
617
|
show_bps
|
523
|
-
|
618
|
+
next :retry
|
524
619
|
else
|
525
620
|
case bp = repl_add_breakpoint(arg)
|
526
621
|
when :noretry
|
527
622
|
when nil
|
528
|
-
|
623
|
+
next :retry
|
529
624
|
else
|
530
625
|
show_bps bp
|
531
|
-
|
626
|
+
next :retry
|
532
627
|
end
|
533
628
|
end
|
534
|
-
|
535
|
-
# skip
|
536
|
-
when 'bv'
|
537
|
-
check_postmortem
|
538
|
-
require 'json'
|
539
|
-
|
540
|
-
h = Hash.new{|h, k| h[k] = []}
|
541
|
-
@bps.each_value{|bp|
|
542
|
-
if LineBreakpoint === bp
|
543
|
-
h[bp.path] << {lnum: bp.line}
|
544
|
-
end
|
545
|
-
}
|
546
|
-
if h.empty?
|
547
|
-
# TODO: clean?
|
548
|
-
else
|
549
|
-
open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
|
550
|
-
end
|
551
|
-
|
552
|
-
vimsrc = File.join(__dir__, 'bp.vim')
|
553
|
-
system("vim -R -S #{vimsrc} #{@tc.location.path}")
|
554
|
-
|
555
|
-
if File.exist?(".rdb_breakpoints.json")
|
556
|
-
pp JSON.load(File.read(".rdb_breakpoints.json"))
|
557
|
-
end
|
558
|
-
|
559
|
-
return :retry
|
629
|
+
end
|
560
630
|
|
561
631
|
# * `catch <Error>`
|
562
632
|
# * Set breakpoint on raising `<Error>`.
|
@@ -566,18 +636,18 @@ module DEBUGGER__
|
|
566
636
|
# * runs `<command>` before stopping.
|
567
637
|
# * `catch ... do: <command>`
|
568
638
|
# * stops and run `<command>`, and continue.
|
569
|
-
# * `catch ... path: <
|
570
|
-
# * stops if the exception is raised from a path
|
571
|
-
|
572
|
-
check_postmortem
|
573
|
-
|
639
|
+
# * `catch ... path: <path>`
|
640
|
+
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
|
641
|
+
register_command 'catch', postmortem: false, unsafe: false do |arg|
|
574
642
|
if arg
|
575
643
|
bp = repl_add_catch_breakpoint arg
|
576
644
|
show_bps bp if bp
|
577
645
|
else
|
578
646
|
show_bps
|
579
647
|
end
|
580
|
-
|
648
|
+
|
649
|
+
:retry
|
650
|
+
end
|
581
651
|
|
582
652
|
# * `watch @ivar`
|
583
653
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
@@ -588,26 +658,22 @@ module DEBUGGER__
|
|
588
658
|
# * runs `<command>` before stopping.
|
589
659
|
# * `watch ... do: <command>`
|
590
660
|
# * stops and run `<command>`, and continue.
|
591
|
-
# * `watch ... path: <
|
592
|
-
# * stops if the
|
593
|
-
|
594
|
-
check_postmortem
|
595
|
-
|
661
|
+
# * `watch ... path: <path>`
|
662
|
+
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
|
663
|
+
register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
|
596
664
|
if arg && arg.match?(/\A@\w+/)
|
597
665
|
repl_add_watch_breakpoint(arg)
|
598
666
|
else
|
599
667
|
show_bps
|
600
|
-
|
668
|
+
:retry
|
601
669
|
end
|
670
|
+
end
|
602
671
|
|
603
672
|
# * `del[ete]`
|
604
673
|
# * delete all breakpoints.
|
605
674
|
# * `del[ete] <bpnum>`
|
606
675
|
# * delete specified breakpoint.
|
607
|
-
|
608
|
-
check_postmortem
|
609
|
-
|
610
|
-
bp =
|
676
|
+
register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
|
611
677
|
case arg
|
612
678
|
when nil
|
613
679
|
show_bps
|
@@ -615,12 +681,13 @@ module DEBUGGER__
|
|
615
681
|
delete_bp
|
616
682
|
end
|
617
683
|
when /\d+/
|
618
|
-
delete_bp arg.to_i
|
684
|
+
bp = delete_bp arg.to_i
|
619
685
|
else
|
620
686
|
nil
|
621
687
|
end
|
622
688
|
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
|
623
|
-
|
689
|
+
:retry
|
690
|
+
end
|
624
691
|
|
625
692
|
### Information
|
626
693
|
|
@@ -632,19 +699,20 @@ module DEBUGGER__
|
|
632
699
|
# * Only shows frames with method name or location info that matches `/regexp/`.
|
633
700
|
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
|
634
701
|
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
|
635
|
-
|
702
|
+
register_command 'bt', 'backtrace', unsafe: false do |arg|
|
636
703
|
case arg
|
637
704
|
when /\A(\d+)\z/
|
638
|
-
|
705
|
+
request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
|
639
706
|
when /\A\/(.*)\/\z/
|
640
707
|
pattern = $1
|
641
|
-
|
708
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
|
642
709
|
when /\A(\d+)\s+\/(.*)\/\z/
|
643
710
|
max, pattern = $1, $2
|
644
|
-
|
711
|
+
request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
645
712
|
else
|
646
|
-
|
713
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
|
647
714
|
end
|
715
|
+
end
|
648
716
|
|
649
717
|
# * `l[ist]`
|
650
718
|
# * Show current frame's source code.
|
@@ -653,57 +721,75 @@ module DEBUGGER__
|
|
653
721
|
# * Show predecessor lines as opposed to the `list` command.
|
654
722
|
# * `l[ist] <start>` or `l[ist] <start>-<end>`
|
655
723
|
# * Show current frame's source code from the line <start> to <end> if given.
|
656
|
-
|
724
|
+
register_command 'l', 'list', repeat: true, unsafe: false do |arg|
|
657
725
|
case arg ? arg.strip : nil
|
658
726
|
when /\A(\d+)\z/
|
659
|
-
|
727
|
+
request_tc [:show, :list, {start_line: arg.to_i - 1}]
|
660
728
|
when /\A-\z/
|
661
|
-
|
729
|
+
request_tc [:show, :list, {dir: -1}]
|
662
730
|
when /\A(\d+)-(\d+)\z/
|
663
|
-
|
731
|
+
request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
|
664
732
|
when nil
|
665
|
-
|
733
|
+
request_tc [:show, :list]
|
666
734
|
else
|
667
735
|
@ui.puts "Can not handle list argument: #{arg}"
|
668
|
-
|
736
|
+
:retry
|
669
737
|
end
|
738
|
+
end
|
739
|
+
|
740
|
+
# * `whereami`
|
741
|
+
# * Show the current frame with source code.
|
742
|
+
register_command 'whereami', unsafe: false do
|
743
|
+
request_tc [:show, :whereami]
|
744
|
+
end
|
670
745
|
|
671
746
|
# * `edit`
|
672
747
|
# * Open the current file on the editor (use `EDITOR` environment variable).
|
673
748
|
# * Note that edited file will not be reloaded.
|
674
749
|
# * `edit <file>`
|
675
750
|
# * Open <file> on the editor.
|
676
|
-
|
751
|
+
register_command 'edit' do |arg|
|
677
752
|
if @ui.remote?
|
678
753
|
@ui.puts "not supported on the remote console."
|
679
|
-
|
754
|
+
next :retry
|
680
755
|
end
|
681
756
|
|
682
757
|
begin
|
683
758
|
arg = resolve_path(arg) if arg
|
684
759
|
rescue Errno::ENOENT
|
685
760
|
@ui.puts "not found: #{arg}"
|
686
|
-
|
761
|
+
next :retry
|
687
762
|
end
|
688
763
|
|
689
|
-
|
764
|
+
request_tc [:show, :edit, arg]
|
765
|
+
end
|
766
|
+
|
767
|
+
info_subcommands = nil
|
768
|
+
info_subcommands_abbrev = nil
|
690
769
|
|
691
770
|
# * `i[nfo]`
|
692
|
-
#
|
693
|
-
# * `i[nfo]
|
771
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
772
|
+
# * `i[nfo]` <subcommand>
|
773
|
+
# * `info` has the following sub-commands.
|
774
|
+
# * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
|
775
|
+
# * `i[nfo] l or locals or local_variables`
|
694
776
|
# * Show information about the current frame (local variables)
|
695
|
-
# * It includes `self` as `%self` and a return value as
|
696
|
-
# * `i[nfo] i
|
777
|
+
# * It includes `self` as `%self` and a return value as `_return`.
|
778
|
+
# * `i[nfo] i or ivars or instance_variables`
|
697
779
|
# * Show information about instance variables about `self`.
|
698
|
-
#
|
780
|
+
# * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
|
781
|
+
# * `i[nfo] c or consts or constants`
|
699
782
|
# * Show information about accessible constants except toplevel constants.
|
700
|
-
#
|
783
|
+
# * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
|
784
|
+
# * `i[nfo] g or globals or global_variables`
|
701
785
|
# * Show information about global variables
|
702
|
-
# * `i[nfo]
|
703
|
-
# * Filter the output with `</pattern/>`.
|
704
|
-
# * `i[nfo] th[read[s]]`
|
786
|
+
# * `i[nfo] th or threads`
|
705
787
|
# * Show all threads (same as `th[read]`).
|
706
|
-
|
788
|
+
# * `i[nfo] b or breakpoints or w or watchpoints`
|
789
|
+
# * Show all breakpoints and watchpoints.
|
790
|
+
# * `i[nfo] ... /regexp/`
|
791
|
+
# * Filter the output with `/regexp/`.
|
792
|
+
register_command 'i', 'info', unsafe: false do |arg|
|
707
793
|
if /\/(.+)\/\z/ =~ arg
|
708
794
|
pat = Regexp.compile($1)
|
709
795
|
sub = $~.pre_match.strip
|
@@ -711,63 +797,98 @@ module DEBUGGER__
|
|
711
797
|
sub = arg
|
712
798
|
end
|
713
799
|
|
800
|
+
if /\A(.+?)\b(.+)/ =~ sub
|
801
|
+
sub = $1
|
802
|
+
opt = $2.strip
|
803
|
+
opt = nil if opt.empty?
|
804
|
+
end
|
805
|
+
|
806
|
+
if sub && !info_subcommands
|
807
|
+
info_subcommands = {
|
808
|
+
locals: %w[ locals local_variables ],
|
809
|
+
ivars: %w[ ivars instance_variables ],
|
810
|
+
consts: %w[ consts constants ],
|
811
|
+
globals:%w[ globals global_variables ],
|
812
|
+
threads:%w[ threads ],
|
813
|
+
breaks: %w[ breakpoints ],
|
814
|
+
watchs: %w[ watchpoints ],
|
815
|
+
}
|
816
|
+
|
817
|
+
require_relative 'abbrev_command'
|
818
|
+
info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
|
819
|
+
end
|
820
|
+
|
821
|
+
if sub
|
822
|
+
sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
|
823
|
+
# note: unreached now
|
824
|
+
@ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
714
828
|
case sub
|
715
829
|
when nil
|
716
|
-
|
717
|
-
when
|
718
|
-
|
719
|
-
when
|
720
|
-
|
721
|
-
when
|
722
|
-
|
723
|
-
when
|
724
|
-
|
725
|
-
when
|
830
|
+
request_tc_with_restarted_threads [:show, :default, pat] # something useful
|
831
|
+
when :locals
|
832
|
+
request_tc_with_restarted_threads [:show, :locals, pat]
|
833
|
+
when :ivars
|
834
|
+
request_tc_with_restarted_threads [:show, :ivars, pat, opt]
|
835
|
+
when :consts
|
836
|
+
request_tc_with_restarted_threads [:show, :consts, pat, opt]
|
837
|
+
when :globals
|
838
|
+
request_tc_with_restarted_threads [:show, :globals, pat]
|
839
|
+
when :threads
|
726
840
|
thread_list
|
727
|
-
|
841
|
+
:retry
|
842
|
+
when :breaks, :watchs
|
843
|
+
show_bps
|
844
|
+
:retry
|
728
845
|
else
|
729
846
|
@ui.puts "unrecognized argument for info command: #{arg}"
|
730
847
|
show_help 'info'
|
731
|
-
|
848
|
+
:retry
|
732
849
|
end
|
850
|
+
end
|
733
851
|
|
734
852
|
# * `o[utline]` or `ls`
|
735
853
|
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
736
854
|
# * `o[utline] <expr>` or `ls <expr>`
|
737
855
|
# * Show you available methods and instance variables of the given object.
|
738
856
|
# * If the object is a class/module, it also lists its constants.
|
739
|
-
|
740
|
-
|
857
|
+
register_command 'outline', 'o', 'ls', unsafe: false do |arg|
|
858
|
+
request_tc_with_restarted_threads [:show, :outline, arg]
|
859
|
+
end
|
741
860
|
|
742
861
|
# * `display`
|
743
862
|
# * Show display setting.
|
744
863
|
# * `display <expr>`
|
745
864
|
# * Show the result of `<expr>` at every suspended timing.
|
746
|
-
|
865
|
+
register_command 'display', postmortem: false do |arg|
|
747
866
|
if arg && !arg.empty?
|
748
867
|
@displays << arg
|
749
|
-
|
868
|
+
request_eval :try_display, @displays
|
750
869
|
else
|
751
|
-
|
870
|
+
request_eval :display, @displays
|
752
871
|
end
|
872
|
+
end
|
753
873
|
|
754
874
|
# * `undisplay`
|
755
875
|
# * Remove all display settings.
|
756
876
|
# * `undisplay <displaynum>`
|
757
877
|
# * Remove a specified display setting.
|
758
|
-
|
878
|
+
register_command 'undisplay', postmortem: false, unsafe: false do |arg|
|
759
879
|
case arg
|
760
880
|
when /(\d+)/
|
761
881
|
if @displays[n = $1.to_i]
|
762
882
|
@displays.delete_at n
|
763
883
|
end
|
764
|
-
|
884
|
+
request_eval :display, @displays
|
765
885
|
when nil
|
766
886
|
if ask "clear all?", 'N'
|
767
887
|
@displays.clear
|
768
888
|
end
|
769
|
-
|
889
|
+
:retry
|
770
890
|
end
|
891
|
+
end
|
771
892
|
|
772
893
|
### Frame control
|
773
894
|
|
@@ -775,53 +896,59 @@ module DEBUGGER__
|
|
775
896
|
# * Show the current frame.
|
776
897
|
# * `f[rame] <framenum>`
|
777
898
|
# * Specify a current frame. Evaluation are run on specified frame.
|
778
|
-
|
779
|
-
|
899
|
+
register_command 'frame', 'f', unsafe: false do |arg|
|
900
|
+
request_tc [:frame, :set, arg]
|
901
|
+
end
|
780
902
|
|
781
903
|
# * `up`
|
782
904
|
# * Specify the upper frame.
|
783
|
-
|
784
|
-
|
905
|
+
register_command 'up', repeat: true, unsafe: false do |arg|
|
906
|
+
request_tc [:frame, :up]
|
907
|
+
end
|
785
908
|
|
786
909
|
# * `down`
|
787
910
|
# * Specify the lower frame.
|
788
|
-
|
789
|
-
|
911
|
+
register_command 'down', repeat: true, unsafe: false do |arg|
|
912
|
+
request_tc [:frame, :down]
|
913
|
+
end
|
790
914
|
|
791
915
|
### Evaluate
|
792
916
|
|
793
917
|
# * `p <expr>`
|
794
918
|
# * Evaluate like `p <expr>` on the current frame.
|
795
|
-
|
796
|
-
|
919
|
+
register_command 'p' do |arg|
|
920
|
+
request_eval :p, arg.to_s
|
921
|
+
end
|
797
922
|
|
798
923
|
# * `pp <expr>`
|
799
924
|
# * Evaluate like `pp <expr>` on the current frame.
|
800
|
-
|
801
|
-
|
925
|
+
register_command 'pp' do |arg|
|
926
|
+
request_eval :pp, arg.to_s
|
927
|
+
end
|
802
928
|
|
803
929
|
# * `eval <expr>`
|
804
930
|
# * Evaluate `<expr>` on the current frame.
|
805
|
-
|
931
|
+
register_command 'eval', 'call' do |arg|
|
806
932
|
if arg == nil || arg.empty?
|
807
933
|
show_help 'eval'
|
808
934
|
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
809
|
-
|
935
|
+
:retry
|
810
936
|
else
|
811
|
-
|
937
|
+
request_eval :call, arg
|
812
938
|
end
|
939
|
+
end
|
813
940
|
|
814
941
|
# * `irb`
|
815
|
-
# *
|
816
|
-
|
942
|
+
# * Activate and switch to `irb:rdbg` console
|
943
|
+
register_command 'irb' do |arg|
|
817
944
|
if @ui.remote?
|
818
|
-
@ui.puts "not supported on the remote console."
|
819
|
-
|
945
|
+
@ui.puts "\nIRB is not supported on the remote console."
|
946
|
+
else
|
947
|
+
config_set :irb_console, true
|
820
948
|
end
|
821
|
-
@tc << [:eval, :irb]
|
822
949
|
|
823
|
-
|
824
|
-
|
950
|
+
:retry
|
951
|
+
end
|
825
952
|
|
826
953
|
### Trace
|
827
954
|
# * `trace`
|
@@ -834,15 +961,15 @@ module DEBUGGER__
|
|
834
961
|
# * Add an exception tracer. It indicates raising exceptions.
|
835
962
|
# * `trace object <expr>`
|
836
963
|
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
837
|
-
# * `trace ...
|
838
|
-
# * Indicates only matched events to
|
964
|
+
# * `trace ... /regexp/`
|
965
|
+
# * Indicates only matched events to `/regexp/`.
|
839
966
|
# * `trace ... into: <file>`
|
840
967
|
# * Save trace information into: `<file>`.
|
841
968
|
# * `trace off <num>`
|
842
969
|
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
843
970
|
# * `trace off [line|call|pass]`
|
844
971
|
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
845
|
-
|
972
|
+
register_command 'trace', postmortem: false, unsafe: false do |arg|
|
846
973
|
if (re = /\s+into:\s*(.+)/) =~ arg
|
847
974
|
into = $1
|
848
975
|
arg.sub!(re, '')
|
@@ -860,22 +987,22 @@ module DEBUGGER__
|
|
860
987
|
@ui.puts "* \##{i} #{t}"
|
861
988
|
}
|
862
989
|
@ui.puts
|
863
|
-
|
990
|
+
:retry
|
864
991
|
|
865
992
|
when /\Aline\z/
|
866
993
|
add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
|
867
|
-
|
994
|
+
:retry
|
868
995
|
|
869
996
|
when /\Acall\z/
|
870
997
|
add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
|
871
|
-
|
998
|
+
:retry
|
872
999
|
|
873
1000
|
when /\Aexception\z/
|
874
1001
|
add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
875
|
-
|
1002
|
+
:retry
|
876
1003
|
|
877
1004
|
when /\Aobject\s+(.+)/
|
878
|
-
|
1005
|
+
request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
879
1006
|
|
880
1007
|
when /\Aoff\s+(\d+)\z/
|
881
1008
|
if t = @tracers.values[$1.to_i]
|
@@ -884,7 +1011,7 @@ module DEBUGGER__
|
|
884
1011
|
else
|
885
1012
|
@ui.puts "Unmatched: #{$1}"
|
886
1013
|
end
|
887
|
-
|
1014
|
+
:retry
|
888
1015
|
|
889
1016
|
when /\Aoff(\s+(line|call|exception|object))?\z/
|
890
1017
|
@tracers.values.each{|t|
|
@@ -893,12 +1020,13 @@ module DEBUGGER__
|
|
893
1020
|
@ui.puts "Disable #{t.to_s}"
|
894
1021
|
end
|
895
1022
|
}
|
896
|
-
|
1023
|
+
:retry
|
897
1024
|
|
898
1025
|
else
|
899
1026
|
@ui.puts "Unknown trace option: #{arg.inspect}"
|
900
|
-
|
1027
|
+
:retry
|
901
1028
|
end
|
1029
|
+
end
|
902
1030
|
|
903
1031
|
# Record
|
904
1032
|
# * `record`
|
@@ -910,14 +1038,15 @@ module DEBUGGER__
|
|
910
1038
|
# * `s[tep]` does stepping forward with the last log.
|
911
1039
|
# * `step reset`
|
912
1040
|
# * Stop replay .
|
913
|
-
|
1041
|
+
register_command 'record', postmortem: false, unsafe: false do |arg|
|
914
1042
|
case arg
|
915
1043
|
when nil, 'on', 'off'
|
916
|
-
|
1044
|
+
request_tc [:record, arg&.to_sym]
|
917
1045
|
else
|
918
1046
|
@ui.puts "unknown command: #{arg}"
|
919
|
-
|
1047
|
+
:retry
|
920
1048
|
end
|
1049
|
+
end
|
921
1050
|
|
922
1051
|
### Thread control
|
923
1052
|
|
@@ -925,7 +1054,7 @@ module DEBUGGER__
|
|
925
1054
|
# * Show all threads.
|
926
1055
|
# * `th[read] <thnum>`
|
927
1056
|
# * Switch thread specified by `<thnum>`.
|
928
|
-
|
1057
|
+
register_command 'th', 'thread', unsafe: false do |arg|
|
929
1058
|
case arg
|
930
1059
|
when nil, 'list', 'l'
|
931
1060
|
thread_list
|
@@ -934,7 +1063,8 @@ module DEBUGGER__
|
|
934
1063
|
else
|
935
1064
|
@ui.puts "unknown thread command: #{arg}"
|
936
1065
|
end
|
937
|
-
|
1066
|
+
:retry
|
1067
|
+
end
|
938
1068
|
|
939
1069
|
### Configuration
|
940
1070
|
# * `config`
|
@@ -947,13 +1077,14 @@ module DEBUGGER__
|
|
947
1077
|
# * Append `<val>` to `<name>` if it is an array.
|
948
1078
|
# * `config unset <name>`
|
949
1079
|
# * Set <name> to default.
|
950
|
-
|
1080
|
+
register_command 'config', unsafe: false do |arg|
|
951
1081
|
config_command arg
|
952
|
-
|
1082
|
+
:retry
|
1083
|
+
end
|
953
1084
|
|
954
1085
|
# * `source <file>`
|
955
1086
|
# * Evaluate lines in `<file>` as debug commands.
|
956
|
-
|
1087
|
+
register_command 'source' do |arg|
|
957
1088
|
if arg
|
958
1089
|
begin
|
959
1090
|
cmds = File.readlines(path = File.expand_path(arg))
|
@@ -964,7 +1095,8 @@ module DEBUGGER__
|
|
964
1095
|
else
|
965
1096
|
show_help 'source'
|
966
1097
|
end
|
967
|
-
|
1098
|
+
:retry
|
1099
|
+
end
|
968
1100
|
|
969
1101
|
# * `open`
|
970
1102
|
# * open debuggee port on UNIX domain socket and wait for attaching.
|
@@ -975,26 +1107,28 @@ module DEBUGGER__
|
|
975
1107
|
# * open debuggee port for VSCode and launch VSCode if available.
|
976
1108
|
# * `open chrome`
|
977
1109
|
# * open debuggee port for Chrome and wait for attaching.
|
978
|
-
|
1110
|
+
register_command 'open' do |arg|
|
979
1111
|
case arg&.downcase
|
980
1112
|
when '', nil
|
981
|
-
|
982
|
-
when 'vscode'
|
983
|
-
repl_open_vscode
|
984
|
-
when /\A(.+):(\d+)\z/
|
985
|
-
repl_open_tcp $1, $2.to_i
|
1113
|
+
::DEBUGGER__.open nonstop: true
|
986
1114
|
when /\A(\d+)z/
|
987
|
-
|
1115
|
+
::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
|
1116
|
+
when /\A(.+):(\d+)\z/
|
1117
|
+
::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
|
988
1118
|
when 'tcp'
|
989
|
-
|
1119
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
1120
|
+
when 'vscode'
|
1121
|
+
CONFIG[:open] = 'vscode'
|
1122
|
+
::DEBUGGER__.open nonstop: true
|
990
1123
|
when 'chrome', 'cdp'
|
991
|
-
CONFIG[:
|
992
|
-
|
1124
|
+
CONFIG[:open] = 'chrome'
|
1125
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
993
1126
|
else
|
994
1127
|
raise "Unknown arg: #{arg}"
|
995
1128
|
end
|
996
1129
|
|
997
|
-
|
1130
|
+
:retry
|
1131
|
+
end
|
998
1132
|
|
999
1133
|
### Help
|
1000
1134
|
|
@@ -1002,30 +1136,38 @@ module DEBUGGER__
|
|
1002
1136
|
# * Show help for all commands.
|
1003
1137
|
# * `h[elp] <command>`
|
1004
1138
|
# * Show help for the given command.
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1139
|
+
register_command 'h', 'help', '?', unsafe: false do |arg|
|
1140
|
+
show_help arg
|
1141
|
+
:retry
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
def process_command line
|
1146
|
+
if line.empty?
|
1147
|
+
if @repl_prev_line
|
1148
|
+
line = @repl_prev_line
|
1008
1149
|
else
|
1009
|
-
|
1150
|
+
return :retry
|
1010
1151
|
end
|
1011
|
-
|
1152
|
+
else
|
1153
|
+
@repl_prev_line = line
|
1154
|
+
end
|
1012
1155
|
|
1013
|
-
|
1156
|
+
/([^\s]+)(?:\s+(.+))?/ =~ line
|
1157
|
+
cmd_name, cmd_arg = $1, $2
|
1158
|
+
|
1159
|
+
if cmd = @commands[cmd_name]
|
1160
|
+
check_postmortem if !cmd.postmortem
|
1161
|
+
check_unsafe if cmd.unsafe
|
1162
|
+
cancel_auto_continue if cmd.cancel_auto_continue
|
1163
|
+
@repl_prev_line = nil if !cmd.repeat
|
1164
|
+
|
1165
|
+
cmd.block.call(cmd_arg)
|
1014
1166
|
else
|
1015
|
-
@tc << [:eval, :pp, line]
|
1016
|
-
=begin
|
1017
1167
|
@repl_prev_line = nil
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
1022
|
-
correction = spell_checker.correct(line.split(/\s/).first || '')
|
1023
|
-
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
1024
|
-
rescue LoadError
|
1025
|
-
# Don't use D
|
1026
|
-
end
|
1027
|
-
return :retry
|
1028
|
-
=end
|
1168
|
+
check_unsafe
|
1169
|
+
|
1170
|
+
request_eval :pp, line
|
1029
1171
|
end
|
1030
1172
|
|
1031
1173
|
rescue Interrupt
|
@@ -1041,44 +1183,27 @@ module DEBUGGER__
|
|
1041
1183
|
return :retry
|
1042
1184
|
end
|
1043
1185
|
|
1044
|
-
def
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
thc.mark_as_management
|
1186
|
+
def step_command type, arg
|
1187
|
+
if type == :until
|
1188
|
+
leave_subsession [:step, type, arg]
|
1189
|
+
return
|
1049
1190
|
end
|
1050
|
-
@tp_thread_begin.enable
|
1051
|
-
end
|
1052
1191
|
|
1053
|
-
def repl_open_tcp host, port, **kw
|
1054
|
-
DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
|
1055
|
-
repl_open_setup
|
1056
|
-
end
|
1057
|
-
|
1058
|
-
def repl_open_unix
|
1059
|
-
DEBUGGER__.open_unix nonstop: true
|
1060
|
-
repl_open_setup
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
def repl_open_vscode
|
1064
|
-
CONFIG[:open_frontend] = 'vscode'
|
1065
|
-
repl_open_unix
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
def step_command type, arg
|
1069
1192
|
case arg
|
1070
1193
|
when nil, /\A\d+\z/
|
1071
1194
|
if type == :in && @tc.recorder&.replaying?
|
1072
|
-
|
1195
|
+
request_tc [:step, type, arg&.to_i]
|
1073
1196
|
else
|
1074
1197
|
leave_subsession [:step, type, arg&.to_i]
|
1075
1198
|
end
|
1076
|
-
when /\
|
1199
|
+
when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
|
1077
1200
|
if type != :in
|
1078
1201
|
@ui.puts "only `step #{arg}` is supported."
|
1079
1202
|
:retry
|
1080
1203
|
else
|
1081
|
-
|
1204
|
+
type = $1.to_sym
|
1205
|
+
iter = $2&.to_i
|
1206
|
+
request_tc [:step, type, iter]
|
1082
1207
|
end
|
1083
1208
|
else
|
1084
1209
|
@ui.puts "Unknown option: #{arg}"
|
@@ -1088,11 +1213,18 @@ module DEBUGGER__
|
|
1088
1213
|
|
1089
1214
|
def config_show key
|
1090
1215
|
key = key.to_sym
|
1091
|
-
|
1216
|
+
config_detail = CONFIG_SET[key]
|
1217
|
+
|
1218
|
+
if config_detail
|
1092
1219
|
v = CONFIG[key]
|
1093
|
-
kv = "#{key} = #{v.
|
1094
|
-
desc =
|
1095
|
-
|
1220
|
+
kv = "#{key} = #{v.inspect}"
|
1221
|
+
desc = config_detail[1]
|
1222
|
+
|
1223
|
+
if config_default = config_detail[3]
|
1224
|
+
desc += " (default: #{config_default})"
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
line = "%-34s \# %s" % [kv, desc]
|
1096
1228
|
if line.size > SESSION.width
|
1097
1229
|
@ui.puts "\# #{desc}\n#{kv}"
|
1098
1230
|
else
|
@@ -1142,7 +1274,7 @@ module DEBUGGER__
|
|
1142
1274
|
config_set $1, $2, append: true
|
1143
1275
|
|
1144
1276
|
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
1145
|
-
config_set $1, $2
|
1277
|
+
config_set $1, $2, append: true
|
1146
1278
|
|
1147
1279
|
when /\A(\w+)\z/
|
1148
1280
|
config_show $1
|
@@ -1159,16 +1291,50 @@ module DEBUGGER__
|
|
1159
1291
|
end
|
1160
1292
|
end
|
1161
1293
|
|
1162
|
-
def show_help arg
|
1163
|
-
DEBUGGER__.
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1294
|
+
def show_help arg = nil
|
1295
|
+
instructions = (DEBUGGER__.commands.keys + DEBUGGER__.commands.values).uniq
|
1296
|
+
print_instructions = proc do |desc|
|
1297
|
+
desc.split("\n").each do |line|
|
1298
|
+
next if line.start_with?(" ") # workaround for step back
|
1299
|
+
formatted_line = line.gsub(/[\[\]\*]/, "").strip
|
1300
|
+
instructions.each do |inst|
|
1301
|
+
if formatted_line.start_with?("`#{inst}")
|
1302
|
+
desc.sub!(line, colorize(line, [:CYAN, :BOLD]))
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
end
|
1306
|
+
@ui.puts desc
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
print_category = proc do |cat|
|
1310
|
+
@ui.puts "\n"
|
1311
|
+
@ui.puts colorize("### #{cat}", [:GREEN, :BOLD])
|
1312
|
+
@ui.puts "\n"
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
DEBUGGER__.helps.each { |cat, cs|
|
1316
|
+
# categories
|
1317
|
+
if arg.nil?
|
1318
|
+
print_category.call(cat)
|
1319
|
+
else
|
1320
|
+
cs.each { |ws, _|
|
1321
|
+
if ws.include?(arg)
|
1322
|
+
print_category.call(cat)
|
1323
|
+
break
|
1324
|
+
end
|
1325
|
+
}
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
# instructions
|
1329
|
+
cs.each { |ws, desc|
|
1330
|
+
if arg.nil? || ws.include?(arg)
|
1331
|
+
print_instructions.call(desc.dup)
|
1332
|
+
return if arg
|
1168
1333
|
end
|
1169
1334
|
}
|
1170
1335
|
}
|
1171
|
-
|
1336
|
+
|
1337
|
+
@ui.puts "not found: #{arg}" if arg
|
1172
1338
|
end
|
1173
1339
|
|
1174
1340
|
def ask msg, default = 'Y'
|
@@ -1231,12 +1397,13 @@ module DEBUGGER__
|
|
1231
1397
|
|
1232
1398
|
def add_bp bp
|
1233
1399
|
# don't repeat commands that add breakpoints
|
1234
|
-
@repl_prev_line = nil
|
1235
|
-
|
1236
1400
|
if @bps.has_key? bp.key
|
1237
|
-
|
1401
|
+
if bp.duplicable?
|
1402
|
+
bp
|
1403
|
+
else
|
1238
1404
|
@ui.puts "duplicated breakpoint: #{bp}"
|
1239
1405
|
bp.disable
|
1406
|
+
nil
|
1240
1407
|
end
|
1241
1408
|
else
|
1242
1409
|
@bps[bp.key] = bp
|
@@ -1261,7 +1428,7 @@ module DEBUGGER__
|
|
1261
1428
|
|
1262
1429
|
BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
|
1263
1430
|
|
1264
|
-
def parse_break arg
|
1431
|
+
private def parse_break type, arg
|
1265
1432
|
mode = :sig
|
1266
1433
|
expr = Hash.new{|h, k| h[k] = []}
|
1267
1434
|
arg.split(' ').each{|w|
|
@@ -1272,14 +1439,25 @@ module DEBUGGER__
|
|
1272
1439
|
end
|
1273
1440
|
}
|
1274
1441
|
expr.default_proc = nil
|
1275
|
-
expr.transform_values{|v| v.join(' ')}
|
1442
|
+
expr = expr.transform_values{|v| v.join(' ')}
|
1443
|
+
|
1444
|
+
if (path = expr[:path]) && path =~ /\A\/(.*)\/\z/
|
1445
|
+
expr[:path] = Regexp.compile($1)
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
if expr[:do] || expr[:pre]
|
1449
|
+
check_unsafe
|
1450
|
+
expr[:cmd] = [type, expr[:pre], expr[:do]]
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
expr
|
1276
1454
|
end
|
1277
1455
|
|
1278
1456
|
def repl_add_breakpoint arg
|
1279
|
-
expr = parse_break arg.strip
|
1457
|
+
expr = parse_break 'break', arg.strip
|
1280
1458
|
cond = expr[:if]
|
1281
|
-
cmd
|
1282
|
-
path =
|
1459
|
+
cmd = expr[:cmd]
|
1460
|
+
path = expr[:path]
|
1283
1461
|
|
1284
1462
|
case expr[:sig]
|
1285
1463
|
when /\A(\d+)\z/
|
@@ -1287,10 +1465,10 @@ module DEBUGGER__
|
|
1287
1465
|
when /\A(.+)[:\s+](\d+)\z/
|
1288
1466
|
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
1289
1467
|
when /\A(.+)([\.\#])(.+)\z/
|
1290
|
-
|
1468
|
+
request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
|
1291
1469
|
return :noretry
|
1292
1470
|
when nil
|
1293
|
-
add_check_breakpoint cond, path
|
1471
|
+
add_check_breakpoint cond, path, cmd
|
1294
1472
|
else
|
1295
1473
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
1296
1474
|
@ui.puts
|
@@ -1299,31 +1477,31 @@ module DEBUGGER__
|
|
1299
1477
|
end
|
1300
1478
|
|
1301
1479
|
def repl_add_catch_breakpoint arg
|
1302
|
-
expr = parse_break arg.strip
|
1480
|
+
expr = parse_break 'catch', arg.strip
|
1303
1481
|
cond = expr[:if]
|
1304
|
-
cmd
|
1305
|
-
path =
|
1482
|
+
cmd = expr[:cmd]
|
1483
|
+
path = expr[:path]
|
1306
1484
|
|
1307
1485
|
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
|
1308
1486
|
add_bp bp
|
1309
1487
|
end
|
1310
1488
|
|
1311
1489
|
def repl_add_watch_breakpoint arg
|
1312
|
-
expr = parse_break arg.strip
|
1490
|
+
expr = parse_break 'watch', arg.strip
|
1313
1491
|
cond = expr[:if]
|
1314
|
-
cmd
|
1492
|
+
cmd = expr[:cmd]
|
1315
1493
|
path = Regexp.compile(expr[:path]) if expr[:path]
|
1316
1494
|
|
1317
|
-
|
1495
|
+
request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
|
1318
1496
|
end
|
1319
1497
|
|
1320
|
-
def add_catch_breakpoint pat
|
1321
|
-
bp = CatchBreakpoint.new(pat)
|
1498
|
+
def add_catch_breakpoint pat, cond: nil
|
1499
|
+
bp = CatchBreakpoint.new(pat, cond: cond)
|
1322
1500
|
add_bp bp
|
1323
1501
|
end
|
1324
1502
|
|
1325
|
-
def add_check_breakpoint
|
1326
|
-
bp = CheckBreakpoint.new(
|
1503
|
+
def add_check_breakpoint cond, path, command
|
1504
|
+
bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
|
1327
1505
|
add_bp bp
|
1328
1506
|
end
|
1329
1507
|
|
@@ -1336,6 +1514,34 @@ module DEBUGGER__
|
|
1336
1514
|
@ui.puts e.message
|
1337
1515
|
end
|
1338
1516
|
|
1517
|
+
def clear_breakpoints(&condition)
|
1518
|
+
@bps.delete_if do |k, bp|
|
1519
|
+
if condition.call(k, bp)
|
1520
|
+
bp.delete
|
1521
|
+
true
|
1522
|
+
end
|
1523
|
+
end
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
def clear_line_breakpoints path
|
1527
|
+
path = resolve_path(path)
|
1528
|
+
clear_breakpoints do |k, bp|
|
1529
|
+
bp.is_a?(LineBreakpoint) && bp.path_is?(path)
|
1530
|
+
end
|
1531
|
+
rescue Errno::ENOENT
|
1532
|
+
# just ignore
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
def clear_catch_breakpoints *exception_names
|
1536
|
+
clear_breakpoints do |k, bp|
|
1537
|
+
bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1])
|
1538
|
+
end
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
def clear_all_breakpoints
|
1542
|
+
clear_breakpoints{true}
|
1543
|
+
end
|
1544
|
+
|
1339
1545
|
def add_iseq_breakpoint iseq, **kw
|
1340
1546
|
bp = ISeqBreakpoint.new(iseq, [:line], **kw)
|
1341
1547
|
add_bp bp
|
@@ -1344,9 +1550,7 @@ module DEBUGGER__
|
|
1344
1550
|
# tracers
|
1345
1551
|
|
1346
1552
|
def add_tracer tracer
|
1347
|
-
|
1348
|
-
@repl_prev_line = nil
|
1349
|
-
if @tracers.has_key? tracer.key
|
1553
|
+
if @tracers[tracer.key]&.enabled?
|
1350
1554
|
tracer.disable
|
1351
1555
|
@ui.puts "Duplicated tracer: #{tracer}"
|
1352
1556
|
else
|
@@ -1503,42 +1707,72 @@ module DEBUGGER__
|
|
1503
1707
|
end
|
1504
1708
|
|
1505
1709
|
private def enter_subsession
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1710
|
+
@subsession_id += 1
|
1711
|
+
if !@subsession_stack.empty?
|
1712
|
+
DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
|
1713
|
+
else
|
1714
|
+
DEBUGGER__.debug{ "Enter subsession" }
|
1715
|
+
stop_all_threads
|
1716
|
+
@process_group.lock
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
@subsession_stack << true
|
1511
1720
|
end
|
1512
1721
|
|
1513
1722
|
private def leave_subsession type
|
1514
|
-
|
1515
|
-
@
|
1516
|
-
|
1517
|
-
|
1723
|
+
raise '[BUG] leave_subsession: not entered' if @subsession_stack.empty?
|
1724
|
+
@subsession_stack.pop
|
1725
|
+
|
1726
|
+
if @subsession_stack.empty?
|
1727
|
+
DEBUGGER__.debug{ "Leave subsession" }
|
1728
|
+
@process_group.unlock
|
1729
|
+
restart_all_threads
|
1730
|
+
else
|
1731
|
+
DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
|
1732
|
+
end
|
1733
|
+
|
1734
|
+
request_tc type if type
|
1518
1735
|
@tc = nil
|
1519
|
-
@subsession = false
|
1520
1736
|
rescue Exception => e
|
1521
|
-
STDERR.puts [e, e.backtrace].
|
1737
|
+
STDERR.puts PP.pp([e, e.backtrace], ''.dup)
|
1522
1738
|
raise
|
1523
1739
|
end
|
1524
1740
|
|
1525
1741
|
def in_subsession?
|
1526
|
-
|
1742
|
+
!@subsession_stack.empty?
|
1527
1743
|
end
|
1528
1744
|
|
1529
1745
|
## event
|
1530
1746
|
|
1531
1747
|
def on_load iseq, src
|
1532
1748
|
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
1533
|
-
@sr.add iseq, src
|
1534
|
-
|
1535
|
-
pending_line_breakpoints = @bps.find_all do |key, bp|
|
1536
|
-
LineBreakpoint === bp && !bp.iseq
|
1537
|
-
end
|
1538
1749
|
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1750
|
+
file_path, reloaded = @sr.add(iseq, src)
|
1751
|
+
@ui.event :load, file_path, reloaded
|
1752
|
+
|
1753
|
+
# check breakpoints
|
1754
|
+
if file_path
|
1755
|
+
@bps.find_all do |_key, bp|
|
1756
|
+
LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
|
1757
|
+
end.each do |_key, bp|
|
1758
|
+
if !bp.iseq
|
1759
|
+
bp.try_activate iseq
|
1760
|
+
elsif reloaded
|
1761
|
+
@bps.delete bp.key # to allow duplicate
|
1762
|
+
|
1763
|
+
# When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
|
1764
|
+
# will continue to be enabled and we'll suspend on ghost breakpoints
|
1765
|
+
bp.delete
|
1766
|
+
|
1767
|
+
nbp = LineBreakpoint.copy(bp, iseq)
|
1768
|
+
add_bp nbp
|
1769
|
+
end
|
1770
|
+
end
|
1771
|
+
else # !file_path => file_path is not existing
|
1772
|
+
@bps.find_all do |_key, bp|
|
1773
|
+
LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
|
1774
|
+
end.each do |_key, bp|
|
1775
|
+
bp.try_activate iseq
|
1542
1776
|
end
|
1543
1777
|
end
|
1544
1778
|
end
|
@@ -1563,9 +1797,10 @@ module DEBUGGER__
|
|
1563
1797
|
|
1564
1798
|
def method_added tp
|
1565
1799
|
b = tp.binding
|
1800
|
+
|
1566
1801
|
if var_name = b.local_variables.first
|
1567
1802
|
mid = b.local_variable_get(var_name)
|
1568
|
-
|
1803
|
+
resolved = true
|
1569
1804
|
|
1570
1805
|
@bps.each{|k, bp|
|
1571
1806
|
case bp
|
@@ -1576,15 +1811,57 @@ module DEBUGGER__
|
|
1576
1811
|
end
|
1577
1812
|
end
|
1578
1813
|
|
1579
|
-
|
1814
|
+
resolved = false if !bp.enabled?
|
1580
1815
|
end
|
1581
1816
|
}
|
1582
|
-
|
1583
|
-
|
1817
|
+
|
1818
|
+
if resolved
|
1819
|
+
Session.deactivate_method_added_trackers
|
1820
|
+
end
|
1821
|
+
|
1822
|
+
case mid
|
1823
|
+
when :method_added, :singleton_method_added
|
1824
|
+
Session.create_method_added_tracker(tp.self, mid)
|
1825
|
+
Session.activate_method_added_trackers unless resolved
|
1584
1826
|
end
|
1585
1827
|
end
|
1586
1828
|
end
|
1587
1829
|
|
1830
|
+
class ::Module
|
1831
|
+
undef method_added
|
1832
|
+
def method_added mid; end
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
class ::BasicObject
|
1836
|
+
undef singleton_method_added
|
1837
|
+
def singleton_method_added mid; end
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
|
1841
|
+
m = mod.__send__(method_accessor, method_added_id)
|
1842
|
+
METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
|
1843
|
+
SESSION.method_added tp
|
1844
|
+
end
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
def self.activate_method_added_trackers
|
1848
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1849
|
+
tp.enable(target: m) unless tp.enabled?
|
1850
|
+
rescue ArgumentError
|
1851
|
+
DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
|
1852
|
+
end
|
1853
|
+
end
|
1854
|
+
|
1855
|
+
def self.deactivate_method_added_trackers
|
1856
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1857
|
+
tp.disable if tp.enabled?
|
1858
|
+
end
|
1859
|
+
end
|
1860
|
+
|
1861
|
+
METHOD_ADDED_TRACKERS = Hash.new
|
1862
|
+
create_method_added_tracker Module, :method_added, :instance_method
|
1863
|
+
create_method_added_tracker BasicObject, :singleton_method_added, :instance_method
|
1864
|
+
|
1588
1865
|
def width
|
1589
1866
|
@ui.width
|
1590
1867
|
end
|
@@ -1595,6 +1872,18 @@ module DEBUGGER__
|
|
1595
1872
|
end
|
1596
1873
|
end
|
1597
1874
|
|
1875
|
+
def check_unsafe
|
1876
|
+
if @unsafe_context
|
1877
|
+
raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
|
1878
|
+
end
|
1879
|
+
end
|
1880
|
+
|
1881
|
+
def activate_irb_integration
|
1882
|
+
require_relative "irb_integration"
|
1883
|
+
thc = get_thread_client(@session_server)
|
1884
|
+
thc.activate_irb_integration
|
1885
|
+
end
|
1886
|
+
|
1598
1887
|
def enter_postmortem_session exc
|
1599
1888
|
return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
|
1600
1889
|
|
@@ -1674,6 +1963,17 @@ module DEBUGGER__
|
|
1674
1963
|
end
|
1675
1964
|
end
|
1676
1965
|
|
1966
|
+
def set_no_sigint_hook old, new
|
1967
|
+
return unless old != new
|
1968
|
+
return unless @ui.respond_to? :activate_sigint
|
1969
|
+
|
1970
|
+
if old # no -> yes
|
1971
|
+
@ui.activate_sigint
|
1972
|
+
else
|
1973
|
+
@ui.deactivate_sigint
|
1974
|
+
end
|
1975
|
+
end
|
1976
|
+
|
1677
1977
|
def save_int_trap cmd
|
1678
1978
|
prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
|
1679
1979
|
prev
|
@@ -1717,6 +2017,13 @@ module DEBUGGER__
|
|
1717
2017
|
def after_fork_parent
|
1718
2018
|
@ui.after_fork_parent
|
1719
2019
|
end
|
2020
|
+
|
2021
|
+
# experimental API
|
2022
|
+
def extend_feature session: nil, thread_client: nil, ui: nil
|
2023
|
+
Session.include session if session
|
2024
|
+
ThreadClient.include thread_client if thread_client
|
2025
|
+
@ui.extend ui if ui
|
2026
|
+
end
|
1720
2027
|
end
|
1721
2028
|
|
1722
2029
|
class ProcessGroup
|
@@ -1765,9 +2072,11 @@ module DEBUGGER__
|
|
1765
2072
|
|
1766
2073
|
def after_fork child: true
|
1767
2074
|
if child || !@lock_file
|
1768
|
-
@m = Mutex.new
|
1769
|
-
@
|
1770
|
-
|
2075
|
+
@m = Mutex.new unless @m
|
2076
|
+
@m.synchronize do
|
2077
|
+
@lock_level = 0
|
2078
|
+
@lock_file = open(@lock_tempfile.path, 'w')
|
2079
|
+
end
|
1771
2080
|
end
|
1772
2081
|
end
|
1773
2082
|
|
@@ -1776,7 +2085,7 @@ module DEBUGGER__
|
|
1776
2085
|
end
|
1777
2086
|
|
1778
2087
|
def locked?
|
1779
|
-
# DEBUGGER__.
|
2088
|
+
# DEBUGGER__.debug{ "locked? #{@lock_level}" }
|
1780
2089
|
@lock_level > 0
|
1781
2090
|
end
|
1782
2091
|
|
@@ -1860,6 +2169,13 @@ module DEBUGGER__
|
|
1860
2169
|
puts "\nStop by #{args.first}"
|
1861
2170
|
end
|
1862
2171
|
end
|
2172
|
+
|
2173
|
+
def ignore_output_on_suspend?
|
2174
|
+
false
|
2175
|
+
end
|
2176
|
+
|
2177
|
+
def flush
|
2178
|
+
end
|
1863
2179
|
end
|
1864
2180
|
|
1865
2181
|
# manual configuration methods
|
@@ -1876,12 +2192,13 @@ module DEBUGGER__
|
|
1876
2192
|
# nil for -r
|
1877
2193
|
def self.require_location
|
1878
2194
|
locs = caller_locations
|
1879
|
-
dir_prefix = /#{__dir__}/
|
2195
|
+
dir_prefix = /#{Regexp.escape(__dir__)}/
|
1880
2196
|
|
1881
2197
|
locs.each do |loc|
|
1882
2198
|
case loc.absolute_path
|
1883
2199
|
when dir_prefix
|
1884
2200
|
when %r{rubygems/core_ext/kernel_require\.rb}
|
2201
|
+
when %r{bundled_gems\.rb}
|
1885
2202
|
else
|
1886
2203
|
return loc if loc.absolute_path
|
1887
2204
|
end
|
@@ -1894,18 +2211,22 @@ module DEBUGGER__
|
|
1894
2211
|
def self.start nonstop: false, **kw
|
1895
2212
|
CONFIG.set_config(**kw)
|
1896
2213
|
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
2214
|
+
if CONFIG[:open]
|
2215
|
+
open nonstop: nonstop, **kw
|
2216
|
+
else
|
2217
|
+
unless defined? SESSION
|
2218
|
+
require_relative 'local'
|
2219
|
+
initialize_session{ UI_LocalConsole.new }
|
2220
|
+
end
|
2221
|
+
setup_initial_suspend unless nonstop
|
1900
2222
|
end
|
1901
|
-
|
1902
|
-
setup_initial_suspend unless nonstop
|
1903
2223
|
end
|
1904
2224
|
|
1905
2225
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1906
2226
|
CONFIG.set_config(**kw)
|
2227
|
+
require_relative 'server'
|
1907
2228
|
|
1908
|
-
if port || CONFIG[:
|
2229
|
+
if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
|
1909
2230
|
open_tcp host: host, port: (port || 0), nonstop: nonstop
|
1910
2231
|
else
|
1911
2232
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
@@ -1919,7 +2240,7 @@ module DEBUGGER__
|
|
1919
2240
|
if defined? SESSION
|
1920
2241
|
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
1921
2242
|
else
|
1922
|
-
initialize_session UI_TcpServer.new(host: host, port: port)
|
2243
|
+
initialize_session{ UI_TcpServer.new(host: host, port: port) }
|
1923
2244
|
end
|
1924
2245
|
|
1925
2246
|
setup_initial_suspend unless nonstop
|
@@ -1932,7 +2253,7 @@ module DEBUGGER__
|
|
1932
2253
|
if defined? SESSION
|
1933
2254
|
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1934
2255
|
else
|
1935
|
-
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
2256
|
+
initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
|
1936
2257
|
end
|
1937
2258
|
|
1938
2259
|
setup_initial_suspend unless nonstop
|
@@ -1959,13 +2280,26 @@ module DEBUGGER__
|
|
1959
2280
|
end
|
1960
2281
|
|
1961
2282
|
class << self
|
1962
|
-
define_method :initialize_session do |
|
2283
|
+
define_method :initialize_session do |&init_ui|
|
1963
2284
|
DEBUGGER__.info "Session start (pid: #{Process.pid})"
|
1964
|
-
::DEBUGGER__.const_set(:SESSION, Session.new
|
2285
|
+
::DEBUGGER__.const_set(:SESSION, Session.new)
|
2286
|
+
SESSION.activate init_ui.call
|
1965
2287
|
load_rc
|
1966
2288
|
end
|
1967
2289
|
end
|
1968
2290
|
|
2291
|
+
# Exiting control
|
2292
|
+
|
2293
|
+
class << self
|
2294
|
+
def skip_all
|
2295
|
+
@skip_all = true
|
2296
|
+
end
|
2297
|
+
|
2298
|
+
def skip?
|
2299
|
+
@skip_all
|
2300
|
+
end
|
2301
|
+
end
|
2302
|
+
|
1969
2303
|
def self.load_rc
|
1970
2304
|
[[File.expand_path('~/.rdbgrc'), true],
|
1971
2305
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
@@ -1993,34 +2327,53 @@ module DEBUGGER__
|
|
1993
2327
|
end
|
1994
2328
|
end
|
1995
2329
|
|
1996
|
-
|
1997
|
-
undef method_added
|
1998
|
-
def method_added mid; end
|
1999
|
-
def singleton_method_added mid; end
|
2000
|
-
end
|
2330
|
+
# Inspector
|
2001
2331
|
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2332
|
+
SHORT_INSPECT_LENGTH = 40
|
2333
|
+
|
2334
|
+
class LimitedPP
|
2335
|
+
def self.pp(obj, max=80)
|
2336
|
+
out = self.new(max)
|
2337
|
+
catch out do
|
2338
|
+
PP.singleline_pp(obj, out)
|
2339
|
+
end
|
2340
|
+
out.buf
|
2007
2341
|
end
|
2008
|
-
end
|
2009
2342
|
|
2010
|
-
|
2343
|
+
attr_reader :buf
|
2011
2344
|
|
2012
|
-
|
2345
|
+
def initialize max
|
2346
|
+
@max = max
|
2347
|
+
@cnt = 0
|
2348
|
+
@buf = String.new
|
2349
|
+
end
|
2013
2350
|
|
2014
|
-
|
2015
|
-
|
2351
|
+
def <<(other)
|
2352
|
+
@buf << other
|
2016
2353
|
|
2017
|
-
|
2018
|
-
|
2354
|
+
if @buf.size >= @max
|
2355
|
+
@buf = @buf[0..@max] + '...'
|
2356
|
+
throw self
|
2357
|
+
end
|
2358
|
+
end
|
2359
|
+
end
|
2360
|
+
|
2361
|
+
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
|
2362
|
+
if short
|
2363
|
+
LimitedPP.pp(obj, max_length)
|
2364
|
+
else
|
2365
|
+
obj.inspect
|
2366
|
+
end
|
2367
|
+
rescue NoMethodError => e
|
2368
|
+
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
|
2369
|
+
if obj == (r = e.receiver)
|
2370
|
+
"<\##{klass.name}#{oid} does not have \#inspect>"
|
2019
2371
|
else
|
2020
|
-
|
2372
|
+
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
|
2373
|
+
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
|
2021
2374
|
end
|
2022
2375
|
rescue Exception => e
|
2023
|
-
|
2376
|
+
"<#inspect raises #{e.inspect}>"
|
2024
2377
|
end
|
2025
2378
|
|
2026
2379
|
def self.warn msg
|
@@ -2031,18 +2384,28 @@ module DEBUGGER__
|
|
2031
2384
|
log :INFO, msg
|
2032
2385
|
end
|
2033
2386
|
|
2034
|
-
def self.
|
2035
|
-
@logfile = STDERR unless defined? @logfile
|
2036
|
-
|
2387
|
+
def self.check_loglevel level
|
2037
2388
|
lv = LOG_LEVELS[level]
|
2038
|
-
config_lv = LOG_LEVELS[CONFIG[:log_level]
|
2389
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level]]
|
2390
|
+
lv <= config_lv
|
2391
|
+
end
|
2039
2392
|
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2393
|
+
def self.debug(&b)
|
2394
|
+
if check_loglevel :DEBUG
|
2395
|
+
log :DEBUG, b.call
|
2043
2396
|
end
|
2397
|
+
end
|
2398
|
+
|
2399
|
+
def self.log level, msg
|
2400
|
+
if check_loglevel level
|
2401
|
+
@logfile = STDERR unless defined? @logfile
|
2402
|
+
return if @logfile.closed?
|
2403
|
+
|
2404
|
+
if defined? SESSION
|
2405
|
+
pi = SESSION.process_info
|
2406
|
+
process_info = pi ? "[#{pi}]" : nil
|
2407
|
+
end
|
2044
2408
|
|
2045
|
-
if lv <= config_lv
|
2046
2409
|
if level == :WARN
|
2047
2410
|
# :WARN on debugger is general information
|
2048
2411
|
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
@@ -2062,17 +2425,85 @@ module DEBUGGER__
|
|
2062
2425
|
yield
|
2063
2426
|
end
|
2064
2427
|
|
2428
|
+
if File.identical?(__FILE__.upcase, __FILE__.downcase)
|
2429
|
+
# For case insensitive file system (like Windows)
|
2430
|
+
# Note that this check is not enough because case sensitive/insensitive is
|
2431
|
+
# depend on the file system. So this check is only roughly estimation.
|
2432
|
+
|
2433
|
+
def self.compare_path(a, b)
|
2434
|
+
a&.downcase == b&.downcase
|
2435
|
+
end
|
2436
|
+
else
|
2437
|
+
def self.compare_path(a, b)
|
2438
|
+
a == b
|
2439
|
+
end
|
2440
|
+
end
|
2441
|
+
|
2065
2442
|
module ForkInterceptor
|
2066
|
-
|
2067
|
-
|
2443
|
+
if Process.respond_to? :_fork
|
2444
|
+
def _fork
|
2445
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2446
|
+
|
2447
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2068
2448
|
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2449
|
+
super.tap do |pid|
|
2450
|
+
if pid != 0
|
2451
|
+
# after fork: parent
|
2452
|
+
parent_hook.call pid
|
2453
|
+
else
|
2454
|
+
# after fork: child
|
2455
|
+
child_hook.call
|
2456
|
+
end
|
2457
|
+
end
|
2458
|
+
end
|
2459
|
+
else
|
2460
|
+
def fork(&given_block)
|
2461
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2462
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2463
|
+
|
2464
|
+
if given_block
|
2465
|
+
new_block = proc {
|
2466
|
+
# after fork: child
|
2467
|
+
child_hook.call
|
2468
|
+
given_block.call
|
2469
|
+
}
|
2470
|
+
super(&new_block).tap{|pid| parent_hook.call(pid)}
|
2072
2471
|
else
|
2073
|
-
|
2472
|
+
super.tap do |pid|
|
2473
|
+
if pid
|
2474
|
+
# after fork: parent
|
2475
|
+
parent_hook.call pid
|
2476
|
+
else
|
2477
|
+
# after fork: child
|
2478
|
+
child_hook.call
|
2479
|
+
end
|
2480
|
+
end
|
2481
|
+
end
|
2482
|
+
end
|
2483
|
+
end
|
2484
|
+
|
2485
|
+
module DaemonInterceptor
|
2486
|
+
def daemon(*args)
|
2487
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2488
|
+
|
2489
|
+
_, child_hook = __fork_setup_for_debugger(:child)
|
2490
|
+
|
2491
|
+
unless SESSION.remote?
|
2492
|
+
DEBUGGER__.warn "Can't debug the code after Process.daemon locally. Use the remote debugging feature."
|
2493
|
+
end
|
2494
|
+
|
2495
|
+
super.tap do
|
2496
|
+
child_hook.call
|
2074
2497
|
end
|
2075
2498
|
end
|
2499
|
+
end
|
2500
|
+
|
2501
|
+
private def __fork_setup_for_debugger fork_mode = nil
|
2502
|
+
fork_mode ||= CONFIG[:fork_mode]
|
2503
|
+
|
2504
|
+
if fork_mode == :both && CONFIG[:parent_on_fork]
|
2505
|
+
fork_mode = :parent
|
2506
|
+
end
|
2076
2507
|
|
2077
2508
|
parent_pid = Process.pid
|
2078
2509
|
|
@@ -2083,19 +2514,19 @@ module DEBUGGER__
|
|
2083
2514
|
# Do nothing
|
2084
2515
|
}
|
2085
2516
|
child_hook = -> {
|
2086
|
-
DEBUGGER__.
|
2517
|
+
DEBUGGER__.info "Detaching after fork from child process #{Process.pid}"
|
2087
2518
|
SESSION.deactivate
|
2088
2519
|
}
|
2089
2520
|
when :child
|
2090
2521
|
SESSION.before_fork false
|
2091
2522
|
|
2092
2523
|
parent_hook = -> child_pid {
|
2093
|
-
DEBUGGER__.
|
2524
|
+
DEBUGGER__.info "Detaching after fork from parent process #{Process.pid}"
|
2094
2525
|
SESSION.after_fork_parent
|
2095
2526
|
SESSION.deactivate
|
2096
2527
|
}
|
2097
2528
|
child_hook = -> {
|
2098
|
-
DEBUGGER__.
|
2529
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2099
2530
|
SESSION.activate on_fork: true
|
2100
2531
|
}
|
2101
2532
|
when :both
|
@@ -2106,38 +2537,29 @@ module DEBUGGER__
|
|
2106
2537
|
SESSION.after_fork_parent
|
2107
2538
|
}
|
2108
2539
|
child_hook = -> {
|
2109
|
-
DEBUGGER__.
|
2540
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2110
2541
|
SESSION.process_group.after_fork child: true
|
2111
2542
|
SESSION.activate on_fork: true
|
2112
2543
|
}
|
2113
2544
|
end
|
2114
2545
|
|
2115
|
-
|
2116
|
-
new_block = proc {
|
2117
|
-
# after fork: child
|
2118
|
-
child_hook.call
|
2119
|
-
given_block.call
|
2120
|
-
}
|
2121
|
-
pid = super(&new_block)
|
2122
|
-
parent_hook.call(pid)
|
2123
|
-
pid
|
2124
|
-
else
|
2125
|
-
if pid = super
|
2126
|
-
# after fork: parent
|
2127
|
-
parent_hook.call pid
|
2128
|
-
else
|
2129
|
-
# after fork: child
|
2130
|
-
child_hook.call
|
2131
|
-
end
|
2132
|
-
|
2133
|
-
pid
|
2134
|
-
end
|
2546
|
+
return parent_hook, child_hook
|
2135
2547
|
end
|
2136
2548
|
end
|
2137
2549
|
|
2138
2550
|
module TrapInterceptor
|
2139
2551
|
def trap sig, *command, &command_proc
|
2140
|
-
|
2552
|
+
sym =
|
2553
|
+
case sig
|
2554
|
+
when String
|
2555
|
+
sig.to_sym
|
2556
|
+
when Integer
|
2557
|
+
Signal.signame(sig)&.to_sym
|
2558
|
+
else
|
2559
|
+
sig
|
2560
|
+
end
|
2561
|
+
|
2562
|
+
case sym
|
2141
2563
|
when :INT, :SIGINT
|
2142
2564
|
if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
|
2143
2565
|
return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
|
@@ -2148,28 +2570,48 @@ module DEBUGGER__
|
|
2148
2570
|
end
|
2149
2571
|
end
|
2150
2572
|
|
2151
|
-
if
|
2573
|
+
if Process.respond_to? :_fork
|
2574
|
+
module ::Process
|
2575
|
+
class << self
|
2576
|
+
prepend ForkInterceptor
|
2577
|
+
prepend DaemonInterceptor
|
2578
|
+
end
|
2579
|
+
end
|
2580
|
+
|
2581
|
+
# trap
|
2152
2582
|
module ::Kernel
|
2153
|
-
prepend ForkInterceptor
|
2154
2583
|
prepend TrapInterceptor
|
2155
2584
|
end
|
2585
|
+
module ::Signal
|
2586
|
+
class << self
|
2587
|
+
prepend TrapInterceptor
|
2588
|
+
end
|
2589
|
+
end
|
2156
2590
|
else
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2591
|
+
if RUBY_VERSION >= '3.0.0'
|
2592
|
+
module ::Kernel
|
2593
|
+
prepend ForkInterceptor
|
2594
|
+
prepend TrapInterceptor
|
2595
|
+
end
|
2596
|
+
else
|
2597
|
+
class ::Object
|
2598
|
+
include ForkInterceptor
|
2599
|
+
include TrapInterceptor
|
2600
|
+
end
|
2160
2601
|
end
|
2161
|
-
end
|
2162
2602
|
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2603
|
+
module ::Kernel
|
2604
|
+
class << self
|
2605
|
+
prepend ForkInterceptor
|
2606
|
+
prepend TrapInterceptor
|
2607
|
+
end
|
2167
2608
|
end
|
2168
|
-
end
|
2169
2609
|
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2610
|
+
module ::Process
|
2611
|
+
class << self
|
2612
|
+
prepend ForkInterceptor
|
2613
|
+
prepend DaemonInterceptor
|
2614
|
+
end
|
2173
2615
|
end
|
2174
2616
|
end
|
2175
2617
|
|
@@ -2185,10 +2627,17 @@ module Kernel
|
|
2185
2627
|
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
2186
2628
|
|
2187
2629
|
if pre || (do_expr = binding.local_variable_get(:do))
|
2188
|
-
cmds = ['
|
2630
|
+
cmds = ['#debugger', pre, do_expr]
|
2189
2631
|
end
|
2190
2632
|
|
2191
|
-
|
2633
|
+
if ::DEBUGGER__::SESSION.in_subsession?
|
2634
|
+
if cmds
|
2635
|
+
commands = [*cmds[1], *cmds[2]].map{|c| c.split(';;').join("\n")}
|
2636
|
+
::DEBUGGER__::SESSION.add_preset_commands cmds[0], commands, kick: false, continue: false
|
2637
|
+
end
|
2638
|
+
else
|
2639
|
+
loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
|
2640
|
+
end
|
2192
2641
|
self
|
2193
2642
|
end
|
2194
2643
|
|
@@ -2199,3 +2648,12 @@ class Binding
|
|
2199
2648
|
alias break debugger
|
2200
2649
|
alias b debugger
|
2201
2650
|
end
|
2651
|
+
|
2652
|
+
# for Ruby 2.6 compatibility
|
2653
|
+
unless method(:p).unbind.respond_to? :bind_call
|
2654
|
+
class UnboundMethod
|
2655
|
+
def bind_call(obj, *args)
|
2656
|
+
self.bind(obj).call(*args)
|
2657
|
+
end
|
2658
|
+
end
|
2659
|
+
end
|