debug 1.6.1 → 1.9.1
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 +22 -10
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +84 -55
- data/Rakefile +8 -3
- data/TODO.md +8 -8
- data/debug.gemspec +3 -3
- data/exe/rdbg +19 -4
- data/ext/debug/debug.c +33 -5
- data/ext/debug/extconf.rb +1 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +15 -11
- data/lib/debug/client.rb +26 -8
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +69 -23
- data/lib/debug/console.rb +8 -29
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +9 -0
- data/lib/debug/irb_integration.rb +27 -0
- data/lib/debug/local.rb +16 -10
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +2 -1
- data/lib/debug/server.rb +32 -27
- data/lib/debug/server_cdp.rb +360 -155
- data/lib/debug/server_dap.rb +330 -197
- data/lib/debug/session.rb +494 -258
- data/lib/debug/source_repository.rb +41 -21
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +241 -82
- data/lib/debug/tracer.rb +4 -5
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +50 -44
- metadata +13 -10
data/lib/debug/session.rb
CHANGED
@@ -22,8 +22,9 @@ end
|
|
22
22
|
# restore RUBYOPT
|
23
23
|
if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
|
24
24
|
(rubyopt = ENV['RUBYOPT']) &&
|
25
|
-
rubyopt.
|
26
|
-
|
25
|
+
rubyopt.end_with?(added_opt)
|
26
|
+
|
27
|
+
ENV['RUBYOPT'] = rubyopt.delete_suffix(added_opt)
|
27
28
|
ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
|
28
29
|
end
|
29
30
|
|
@@ -81,14 +82,16 @@ class RubyVM::InstructionSequence
|
|
81
82
|
def first_line
|
82
83
|
self.to_a[4][:code_location][0]
|
83
84
|
end unless method_defined?(:first_line)
|
84
|
-
end
|
85
|
+
end if defined?(RubyVM::InstructionSequence)
|
85
86
|
|
86
87
|
module DEBUGGER__
|
87
|
-
|
88
|
+
PresetCommands = Struct.new(:commands, :source, :auto_continue)
|
89
|
+
SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)
|
90
|
+
|
88
91
|
class PostmortemError < RuntimeError; end
|
89
92
|
|
90
93
|
class Session
|
91
|
-
attr_reader :intercepted_sigint_cmd, :process_group
|
94
|
+
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
|
92
95
|
|
93
96
|
include Color
|
94
97
|
|
@@ -115,6 +118,7 @@ module DEBUGGER__
|
|
115
118
|
@intercepted_sigint_cmd = 'DEFAULT'
|
116
119
|
@process_group = ProcessGroup.new
|
117
120
|
@subsession_stack = []
|
121
|
+
@subsession_id = 0
|
118
122
|
|
119
123
|
@frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
|
120
124
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
@@ -124,33 +128,58 @@ module DEBUGGER__
|
|
124
128
|
@obj_map = {} # { object_id => ... } for CDP
|
125
129
|
|
126
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
|
+
|
127
138
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
128
|
-
|
139
|
+
eval_script = tp.eval_script unless @has_keep_script_lines
|
140
|
+
ThreadClient.current.on_load tp.instruction_sequence, eval_script
|
129
141
|
}
|
130
142
|
@tp_load_script.enable
|
131
143
|
|
132
144
|
@thread_stopper = thread_stopper
|
133
145
|
self.postmortem = CONFIG[:postmortem]
|
146
|
+
|
147
|
+
register_default_command
|
134
148
|
end
|
135
149
|
|
136
150
|
def active?
|
137
151
|
!@q_evt.closed?
|
138
152
|
end
|
139
153
|
|
140
|
-
def
|
141
|
-
@
|
154
|
+
def remote?
|
155
|
+
@ui.remote?
|
156
|
+
end
|
157
|
+
|
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
|
163
|
+
else
|
164
|
+
false
|
165
|
+
end
|
142
166
|
end
|
143
167
|
|
144
168
|
def activate ui = nil, on_fork: false
|
145
169
|
@ui = ui if ui
|
146
170
|
|
147
171
|
@tp_thread_begin&.disable
|
172
|
+
@tp_thread_end&.disable
|
148
173
|
@tp_thread_begin = nil
|
149
|
-
|
174
|
+
@tp_thread_end = nil
|
150
175
|
@ui.activate self, on_fork: on_fork
|
151
176
|
|
152
177
|
q = Queue.new
|
178
|
+
first_q = Queue.new
|
153
179
|
@session_server = Thread.new do
|
180
|
+
# make sure `@session_server` is assigned
|
181
|
+
first_q.pop; first_q = nil
|
182
|
+
|
154
183
|
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
155
184
|
Thread.current.abort_on_exception = true
|
156
185
|
|
@@ -168,10 +197,21 @@ module DEBUGGER__
|
|
168
197
|
end
|
169
198
|
@tp_thread_begin.enable
|
170
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
|
+
|
205
|
+
if CONFIG[:irb_console] && !CONFIG[:open]
|
206
|
+
require_relative "irb_integration"
|
207
|
+
thc.activate_irb_integration
|
208
|
+
end
|
209
|
+
|
171
210
|
# session start
|
172
211
|
q << true
|
173
212
|
session_server_main
|
174
213
|
end
|
214
|
+
first_q << :ok
|
175
215
|
|
176
216
|
q.pop
|
177
217
|
end
|
@@ -181,6 +221,7 @@ module DEBUGGER__
|
|
181
221
|
@thread_stopper.disable
|
182
222
|
@tp_load_script.disable
|
183
223
|
@tp_thread_begin.disable
|
224
|
+
@tp_thread_end.disable
|
184
225
|
@bps.each_value{|bp| bp.disable}
|
185
226
|
@th_clients.each_value{|thc| thc.close}
|
186
227
|
@tracers.values.each{|t| t.disable}
|
@@ -192,6 +233,16 @@ module DEBUGGER__
|
|
192
233
|
def reset_ui ui
|
193
234
|
@ui.deactivate
|
194
235
|
@ui = ui
|
236
|
+
|
237
|
+
# activate new ui
|
238
|
+
@tp_thread_begin.disable
|
239
|
+
@tp_thread_end.disable
|
240
|
+
@ui.activate self
|
241
|
+
if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
|
242
|
+
thc.mark_as_management
|
243
|
+
end
|
244
|
+
@tp_thread_begin.enable
|
245
|
+
@tp_thread_end.enable
|
195
246
|
end
|
196
247
|
|
197
248
|
def pop_event
|
@@ -210,20 +261,35 @@ module DEBUGGER__
|
|
210
261
|
@tc << req
|
211
262
|
end
|
212
263
|
|
264
|
+
def request_tc_with_restarted_threads(req)
|
265
|
+
restart_all_threads
|
266
|
+
request_tc(req)
|
267
|
+
end
|
268
|
+
|
269
|
+
def request_eval type, src
|
270
|
+
request_tc_with_restarted_threads [:eval, type, src]
|
271
|
+
end
|
272
|
+
|
213
273
|
def process_event evt
|
214
274
|
# variable `@internal_info` is only used for test
|
215
|
-
|
275
|
+
tc, output, ev, @internal_info, *ev_args = evt
|
216
276
|
|
217
277
|
output.each{|str| @ui.puts str} if ev != :suspend
|
218
278
|
|
219
|
-
|
220
|
-
|
221
|
-
|
279
|
+
# special event, tc is nil
|
280
|
+
# and we don't want to set @tc to the newly created thread's ThreadClient
|
281
|
+
if ev == :thread_begin
|
222
282
|
th = ev_args.shift
|
223
283
|
q = ev_args.shift
|
224
284
|
on_thread_begin th
|
225
285
|
q << true
|
226
286
|
|
287
|
+
return
|
288
|
+
end
|
289
|
+
|
290
|
+
@tc = tc
|
291
|
+
|
292
|
+
case ev
|
227
293
|
when :init
|
228
294
|
enter_subsession
|
229
295
|
wait_command_loop
|
@@ -241,7 +307,7 @@ module DEBUGGER__
|
|
241
307
|
|
242
308
|
when :suspend
|
243
309
|
enter_subsession if ev_args.first != :replay
|
244
|
-
output.each{|str| @ui.puts str}
|
310
|
+
output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?
|
245
311
|
|
246
312
|
case ev_args.first
|
247
313
|
when :breakpoint
|
@@ -262,7 +328,7 @@ module DEBUGGER__
|
|
262
328
|
if @displays.empty?
|
263
329
|
wait_command_loop
|
264
330
|
else
|
265
|
-
|
331
|
+
request_eval :display, @displays
|
266
332
|
end
|
267
333
|
when :result
|
268
334
|
raise "[BUG] not in subsession" if @subsession_stack.empty?
|
@@ -277,6 +343,7 @@ module DEBUGGER__
|
|
277
343
|
end
|
278
344
|
end
|
279
345
|
|
346
|
+
stop_all_threads
|
280
347
|
when :method_breakpoint, :watch_breakpoint
|
281
348
|
bp = ev_args[1]
|
282
349
|
if bp
|
@@ -290,17 +357,15 @@ module DEBUGGER__
|
|
290
357
|
obj_inspect = ev_args[2]
|
291
358
|
opt = ev_args[3]
|
292
359
|
add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
360
|
+
stop_all_threads
|
293
361
|
else
|
294
|
-
|
362
|
+
stop_all_threads
|
295
363
|
end
|
296
364
|
|
297
365
|
wait_command_loop
|
298
366
|
|
299
|
-
when :
|
300
|
-
|
301
|
-
wait_command_loop
|
302
|
-
when :cdp_result
|
303
|
-
cdp_event ev_args
|
367
|
+
when :protocol_result
|
368
|
+
process_protocol_result ev_args
|
304
369
|
wait_command_loop
|
305
370
|
end
|
306
371
|
end
|
@@ -316,7 +381,7 @@ module DEBUGGER__
|
|
316
381
|
if @preset_command && !@preset_command.commands.empty?
|
317
382
|
@preset_command.commands += cs
|
318
383
|
else
|
319
|
-
@preset_command =
|
384
|
+
@preset_command = PresetCommands.new(cs, name, continue)
|
320
385
|
end
|
321
386
|
|
322
387
|
ThreadClient.current.on_init name if kick
|
@@ -389,97 +454,121 @@ module DEBUGGER__
|
|
389
454
|
end
|
390
455
|
end
|
391
456
|
|
392
|
-
def
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
else
|
397
|
-
return :retry
|
398
|
-
end
|
399
|
-
else
|
400
|
-
@repl_prev_line = line
|
401
|
-
end
|
457
|
+
private def register_command *names,
|
458
|
+
repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
|
459
|
+
&b
|
460
|
+
cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
|
402
461
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
462
|
+
names.each{|name|
|
463
|
+
@commands[name] = cmd
|
464
|
+
}
|
465
|
+
end
|
407
466
|
|
408
|
-
|
467
|
+
def register_default_command
|
409
468
|
### Control flow
|
410
469
|
|
411
470
|
# * `s[tep]`
|
412
471
|
# * Step in. Resume the program until next breakable point.
|
413
472
|
# * `s[tep] <n>`
|
414
473
|
# * Step in, resume the program at `<n>`th breakable point.
|
415
|
-
|
416
|
-
|
417
|
-
|
474
|
+
register_command 's', 'step',
|
475
|
+
repeat: true,
|
476
|
+
cancel_auto_continue: true,
|
477
|
+
postmortem: false do |arg|
|
418
478
|
step_command :in, arg
|
479
|
+
end
|
419
480
|
|
420
481
|
# * `n[ext]`
|
421
482
|
# * Step over. Resume the program until next line.
|
422
483
|
# * `n[ext] <n>`
|
423
484
|
# * Step over, same as `step <n>`.
|
424
|
-
|
425
|
-
|
426
|
-
|
485
|
+
register_command 'n', 'next',
|
486
|
+
repeat: true,
|
487
|
+
cancel_auto_continue: true,
|
488
|
+
postmortem: false do |arg|
|
427
489
|
step_command :next, arg
|
490
|
+
end
|
428
491
|
|
429
492
|
# * `fin[ish]`
|
430
493
|
# * Finish this frame. Resume the program until the current frame is finished.
|
431
494
|
# * `fin[ish] <n>`
|
432
495
|
# * Finish `<n>`th frames.
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
496
|
+
register_command 'fin', 'finish',
|
497
|
+
repeat: true,
|
498
|
+
cancel_auto_continue: true,
|
499
|
+
postmortem: false do |arg|
|
437
500
|
if arg&.to_i == 0
|
438
501
|
raise 'finish command with 0 does not make sense.'
|
439
502
|
end
|
440
503
|
|
441
504
|
step_command :finish, arg
|
505
|
+
end
|
442
506
|
|
443
|
-
# * `
|
507
|
+
# * `u[ntil]`
|
508
|
+
# * Similar to `next` command, but only stop later lines or the end of the current frame.
|
509
|
+
# * Similar to gdb's `advance` command.
|
510
|
+
# * `u[ntil] <[file:]line>`
|
511
|
+
# * Run til the program reaches given location or the end of the current frame.
|
512
|
+
# * `u[ntil] <name>`
|
513
|
+
# * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
|
514
|
+
register_command 'u', 'until',
|
515
|
+
repeat: true,
|
516
|
+
cancel_auto_continue: true,
|
517
|
+
postmortem: false do |arg|
|
518
|
+
|
519
|
+
step_command :until, arg
|
520
|
+
end
|
521
|
+
|
522
|
+
# * `c` or `cont` or `continue`
|
444
523
|
# * Resume the program.
|
445
|
-
|
446
|
-
|
524
|
+
register_command 'c', 'cont', 'continue',
|
525
|
+
repeat: true,
|
526
|
+
cancel_auto_continue: true do |arg|
|
447
527
|
leave_subsession :continue
|
528
|
+
end
|
448
529
|
|
449
530
|
# * `q[uit]` or `Ctrl-D`
|
450
531
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
451
|
-
|
532
|
+
register_command 'q', 'quit' do |arg|
|
452
533
|
if ask 'Really quit?'
|
453
|
-
@ui.quit arg.to_i
|
534
|
+
@ui.quit arg.to_i do
|
535
|
+
request_tc :quit
|
536
|
+
end
|
454
537
|
leave_subsession :continue
|
455
538
|
else
|
456
|
-
|
539
|
+
next :retry
|
457
540
|
end
|
541
|
+
end
|
458
542
|
|
459
543
|
# * `q[uit]!`
|
460
544
|
# * Same as q[uit] but without the confirmation prompt.
|
461
|
-
|
462
|
-
@ui.quit arg.to_i
|
463
|
-
|
545
|
+
register_command 'q!', 'quit!', unsafe: false do |arg|
|
546
|
+
@ui.quit arg.to_i do
|
547
|
+
request_tc :quit
|
548
|
+
end
|
549
|
+
leave_subsession :continue
|
550
|
+
end
|
464
551
|
|
465
552
|
# * `kill`
|
466
553
|
# * Stop the debuggee process with `Kernel#exit!`.
|
467
|
-
|
554
|
+
register_command 'kill' do |arg|
|
468
555
|
if ask 'Really kill?'
|
469
556
|
exit! (arg || 1).to_i
|
470
557
|
else
|
471
|
-
|
558
|
+
next :retry
|
472
559
|
end
|
560
|
+
end
|
473
561
|
|
474
562
|
# * `kill!`
|
475
563
|
# * Same as kill but without the confirmation prompt.
|
476
|
-
|
564
|
+
register_command 'kill!', unsafe: false do |arg|
|
477
565
|
exit! (arg || 1).to_i
|
566
|
+
end
|
478
567
|
|
479
568
|
# * `sigint`
|
480
569
|
# * Execute SIGINT handler registered by the debuggee.
|
481
570
|
# * Note that this command should be used just after stop by `SIGINT`.
|
482
|
-
|
571
|
+
register_command 'sigint' do
|
483
572
|
begin
|
484
573
|
case cmd = @intercepted_sigint_cmd
|
485
574
|
when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
|
@@ -495,8 +584,9 @@ module DEBUGGER__
|
|
495
584
|
rescue Exception => e
|
496
585
|
@ui.puts "Exception: #{e}"
|
497
586
|
@ui.puts e.backtrace.map{|line| " #{e}"}
|
498
|
-
|
587
|
+
next :retry
|
499
588
|
end
|
589
|
+
end
|
500
590
|
|
501
591
|
### Breakpoint
|
502
592
|
|
@@ -521,22 +611,21 @@ module DEBUGGER__
|
|
521
611
|
# * `b[reak] if: <expr>`
|
522
612
|
# * break if: `<expr>` is true at any lines.
|
523
613
|
# * Note that this feature is super slow.
|
524
|
-
|
525
|
-
check_postmortem
|
526
|
-
|
614
|
+
register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
|
527
615
|
if arg == nil
|
528
616
|
show_bps
|
529
|
-
|
617
|
+
next :retry
|
530
618
|
else
|
531
619
|
case bp = repl_add_breakpoint(arg)
|
532
620
|
when :noretry
|
533
621
|
when nil
|
534
|
-
|
622
|
+
next :retry
|
535
623
|
else
|
536
624
|
show_bps bp
|
537
|
-
|
625
|
+
next :retry
|
538
626
|
end
|
539
627
|
end
|
628
|
+
end
|
540
629
|
|
541
630
|
# * `catch <Error>`
|
542
631
|
# * Set breakpoint on raising `<Error>`.
|
@@ -548,16 +637,16 @@ module DEBUGGER__
|
|
548
637
|
# * stops and run `<command>`, and continue.
|
549
638
|
# * `catch ... path: <path>`
|
550
639
|
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
|
551
|
-
|
552
|
-
check_postmortem
|
553
|
-
|
640
|
+
register_command 'catch', postmortem: false, unsafe: false do |arg|
|
554
641
|
if arg
|
555
642
|
bp = repl_add_catch_breakpoint arg
|
556
643
|
show_bps bp if bp
|
557
644
|
else
|
558
645
|
show_bps
|
559
646
|
end
|
560
|
-
|
647
|
+
|
648
|
+
:retry
|
649
|
+
end
|
561
650
|
|
562
651
|
# * `watch @ivar`
|
563
652
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
@@ -570,24 +659,20 @@ module DEBUGGER__
|
|
570
659
|
# * stops and run `<command>`, and continue.
|
571
660
|
# * `watch ... path: <path>`
|
572
661
|
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
|
573
|
-
|
574
|
-
check_postmortem
|
575
|
-
|
662
|
+
register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
|
576
663
|
if arg && arg.match?(/\A@\w+/)
|
577
664
|
repl_add_watch_breakpoint(arg)
|
578
665
|
else
|
579
666
|
show_bps
|
580
|
-
|
667
|
+
:retry
|
581
668
|
end
|
669
|
+
end
|
582
670
|
|
583
671
|
# * `del[ete]`
|
584
672
|
# * delete all breakpoints.
|
585
673
|
# * `del[ete] <bpnum>`
|
586
674
|
# * delete specified breakpoint.
|
587
|
-
|
588
|
-
check_postmortem
|
589
|
-
|
590
|
-
bp =
|
675
|
+
register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
|
591
676
|
case arg
|
592
677
|
when nil
|
593
678
|
show_bps
|
@@ -595,12 +680,13 @@ module DEBUGGER__
|
|
595
680
|
delete_bp
|
596
681
|
end
|
597
682
|
when /\d+/
|
598
|
-
delete_bp arg.to_i
|
683
|
+
bp = delete_bp arg.to_i
|
599
684
|
else
|
600
685
|
nil
|
601
686
|
end
|
602
687
|
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
|
603
|
-
|
688
|
+
:retry
|
689
|
+
end
|
604
690
|
|
605
691
|
### Information
|
606
692
|
|
@@ -612,19 +698,20 @@ module DEBUGGER__
|
|
612
698
|
# * Only shows frames with method name or location info that matches `/regexp/`.
|
613
699
|
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
|
614
700
|
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
|
615
|
-
|
701
|
+
register_command 'bt', 'backtrace', unsafe: false do |arg|
|
616
702
|
case arg
|
617
703
|
when /\A(\d+)\z/
|
618
|
-
|
704
|
+
request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
|
619
705
|
when /\A\/(.*)\/\z/
|
620
706
|
pattern = $1
|
621
|
-
|
707
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
|
622
708
|
when /\A(\d+)\s+\/(.*)\/\z/
|
623
709
|
max, pattern = $1, $2
|
624
|
-
|
710
|
+
request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
625
711
|
else
|
626
|
-
|
712
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
|
627
713
|
end
|
714
|
+
end
|
628
715
|
|
629
716
|
# * `l[ist]`
|
630
717
|
# * Show current frame's source code.
|
@@ -633,7 +720,7 @@ module DEBUGGER__
|
|
633
720
|
# * Show predecessor lines as opposed to the `list` command.
|
634
721
|
# * `l[ist] <start>` or `l[ist] <start>-<end>`
|
635
722
|
# * Show current frame's source code from the line <start> to <end> if given.
|
636
|
-
|
723
|
+
register_command 'l', 'list', repeat: true, unsafe: false do |arg|
|
637
724
|
case arg ? arg.strip : nil
|
638
725
|
when /\A(\d+)\z/
|
639
726
|
request_tc [:show, :list, {start_line: arg.to_i - 1}]
|
@@ -645,45 +732,63 @@ module DEBUGGER__
|
|
645
732
|
request_tc [:show, :list]
|
646
733
|
else
|
647
734
|
@ui.puts "Can not handle list argument: #{arg}"
|
648
|
-
|
735
|
+
:retry
|
649
736
|
end
|
737
|
+
end
|
738
|
+
|
739
|
+
# * `whereami`
|
740
|
+
# * Show the current frame with source code.
|
741
|
+
register_command 'whereami', unsafe: false do
|
742
|
+
request_tc [:show, :whereami]
|
743
|
+
end
|
650
744
|
|
651
745
|
# * `edit`
|
652
746
|
# * Open the current file on the editor (use `EDITOR` environment variable).
|
653
747
|
# * Note that edited file will not be reloaded.
|
654
748
|
# * `edit <file>`
|
655
749
|
# * Open <file> on the editor.
|
656
|
-
|
750
|
+
register_command 'edit' do |arg|
|
657
751
|
if @ui.remote?
|
658
752
|
@ui.puts "not supported on the remote console."
|
659
|
-
|
753
|
+
next :retry
|
660
754
|
end
|
661
755
|
|
662
756
|
begin
|
663
757
|
arg = resolve_path(arg) if arg
|
664
758
|
rescue Errno::ENOENT
|
665
759
|
@ui.puts "not found: #{arg}"
|
666
|
-
|
760
|
+
next :retry
|
667
761
|
end
|
668
762
|
|
669
763
|
request_tc [:show, :edit, arg]
|
764
|
+
end
|
765
|
+
|
766
|
+
info_subcommands = nil
|
767
|
+
info_subcommands_abbrev = nil
|
670
768
|
|
671
769
|
# * `i[nfo]`
|
672
|
-
#
|
673
|
-
# * `i[nfo]
|
770
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
771
|
+
# * `i[nfo]` <subcommand>
|
772
|
+
# * `info` has the following sub-commands.
|
773
|
+
# * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
|
774
|
+
# * `i[nfo] l or locals or local_variables`
|
674
775
|
# * Show information about the current frame (local variables)
|
675
|
-
# * It includes `self` as `%self` and a return value as
|
676
|
-
# * `i[nfo] i
|
776
|
+
# * It includes `self` as `%self` and a return value as `_return`.
|
777
|
+
# * `i[nfo] i or ivars or instance_variables`
|
677
778
|
# * Show information about instance variables about `self`.
|
678
|
-
#
|
779
|
+
# * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
|
780
|
+
# * `i[nfo] c or consts or constants`
|
679
781
|
# * Show information about accessible constants except toplevel constants.
|
680
|
-
#
|
782
|
+
# * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
|
783
|
+
# * `i[nfo] g or globals or global_variables`
|
681
784
|
# * Show information about global variables
|
785
|
+
# * `i[nfo] th or threads`
|
786
|
+
# * Show all threads (same as `th[read]`).
|
787
|
+
# * `i[nfo] b or breakpoints or w or watchpoints`
|
788
|
+
# * Show all breakpoints and watchpoints.
|
682
789
|
# * `i[nfo] ... /regexp/`
|
683
790
|
# * Filter the output with `/regexp/`.
|
684
|
-
|
685
|
-
# * Show all threads (same as `th[read]`).
|
686
|
-
when 'i', 'info'
|
791
|
+
register_command 'i', 'info', unsafe: false do |arg|
|
687
792
|
if /\/(.+)\/\z/ =~ arg
|
688
793
|
pat = Regexp.compile($1)
|
689
794
|
sub = $~.pre_match.strip
|
@@ -691,63 +796,98 @@ module DEBUGGER__
|
|
691
796
|
sub = arg
|
692
797
|
end
|
693
798
|
|
799
|
+
if /\A(.+?)\b(.+)/ =~ sub
|
800
|
+
sub = $1
|
801
|
+
opt = $2.strip
|
802
|
+
opt = nil if opt.empty?
|
803
|
+
end
|
804
|
+
|
805
|
+
if sub && !info_subcommands
|
806
|
+
info_subcommands = {
|
807
|
+
locals: %w[ locals local_variables ],
|
808
|
+
ivars: %w[ ivars instance_variables ],
|
809
|
+
consts: %w[ consts constants ],
|
810
|
+
globals:%w[ globals global_variables ],
|
811
|
+
threads:%w[ threads ],
|
812
|
+
breaks: %w[ breakpoints ],
|
813
|
+
watchs: %w[ watchpoints ],
|
814
|
+
}
|
815
|
+
|
816
|
+
require_relative 'abbrev_command'
|
817
|
+
info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
|
818
|
+
end
|
819
|
+
|
820
|
+
if sub
|
821
|
+
sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
|
822
|
+
# note: unreached now
|
823
|
+
@ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
694
827
|
case sub
|
695
828
|
when nil
|
696
|
-
|
697
|
-
when
|
698
|
-
|
699
|
-
when
|
700
|
-
|
701
|
-
when
|
702
|
-
|
703
|
-
when
|
704
|
-
|
705
|
-
when
|
829
|
+
request_tc_with_restarted_threads [:show, :default, pat] # something useful
|
830
|
+
when :locals
|
831
|
+
request_tc_with_restarted_threads [:show, :locals, pat]
|
832
|
+
when :ivars
|
833
|
+
request_tc_with_restarted_threads [:show, :ivars, pat, opt]
|
834
|
+
when :consts
|
835
|
+
request_tc_with_restarted_threads [:show, :consts, pat, opt]
|
836
|
+
when :globals
|
837
|
+
request_tc_with_restarted_threads [:show, :globals, pat]
|
838
|
+
when :threads
|
706
839
|
thread_list
|
707
|
-
|
840
|
+
:retry
|
841
|
+
when :breaks, :watchs
|
842
|
+
show_bps
|
843
|
+
:retry
|
708
844
|
else
|
709
845
|
@ui.puts "unrecognized argument for info command: #{arg}"
|
710
846
|
show_help 'info'
|
711
|
-
|
847
|
+
:retry
|
712
848
|
end
|
849
|
+
end
|
713
850
|
|
714
851
|
# * `o[utline]` or `ls`
|
715
852
|
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
716
853
|
# * `o[utline] <expr>` or `ls <expr>`
|
717
854
|
# * Show you available methods and instance variables of the given object.
|
718
855
|
# * If the object is a class/module, it also lists its constants.
|
719
|
-
|
720
|
-
|
856
|
+
register_command 'outline', 'o', 'ls', unsafe: false do |arg|
|
857
|
+
request_tc_with_restarted_threads [:show, :outline, arg]
|
858
|
+
end
|
721
859
|
|
722
860
|
# * `display`
|
723
861
|
# * Show display setting.
|
724
862
|
# * `display <expr>`
|
725
863
|
# * Show the result of `<expr>` at every suspended timing.
|
726
|
-
|
864
|
+
register_command 'display', postmortem: false do |arg|
|
727
865
|
if arg && !arg.empty?
|
728
866
|
@displays << arg
|
729
|
-
|
867
|
+
request_eval :try_display, @displays
|
730
868
|
else
|
731
|
-
|
869
|
+
request_eval :display, @displays
|
732
870
|
end
|
871
|
+
end
|
733
872
|
|
734
873
|
# * `undisplay`
|
735
874
|
# * Remove all display settings.
|
736
875
|
# * `undisplay <displaynum>`
|
737
876
|
# * Remove a specified display setting.
|
738
|
-
|
877
|
+
register_command 'undisplay', postmortem: false, unsafe: false do |arg|
|
739
878
|
case arg
|
740
879
|
when /(\d+)/
|
741
880
|
if @displays[n = $1.to_i]
|
742
881
|
@displays.delete_at n
|
743
882
|
end
|
744
|
-
|
883
|
+
request_eval :display, @displays
|
745
884
|
when nil
|
746
885
|
if ask "clear all?", 'N'
|
747
886
|
@displays.clear
|
748
887
|
end
|
749
|
-
|
888
|
+
:retry
|
750
889
|
end
|
890
|
+
end
|
751
891
|
|
752
892
|
### Frame control
|
753
893
|
|
@@ -755,53 +895,58 @@ module DEBUGGER__
|
|
755
895
|
# * Show the current frame.
|
756
896
|
# * `f[rame] <framenum>`
|
757
897
|
# * Specify a current frame. Evaluation are run on specified frame.
|
758
|
-
|
898
|
+
register_command 'frame', 'f', unsafe: false do |arg|
|
759
899
|
request_tc [:frame, :set, arg]
|
900
|
+
end
|
760
901
|
|
761
902
|
# * `up`
|
762
903
|
# * Specify the upper frame.
|
763
|
-
|
904
|
+
register_command 'up', repeat: true, unsafe: false do |arg|
|
764
905
|
request_tc [:frame, :up]
|
906
|
+
end
|
765
907
|
|
766
908
|
# * `down`
|
767
909
|
# * Specify the lower frame.
|
768
|
-
|
910
|
+
register_command 'down', repeat: true, unsafe: false do |arg|
|
769
911
|
request_tc [:frame, :down]
|
912
|
+
end
|
770
913
|
|
771
914
|
### Evaluate
|
772
915
|
|
773
916
|
# * `p <expr>`
|
774
917
|
# * Evaluate like `p <expr>` on the current frame.
|
775
|
-
|
776
|
-
|
918
|
+
register_command 'p' do |arg|
|
919
|
+
request_eval :p, arg.to_s
|
920
|
+
end
|
777
921
|
|
778
922
|
# * `pp <expr>`
|
779
923
|
# * Evaluate like `pp <expr>` on the current frame.
|
780
|
-
|
781
|
-
|
924
|
+
register_command 'pp' do |arg|
|
925
|
+
request_eval :pp, arg.to_s
|
926
|
+
end
|
782
927
|
|
783
928
|
# * `eval <expr>`
|
784
929
|
# * Evaluate `<expr>` on the current frame.
|
785
|
-
|
930
|
+
register_command 'eval', 'call' do |arg|
|
786
931
|
if arg == nil || arg.empty?
|
787
932
|
show_help 'eval'
|
788
933
|
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
789
|
-
|
934
|
+
:retry
|
790
935
|
else
|
791
|
-
|
936
|
+
request_eval :call, arg
|
792
937
|
end
|
938
|
+
end
|
793
939
|
|
794
940
|
# * `irb`
|
795
941
|
# * Invoke `irb` on the current frame.
|
796
|
-
|
942
|
+
register_command 'irb' do |arg|
|
797
943
|
if @ui.remote?
|
798
|
-
@ui.puts "not supported on the remote console."
|
799
|
-
|
944
|
+
@ui.puts "\nIRB is not supported on the remote console."
|
945
|
+
:retry
|
946
|
+
else
|
947
|
+
request_eval :irb, nil
|
800
948
|
end
|
801
|
-
|
802
|
-
|
803
|
-
# don't repeat irb command
|
804
|
-
@repl_prev_line = nil
|
949
|
+
end
|
805
950
|
|
806
951
|
### Trace
|
807
952
|
# * `trace`
|
@@ -822,7 +967,7 @@ module DEBUGGER__
|
|
822
967
|
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
823
968
|
# * `trace off [line|call|pass]`
|
824
969
|
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
825
|
-
|
970
|
+
register_command 'trace', postmortem: false, unsafe: false do |arg|
|
826
971
|
if (re = /\s+into:\s*(.+)/) =~ arg
|
827
972
|
into = $1
|
828
973
|
arg.sub!(re, '')
|
@@ -840,22 +985,22 @@ module DEBUGGER__
|
|
840
985
|
@ui.puts "* \##{i} #{t}"
|
841
986
|
}
|
842
987
|
@ui.puts
|
843
|
-
|
988
|
+
:retry
|
844
989
|
|
845
990
|
when /\Aline\z/
|
846
991
|
add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
|
847
|
-
|
992
|
+
:retry
|
848
993
|
|
849
994
|
when /\Acall\z/
|
850
995
|
add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
|
851
|
-
|
996
|
+
:retry
|
852
997
|
|
853
998
|
when /\Aexception\z/
|
854
999
|
add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
855
|
-
|
1000
|
+
:retry
|
856
1001
|
|
857
1002
|
when /\Aobject\s+(.+)/
|
858
|
-
|
1003
|
+
request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
859
1004
|
|
860
1005
|
when /\Aoff\s+(\d+)\z/
|
861
1006
|
if t = @tracers.values[$1.to_i]
|
@@ -864,7 +1009,7 @@ module DEBUGGER__
|
|
864
1009
|
else
|
865
1010
|
@ui.puts "Unmatched: #{$1}"
|
866
1011
|
end
|
867
|
-
|
1012
|
+
:retry
|
868
1013
|
|
869
1014
|
when /\Aoff(\s+(line|call|exception|object))?\z/
|
870
1015
|
@tracers.values.each{|t|
|
@@ -873,12 +1018,13 @@ module DEBUGGER__
|
|
873
1018
|
@ui.puts "Disable #{t.to_s}"
|
874
1019
|
end
|
875
1020
|
}
|
876
|
-
|
1021
|
+
:retry
|
877
1022
|
|
878
1023
|
else
|
879
1024
|
@ui.puts "Unknown trace option: #{arg.inspect}"
|
880
|
-
|
1025
|
+
:retry
|
881
1026
|
end
|
1027
|
+
end
|
882
1028
|
|
883
1029
|
# Record
|
884
1030
|
# * `record`
|
@@ -890,14 +1036,15 @@ module DEBUGGER__
|
|
890
1036
|
# * `s[tep]` does stepping forward with the last log.
|
891
1037
|
# * `step reset`
|
892
1038
|
# * Stop replay .
|
893
|
-
|
1039
|
+
register_command 'record', postmortem: false, unsafe: false do |arg|
|
894
1040
|
case arg
|
895
1041
|
when nil, 'on', 'off'
|
896
1042
|
request_tc [:record, arg&.to_sym]
|
897
1043
|
else
|
898
1044
|
@ui.puts "unknown command: #{arg}"
|
899
|
-
|
1045
|
+
:retry
|
900
1046
|
end
|
1047
|
+
end
|
901
1048
|
|
902
1049
|
### Thread control
|
903
1050
|
|
@@ -905,7 +1052,7 @@ module DEBUGGER__
|
|
905
1052
|
# * Show all threads.
|
906
1053
|
# * `th[read] <thnum>`
|
907
1054
|
# * Switch thread specified by `<thnum>`.
|
908
|
-
|
1055
|
+
register_command 'th', 'thread', unsafe: false do |arg|
|
909
1056
|
case arg
|
910
1057
|
when nil, 'list', 'l'
|
911
1058
|
thread_list
|
@@ -914,7 +1061,8 @@ module DEBUGGER__
|
|
914
1061
|
else
|
915
1062
|
@ui.puts "unknown thread command: #{arg}"
|
916
1063
|
end
|
917
|
-
|
1064
|
+
:retry
|
1065
|
+
end
|
918
1066
|
|
919
1067
|
### Configuration
|
920
1068
|
# * `config`
|
@@ -927,13 +1075,14 @@ module DEBUGGER__
|
|
927
1075
|
# * Append `<val>` to `<name>` if it is an array.
|
928
1076
|
# * `config unset <name>`
|
929
1077
|
# * Set <name> to default.
|
930
|
-
|
1078
|
+
register_command 'config', unsafe: false do |arg|
|
931
1079
|
config_command arg
|
932
|
-
|
1080
|
+
:retry
|
1081
|
+
end
|
933
1082
|
|
934
1083
|
# * `source <file>`
|
935
1084
|
# * Evaluate lines in `<file>` as debug commands.
|
936
|
-
|
1085
|
+
register_command 'source' do |arg|
|
937
1086
|
if arg
|
938
1087
|
begin
|
939
1088
|
cmds = File.readlines(path = File.expand_path(arg))
|
@@ -944,7 +1093,8 @@ module DEBUGGER__
|
|
944
1093
|
else
|
945
1094
|
show_help 'source'
|
946
1095
|
end
|
947
|
-
|
1096
|
+
:retry
|
1097
|
+
end
|
948
1098
|
|
949
1099
|
# * `open`
|
950
1100
|
# * open debuggee port on UNIX domain socket and wait for attaching.
|
@@ -955,26 +1105,28 @@ module DEBUGGER__
|
|
955
1105
|
# * open debuggee port for VSCode and launch VSCode if available.
|
956
1106
|
# * `open chrome`
|
957
1107
|
# * open debuggee port for Chrome and wait for attaching.
|
958
|
-
|
1108
|
+
register_command 'open' do |arg|
|
959
1109
|
case arg&.downcase
|
960
1110
|
when '', nil
|
961
|
-
|
962
|
-
when 'vscode'
|
963
|
-
repl_open_vscode
|
964
|
-
when /\A(.+):(\d+)\z/
|
965
|
-
repl_open_tcp $1, $2.to_i
|
1111
|
+
::DEBUGGER__.open nonstop: true
|
966
1112
|
when /\A(\d+)z/
|
967
|
-
|
1113
|
+
::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
|
1114
|
+
when /\A(.+):(\d+)\z/
|
1115
|
+
::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
|
968
1116
|
when 'tcp'
|
969
|
-
|
1117
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
1118
|
+
when 'vscode'
|
1119
|
+
CONFIG[:open] = 'vscode'
|
1120
|
+
::DEBUGGER__.open nonstop: true
|
970
1121
|
when 'chrome', 'cdp'
|
971
|
-
CONFIG[:
|
972
|
-
|
1122
|
+
CONFIG[:open] = 'chrome'
|
1123
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
973
1124
|
else
|
974
1125
|
raise "Unknown arg: #{arg}"
|
975
1126
|
end
|
976
1127
|
|
977
|
-
|
1128
|
+
:retry
|
1129
|
+
end
|
978
1130
|
|
979
1131
|
### Help
|
980
1132
|
|
@@ -982,26 +1134,38 @@ module DEBUGGER__
|
|
982
1134
|
# * Show help for all commands.
|
983
1135
|
# * `h[elp] <command>`
|
984
1136
|
# * Show help for the given command.
|
985
|
-
|
1137
|
+
register_command 'h', 'help', '?', unsafe: false do |arg|
|
986
1138
|
show_help arg
|
987
|
-
|
1139
|
+
:retry
|
1140
|
+
end
|
1141
|
+
end
|
988
1142
|
|
989
|
-
|
1143
|
+
def process_command line
|
1144
|
+
if line.empty?
|
1145
|
+
if @repl_prev_line
|
1146
|
+
line = @repl_prev_line
|
1147
|
+
else
|
1148
|
+
return :retry
|
1149
|
+
end
|
1150
|
+
else
|
1151
|
+
@repl_prev_line = line
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
/([^\s]+)(?:\s+(.+))?/ =~ line
|
1155
|
+
cmd_name, cmd_arg = $1, $2
|
1156
|
+
|
1157
|
+
if cmd = @commands[cmd_name]
|
1158
|
+
check_postmortem if !cmd.postmortem
|
1159
|
+
check_unsafe if cmd.unsafe
|
1160
|
+
cancel_auto_continue if cmd.cancel_auto_continue
|
1161
|
+
@repl_prev_line = nil if !cmd.repeat
|
1162
|
+
|
1163
|
+
cmd.block.call(cmd_arg)
|
990
1164
|
else
|
991
|
-
request_tc [:eval, :pp, line]
|
992
|
-
=begin
|
993
1165
|
@repl_prev_line = nil
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
998
|
-
correction = spell_checker.correct(line.split(/\s/).first || '')
|
999
|
-
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
1000
|
-
rescue LoadError
|
1001
|
-
# Don't use D
|
1002
|
-
end
|
1003
|
-
return :retry
|
1004
|
-
=end
|
1166
|
+
check_unsafe
|
1167
|
+
|
1168
|
+
request_eval :pp, line
|
1005
1169
|
end
|
1006
1170
|
|
1007
1171
|
rescue Interrupt
|
@@ -1017,31 +1181,12 @@ module DEBUGGER__
|
|
1017
1181
|
return :retry
|
1018
1182
|
end
|
1019
1183
|
|
1020
|
-
def
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
thc.mark_as_management
|
1184
|
+
def step_command type, arg
|
1185
|
+
if type == :until
|
1186
|
+
leave_subsession [:step, type, arg]
|
1187
|
+
return
|
1025
1188
|
end
|
1026
|
-
@tp_thread_begin.enable
|
1027
|
-
end
|
1028
|
-
|
1029
|
-
def repl_open_tcp host, port, **kw
|
1030
|
-
DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
|
1031
|
-
repl_open_setup
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def repl_open
|
1035
|
-
DEBUGGER__.open nonstop: true
|
1036
|
-
repl_open_setup
|
1037
|
-
end
|
1038
|
-
|
1039
|
-
def repl_open_vscode
|
1040
|
-
CONFIG[:open_frontend] = 'vscode'
|
1041
|
-
repl_open
|
1042
|
-
end
|
1043
1189
|
|
1044
|
-
def step_command type, arg
|
1045
1190
|
case arg
|
1046
1191
|
when nil, /\A\d+\z/
|
1047
1192
|
if type == :in && @tc.recorder&.replaying?
|
@@ -1049,12 +1194,14 @@ module DEBUGGER__
|
|
1049
1194
|
else
|
1050
1195
|
leave_subsession [:step, type, arg&.to_i]
|
1051
1196
|
end
|
1052
|
-
when /\
|
1197
|
+
when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
|
1053
1198
|
if type != :in
|
1054
1199
|
@ui.puts "only `step #{arg}` is supported."
|
1055
1200
|
:retry
|
1056
1201
|
else
|
1057
|
-
|
1202
|
+
type = $1.to_sym
|
1203
|
+
iter = $2&.to_i
|
1204
|
+
request_tc [:step, type, iter]
|
1058
1205
|
end
|
1059
1206
|
else
|
1060
1207
|
@ui.puts "Unknown option: #{arg}"
|
@@ -1248,8 +1395,6 @@ module DEBUGGER__
|
|
1248
1395
|
|
1249
1396
|
def add_bp bp
|
1250
1397
|
# don't repeat commands that add breakpoints
|
1251
|
-
@repl_prev_line = nil
|
1252
|
-
|
1253
1398
|
if @bps.has_key? bp.key
|
1254
1399
|
if bp.duplicable?
|
1255
1400
|
bp
|
@@ -1281,7 +1426,7 @@ module DEBUGGER__
|
|
1281
1426
|
|
1282
1427
|
BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
|
1283
1428
|
|
1284
|
-
def parse_break arg
|
1429
|
+
private def parse_break type, arg
|
1285
1430
|
mode = :sig
|
1286
1431
|
expr = Hash.new{|h, k| h[k] = []}
|
1287
1432
|
arg.split(' ').each{|w|
|
@@ -1298,13 +1443,18 @@ module DEBUGGER__
|
|
1298
1443
|
expr[:path] = Regexp.compile($1)
|
1299
1444
|
end
|
1300
1445
|
|
1446
|
+
if expr[:do] || expr[:pre]
|
1447
|
+
check_unsafe
|
1448
|
+
expr[:cmd] = [type, expr[:pre], expr[:do]]
|
1449
|
+
end
|
1450
|
+
|
1301
1451
|
expr
|
1302
1452
|
end
|
1303
1453
|
|
1304
1454
|
def repl_add_breakpoint arg
|
1305
|
-
expr = parse_break arg.strip
|
1455
|
+
expr = parse_break 'break', arg.strip
|
1306
1456
|
cond = expr[:if]
|
1307
|
-
cmd
|
1457
|
+
cmd = expr[:cmd]
|
1308
1458
|
path = expr[:path]
|
1309
1459
|
|
1310
1460
|
case expr[:sig]
|
@@ -1325,9 +1475,9 @@ module DEBUGGER__
|
|
1325
1475
|
end
|
1326
1476
|
|
1327
1477
|
def repl_add_catch_breakpoint arg
|
1328
|
-
expr = parse_break arg.strip
|
1478
|
+
expr = parse_break 'catch', arg.strip
|
1329
1479
|
cond = expr[:if]
|
1330
|
-
cmd
|
1480
|
+
cmd = expr[:cmd]
|
1331
1481
|
path = expr[:path]
|
1332
1482
|
|
1333
1483
|
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
|
@@ -1335,9 +1485,9 @@ module DEBUGGER__
|
|
1335
1485
|
end
|
1336
1486
|
|
1337
1487
|
def repl_add_watch_breakpoint arg
|
1338
|
-
expr = parse_break arg.strip
|
1488
|
+
expr = parse_break 'watch', arg.strip
|
1339
1489
|
cond = expr[:if]
|
1340
|
-
cmd
|
1490
|
+
cmd = expr[:cmd]
|
1341
1491
|
path = Regexp.compile(expr[:path]) if expr[:path]
|
1342
1492
|
|
1343
1493
|
request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
|
@@ -1374,7 +1524,7 @@ module DEBUGGER__
|
|
1374
1524
|
def clear_line_breakpoints path
|
1375
1525
|
path = resolve_path(path)
|
1376
1526
|
clear_breakpoints do |k, bp|
|
1377
|
-
bp.is_a?(LineBreakpoint) &&
|
1527
|
+
bp.is_a?(LineBreakpoint) && bp.path_is?(path)
|
1378
1528
|
end
|
1379
1529
|
rescue Errno::ENOENT
|
1380
1530
|
# just ignore
|
@@ -1398,9 +1548,7 @@ module DEBUGGER__
|
|
1398
1548
|
# tracers
|
1399
1549
|
|
1400
1550
|
def add_tracer tracer
|
1401
|
-
|
1402
|
-
@repl_prev_line = nil
|
1403
|
-
if @tracers.has_key? tracer.key
|
1551
|
+
if @tracers[tracer.key]&.enabled?
|
1404
1552
|
tracer.disable
|
1405
1553
|
@ui.puts "Duplicated tracer: #{tracer}"
|
1406
1554
|
else
|
@@ -1557,10 +1705,11 @@ module DEBUGGER__
|
|
1557
1705
|
end
|
1558
1706
|
|
1559
1707
|
private def enter_subsession
|
1708
|
+
@subsession_id += 1
|
1560
1709
|
if !@subsession_stack.empty?
|
1561
|
-
DEBUGGER__.
|
1710
|
+
DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
|
1562
1711
|
else
|
1563
|
-
DEBUGGER__.
|
1712
|
+
DEBUGGER__.debug{ "Enter subsession" }
|
1564
1713
|
stop_all_threads
|
1565
1714
|
@process_group.lock
|
1566
1715
|
end
|
@@ -1573,11 +1722,11 @@ module DEBUGGER__
|
|
1573
1722
|
@subsession_stack.pop
|
1574
1723
|
|
1575
1724
|
if @subsession_stack.empty?
|
1576
|
-
DEBUGGER__.
|
1725
|
+
DEBUGGER__.debug{ "Leave subsession" }
|
1577
1726
|
@process_group.unlock
|
1578
1727
|
restart_all_threads
|
1579
1728
|
else
|
1580
|
-
DEBUGGER__.
|
1729
|
+
DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
|
1581
1730
|
end
|
1582
1731
|
|
1583
1732
|
request_tc type if type
|
@@ -1599,25 +1748,30 @@ module DEBUGGER__
|
|
1599
1748
|
file_path, reloaded = @sr.add(iseq, src)
|
1600
1749
|
@ui.event :load, file_path, reloaded
|
1601
1750
|
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1751
|
+
# check breakpoints
|
1752
|
+
if file_path
|
1753
|
+
@bps.find_all do |_key, bp|
|
1754
|
+
LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
|
1755
|
+
end.each do |_key, bp|
|
1756
|
+
if !bp.iseq
|
1757
|
+
bp.try_activate iseq
|
1758
|
+
elsif reloaded
|
1759
|
+
@bps.delete bp.key # to allow duplicate
|
1605
1760
|
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
end
|
1610
|
-
end
|
1761
|
+
# When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
|
1762
|
+
# will continue to be enabled and we'll suspend on ghost breakpoints
|
1763
|
+
bp.delete
|
1611
1764
|
|
1612
|
-
|
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)
|
1765
|
+
nbp = LineBreakpoint.copy(bp, iseq)
|
1618
1766
|
add_bp nbp
|
1619
1767
|
end
|
1620
1768
|
end
|
1769
|
+
else # !file_path => file_path is not existing
|
1770
|
+
@bps.find_all do |_key, bp|
|
1771
|
+
LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
|
1772
|
+
end.each do |_key, bp|
|
1773
|
+
bp.try_activate iseq
|
1774
|
+
end
|
1621
1775
|
end
|
1622
1776
|
end
|
1623
1777
|
|
@@ -1716,6 +1870,12 @@ module DEBUGGER__
|
|
1716
1870
|
end
|
1717
1871
|
end
|
1718
1872
|
|
1873
|
+
def check_unsafe
|
1874
|
+
if @unsafe_context
|
1875
|
+
raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
|
1876
|
+
end
|
1877
|
+
end
|
1878
|
+
|
1719
1879
|
def enter_postmortem_session exc
|
1720
1880
|
return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
|
1721
1881
|
|
@@ -1795,6 +1955,17 @@ module DEBUGGER__
|
|
1795
1955
|
end
|
1796
1956
|
end
|
1797
1957
|
|
1958
|
+
def set_no_sigint_hook old, new
|
1959
|
+
return unless old != new
|
1960
|
+
return unless @ui.respond_to? :activate_sigint
|
1961
|
+
|
1962
|
+
if old # no -> yes
|
1963
|
+
@ui.activate_sigint
|
1964
|
+
else
|
1965
|
+
@ui.deactivate_sigint
|
1966
|
+
end
|
1967
|
+
end
|
1968
|
+
|
1798
1969
|
def save_int_trap cmd
|
1799
1970
|
prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
|
1800
1971
|
prev
|
@@ -1838,6 +2009,13 @@ module DEBUGGER__
|
|
1838
2009
|
def after_fork_parent
|
1839
2010
|
@ui.after_fork_parent
|
1840
2011
|
end
|
2012
|
+
|
2013
|
+
# experimental API
|
2014
|
+
def extend_feature session: nil, thread_client: nil, ui: nil
|
2015
|
+
Session.include session if session
|
2016
|
+
ThreadClient.include thread_client if thread_client
|
2017
|
+
@ui.extend ui if ui
|
2018
|
+
end
|
1841
2019
|
end
|
1842
2020
|
|
1843
2021
|
class ProcessGroup
|
@@ -1886,9 +2064,11 @@ module DEBUGGER__
|
|
1886
2064
|
|
1887
2065
|
def after_fork child: true
|
1888
2066
|
if child || !@lock_file
|
1889
|
-
@m = Mutex.new
|
1890
|
-
@
|
1891
|
-
|
2067
|
+
@m = Mutex.new unless @m
|
2068
|
+
@m.synchronize do
|
2069
|
+
@lock_level = 0
|
2070
|
+
@lock_file = open(@lock_tempfile.path, 'w')
|
2071
|
+
end
|
1892
2072
|
end
|
1893
2073
|
end
|
1894
2074
|
|
@@ -1897,7 +2077,7 @@ module DEBUGGER__
|
|
1897
2077
|
end
|
1898
2078
|
|
1899
2079
|
def locked?
|
1900
|
-
# DEBUGGER__.
|
2080
|
+
# DEBUGGER__.debug{ "locked? #{@lock_level}" }
|
1901
2081
|
@lock_level > 0
|
1902
2082
|
end
|
1903
2083
|
|
@@ -1982,6 +2162,10 @@ module DEBUGGER__
|
|
1982
2162
|
end
|
1983
2163
|
end
|
1984
2164
|
|
2165
|
+
def ignore_output_on_suspend?
|
2166
|
+
false
|
2167
|
+
end
|
2168
|
+
|
1985
2169
|
def flush
|
1986
2170
|
end
|
1987
2171
|
end
|
@@ -2006,6 +2190,7 @@ module DEBUGGER__
|
|
2006
2190
|
case loc.absolute_path
|
2007
2191
|
when dir_prefix
|
2008
2192
|
when %r{rubygems/core_ext/kernel_require\.rb}
|
2193
|
+
when %r{bundled_gems\.rb}
|
2009
2194
|
else
|
2010
2195
|
return loc if loc.absolute_path
|
2011
2196
|
end
|
@@ -2018,19 +2203,22 @@ module DEBUGGER__
|
|
2018
2203
|
def self.start nonstop: false, **kw
|
2019
2204
|
CONFIG.set_config(**kw)
|
2020
2205
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2206
|
+
if CONFIG[:open]
|
2207
|
+
open nonstop: nonstop, **kw
|
2208
|
+
else
|
2209
|
+
unless defined? SESSION
|
2210
|
+
require_relative 'local'
|
2211
|
+
initialize_session{ UI_LocalConsole.new }
|
2212
|
+
end
|
2213
|
+
setup_initial_suspend unless nonstop
|
2024
2214
|
end
|
2025
|
-
|
2026
|
-
setup_initial_suspend unless nonstop
|
2027
2215
|
end
|
2028
2216
|
|
2029
2217
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
2030
2218
|
CONFIG.set_config(**kw)
|
2031
2219
|
require_relative 'server'
|
2032
2220
|
|
2033
|
-
if port || CONFIG[:
|
2221
|
+
if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
|
2034
2222
|
open_tcp host: host, port: (port || 0), nonstop: nonstop
|
2035
2223
|
else
|
2036
2224
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
@@ -2092,6 +2280,18 @@ module DEBUGGER__
|
|
2092
2280
|
end
|
2093
2281
|
end
|
2094
2282
|
|
2283
|
+
# Exiting control
|
2284
|
+
|
2285
|
+
class << self
|
2286
|
+
def skip_all
|
2287
|
+
@skip_all = true
|
2288
|
+
end
|
2289
|
+
|
2290
|
+
def skip?
|
2291
|
+
@skip_all
|
2292
|
+
end
|
2293
|
+
end
|
2294
|
+
|
2095
2295
|
def self.load_rc
|
2096
2296
|
[[File.expand_path('~/.rdbgrc'), true],
|
2097
2297
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
@@ -2120,7 +2320,7 @@ module DEBUGGER__
|
|
2120
2320
|
end
|
2121
2321
|
|
2122
2322
|
# Inspector
|
2123
|
-
|
2323
|
+
|
2124
2324
|
SHORT_INSPECT_LENGTH = 40
|
2125
2325
|
|
2126
2326
|
class LimitedPP
|
@@ -2191,6 +2391,7 @@ module DEBUGGER__
|
|
2191
2391
|
def self.log level, msg
|
2192
2392
|
if check_loglevel level
|
2193
2393
|
@logfile = STDERR unless defined? @logfile
|
2394
|
+
return if @logfile.closed?
|
2194
2395
|
|
2195
2396
|
if defined? SESSION
|
2196
2397
|
pi = SESSION.process_info
|
@@ -2222,7 +2423,7 @@ module DEBUGGER__
|
|
2222
2423
|
# depend on the file system. So this check is only roughly estimation.
|
2223
2424
|
|
2224
2425
|
def self.compare_path(a, b)
|
2225
|
-
a
|
2426
|
+
a&.downcase == b&.downcase
|
2226
2427
|
end
|
2227
2428
|
else
|
2228
2429
|
def self.compare_path(a, b)
|
@@ -2273,8 +2474,24 @@ module DEBUGGER__
|
|
2273
2474
|
end
|
2274
2475
|
end
|
2275
2476
|
|
2276
|
-
|
2277
|
-
|
2477
|
+
module DaemonInterceptor
|
2478
|
+
def daemon(*args)
|
2479
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2480
|
+
|
2481
|
+
_, child_hook = __fork_setup_for_debugger(:child)
|
2482
|
+
|
2483
|
+
unless SESSION.remote?
|
2484
|
+
DEBUGGER__.warn "Can't debug the code after Process.daemon locally. Use the remote debugging feature."
|
2485
|
+
end
|
2486
|
+
|
2487
|
+
super.tap do
|
2488
|
+
child_hook.call
|
2489
|
+
end
|
2490
|
+
end
|
2491
|
+
end
|
2492
|
+
|
2493
|
+
private def __fork_setup_for_debugger fork_mode = nil
|
2494
|
+
fork_mode ||= CONFIG[:fork_mode]
|
2278
2495
|
|
2279
2496
|
if fork_mode == :both && CONFIG[:parent_on_fork]
|
2280
2497
|
fork_mode = :parent
|
@@ -2289,19 +2506,19 @@ module DEBUGGER__
|
|
2289
2506
|
# Do nothing
|
2290
2507
|
}
|
2291
2508
|
child_hook = -> {
|
2292
|
-
DEBUGGER__.
|
2509
|
+
DEBUGGER__.info "Detaching after fork from child process #{Process.pid}"
|
2293
2510
|
SESSION.deactivate
|
2294
2511
|
}
|
2295
2512
|
when :child
|
2296
2513
|
SESSION.before_fork false
|
2297
2514
|
|
2298
2515
|
parent_hook = -> child_pid {
|
2299
|
-
DEBUGGER__.
|
2516
|
+
DEBUGGER__.info "Detaching after fork from parent process #{Process.pid}"
|
2300
2517
|
SESSION.after_fork_parent
|
2301
2518
|
SESSION.deactivate
|
2302
2519
|
}
|
2303
2520
|
child_hook = -> {
|
2304
|
-
DEBUGGER__.
|
2521
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2305
2522
|
SESSION.activate on_fork: true
|
2306
2523
|
}
|
2307
2524
|
when :both
|
@@ -2312,7 +2529,7 @@ module DEBUGGER__
|
|
2312
2529
|
SESSION.after_fork_parent
|
2313
2530
|
}
|
2314
2531
|
child_hook = -> {
|
2315
|
-
DEBUGGER__.
|
2532
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2316
2533
|
SESSION.process_group.after_fork child: true
|
2317
2534
|
SESSION.activate on_fork: true
|
2318
2535
|
}
|
@@ -2324,7 +2541,17 @@ module DEBUGGER__
|
|
2324
2541
|
|
2325
2542
|
module TrapInterceptor
|
2326
2543
|
def trap sig, *command, &command_proc
|
2327
|
-
|
2544
|
+
sym =
|
2545
|
+
case sig
|
2546
|
+
when String
|
2547
|
+
sig.to_sym
|
2548
|
+
when Integer
|
2549
|
+
Signal.signame(sig)&.to_sym
|
2550
|
+
else
|
2551
|
+
sig
|
2552
|
+
end
|
2553
|
+
|
2554
|
+
case sym
|
2328
2555
|
when :INT, :SIGINT
|
2329
2556
|
if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
|
2330
2557
|
return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
|
@@ -2339,6 +2566,7 @@ module DEBUGGER__
|
|
2339
2566
|
module ::Process
|
2340
2567
|
class << self
|
2341
2568
|
prepend ForkInterceptor
|
2569
|
+
prepend DaemonInterceptor
|
2342
2570
|
end
|
2343
2571
|
end
|
2344
2572
|
|
@@ -2374,6 +2602,7 @@ module DEBUGGER__
|
|
2374
2602
|
module ::Process
|
2375
2603
|
class << self
|
2376
2604
|
prepend ForkInterceptor
|
2605
|
+
prepend DaemonInterceptor
|
2377
2606
|
end
|
2378
2607
|
end
|
2379
2608
|
end
|
@@ -2390,10 +2619,17 @@ module Kernel
|
|
2390
2619
|
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
2391
2620
|
|
2392
2621
|
if pre || (do_expr = binding.local_variable_get(:do))
|
2393
|
-
cmds = ['
|
2622
|
+
cmds = ['#debugger', pre, do_expr]
|
2394
2623
|
end
|
2395
2624
|
|
2396
|
-
|
2625
|
+
if ::DEBUGGER__::SESSION.in_subsession?
|
2626
|
+
if cmds
|
2627
|
+
commands = [*cmds[1], *cmds[2]].map{|c| c.split(';;').join("\n")}
|
2628
|
+
::DEBUGGER__::SESSION.add_preset_commands cmds[0], commands, kick: false, continue: false
|
2629
|
+
end
|
2630
|
+
else
|
2631
|
+
loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
|
2632
|
+
end
|
2397
2633
|
self
|
2398
2634
|
end
|
2399
2635
|
|