debug 1.6.3 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/session.rb CHANGED
@@ -85,11 +85,13 @@ class RubyVM::InstructionSequence
85
85
  end
86
86
 
87
87
  module DEBUGGER__
88
- PresetCommand = Struct.new(:commands, :source, :auto_continue)
88
+ PresetCommands = Struct.new(:commands, :source, :auto_continue)
89
+ SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)
90
+
89
91
  class PostmortemError < RuntimeError; end
90
92
 
91
93
  class Session
92
- attr_reader :intercepted_sigint_cmd, :process_group
94
+ attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
93
95
 
94
96
  include Color
95
97
 
@@ -116,6 +118,7 @@ module DEBUGGER__
116
118
  @intercepted_sigint_cmd = 'DEFAULT'
117
119
  @process_group = ProcessGroup.new
118
120
  @subsession_stack = []
121
+ @subsession_id = 0
119
122
 
120
123
  @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
121
124
  @var_map = {1 => [:globals], } # {id => ...} for DAP
@@ -125,21 +128,41 @@ module DEBUGGER__
125
128
  @obj_map = {} # { object_id => ... } for CDP
126
129
 
127
130
  @tp_thread_begin = nil
131
+ @commands = {}
132
+ @unsafe_context = false
133
+
134
+ has_keep_script_lines = RubyVM.respond_to? :keep_script_lines
135
+
128
136
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
129
- ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
137
+ if !has_keep_script_lines || bps_pending_until_load?
138
+ eval_script = tp.eval_script unless has_keep_script_lines
139
+ ThreadClient.current.on_load tp.instruction_sequence, eval_script
140
+ end
130
141
  }
131
142
  @tp_load_script.enable
132
143
 
133
144
  @thread_stopper = thread_stopper
134
145
  self.postmortem = CONFIG[:postmortem]
146
+
147
+ register_default_command
135
148
  end
136
149
 
137
150
  def active?
138
151
  !@q_evt.closed?
139
152
  end
140
153
 
141
- def break_at? file, line
142
- @bps.has_key? [file, line]
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
143
166
  end
144
167
 
145
168
  def activate ui = nil, on_fork: false
@@ -193,6 +216,14 @@ module DEBUGGER__
193
216
  def reset_ui ui
194
217
  @ui.deactivate
195
218
  @ui = ui
219
+
220
+ # activate new ui
221
+ @tp_thread_begin.disable
222
+ @ui.activate self
223
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
224
+ thc.mark_as_management
225
+ end
226
+ @tp_thread_begin.enable
196
227
  end
197
228
 
198
229
  def pop_event
@@ -248,7 +279,7 @@ module DEBUGGER__
248
279
 
249
280
  when :suspend
250
281
  enter_subsession if ev_args.first != :replay
251
- output.each{|str| @ui.puts str}
282
+ output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?
252
283
 
253
284
  case ev_args.first
254
285
  when :breakpoint
@@ -323,7 +354,7 @@ module DEBUGGER__
323
354
  if @preset_command && !@preset_command.commands.empty?
324
355
  @preset_command.commands += cs
325
356
  else
326
- @preset_command = PresetCommand.new(cs, name, continue)
357
+ @preset_command = PresetCommands.new(cs, name, continue)
327
358
  end
328
359
 
329
360
  ThreadClient.current.on_init name if kick
@@ -396,97 +427,121 @@ module DEBUGGER__
396
427
  end
397
428
  end
398
429
 
399
- def process_command line
400
- if line.empty?
401
- if @repl_prev_line
402
- line = @repl_prev_line
403
- else
404
- return :retry
405
- end
406
- else
407
- @repl_prev_line = line
408
- end
409
-
410
- /([^\s]+)(?:\s+(.+))?/ =~ line
411
- cmd, arg = $1, $2
430
+ private def register_command *names,
431
+ repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
432
+ &b
433
+ cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
412
434
 
413
- # p cmd: [cmd, *arg]
435
+ names.each{|name|
436
+ @commands[name] = cmd
437
+ }
438
+ end
414
439
 
415
- case cmd
440
+ def register_default_command
416
441
  ### Control flow
417
442
 
418
443
  # * `s[tep]`
419
444
  # * Step in. Resume the program until next breakable point.
420
445
  # * `s[tep] <n>`
421
446
  # * Step in, resume the program at `<n>`th breakable point.
422
- when 's', 'step'
423
- cancel_auto_continue
424
- check_postmortem
447
+ register_command 's', 'step',
448
+ repeat: true,
449
+ cancel_auto_continue: true,
450
+ postmortem: false do |arg|
425
451
  step_command :in, arg
452
+ end
426
453
 
427
454
  # * `n[ext]`
428
455
  # * Step over. Resume the program until next line.
429
456
  # * `n[ext] <n>`
430
457
  # * Step over, same as `step <n>`.
431
- when 'n', 'next'
432
- cancel_auto_continue
433
- check_postmortem
458
+ register_command 'n', 'next',
459
+ repeat: true,
460
+ cancel_auto_continue: true,
461
+ postmortem: false do |arg|
434
462
  step_command :next, arg
463
+ end
435
464
 
436
465
  # * `fin[ish]`
437
466
  # * Finish this frame. Resume the program until the current frame is finished.
438
467
  # * `fin[ish] <n>`
439
468
  # * Finish `<n>`th frames.
440
- when 'fin', 'finish'
441
- cancel_auto_continue
442
- check_postmortem
443
-
469
+ register_command 'fin', 'finish',
470
+ repeat: true,
471
+ cancel_auto_continue: true,
472
+ postmortem: false do |arg|
444
473
  if arg&.to_i == 0
445
474
  raise 'finish command with 0 does not make sense.'
446
475
  end
447
476
 
448
477
  step_command :finish, arg
478
+ end
449
479
 
450
- # * `c[ontinue]`
480
+ # * `u[ntil]`
481
+ # * Similar to `next` command, but only stop later lines or the end of the current frame.
482
+ # * Similar to gdb's `advance` command.
483
+ # * `u[ntil] <[file:]line>
484
+ # * Run til the program reaches given location or the end of the current frame.
485
+ # * `u[ntil] <name>
486
+ # * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
487
+ register_command 'u', 'until',
488
+ repeat: true,
489
+ cancel_auto_continue: true,
490
+ postmortem: false do |arg|
491
+
492
+ step_command :until, arg
493
+ end
494
+
495
+ # * `c` or `cont` or `continue`
451
496
  # * Resume the program.
452
- when 'c', 'continue'
453
- cancel_auto_continue
497
+ register_command 'c', 'cont', 'continue',
498
+ repeat: true,
499
+ cancel_auto_continue: true do |arg|
454
500
  leave_subsession :continue
501
+ end
455
502
 
456
503
  # * `q[uit]` or `Ctrl-D`
457
504
  # * Finish debugger (with the debuggee process on non-remote debugging).
458
- when 'q', 'quit'
505
+ register_command 'q', 'quit' do |arg|
459
506
  if ask 'Really quit?'
460
- @ui.quit arg.to_i
507
+ @ui.quit arg.to_i do
508
+ request_tc :quit
509
+ end
461
510
  leave_subsession :continue
462
511
  else
463
- return :retry
512
+ next :retry
464
513
  end
514
+ end
465
515
 
466
516
  # * `q[uit]!`
467
517
  # * Same as q[uit] but without the confirmation prompt.
468
- when 'q!', 'quit!'
469
- @ui.quit arg.to_i
470
- leave_subsession nil
518
+ register_command 'q!', 'quit!', unsafe: false do |arg|
519
+ @ui.quit arg.to_i do
520
+ request_tc :quit
521
+ end
522
+ leave_subsession :continue
523
+ end
471
524
 
472
525
  # * `kill`
473
526
  # * Stop the debuggee process with `Kernel#exit!`.
474
- when 'kill'
527
+ register_command 'kill' do |arg|
475
528
  if ask 'Really kill?'
476
529
  exit! (arg || 1).to_i
477
530
  else
478
- return :retry
531
+ next :retry
479
532
  end
533
+ end
480
534
 
481
535
  # * `kill!`
482
536
  # * Same as kill but without the confirmation prompt.
483
- when 'kill!'
537
+ register_command 'kill!', unsafe: false do |arg|
484
538
  exit! (arg || 1).to_i
539
+ end
485
540
 
486
541
  # * `sigint`
487
542
  # * Execute SIGINT handler registered by the debuggee.
488
543
  # * Note that this command should be used just after stop by `SIGINT`.
489
- when 'sigint'
544
+ register_command 'sigint' do
490
545
  begin
491
546
  case cmd = @intercepted_sigint_cmd
492
547
  when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
@@ -502,8 +557,9 @@ module DEBUGGER__
502
557
  rescue Exception => e
503
558
  @ui.puts "Exception: #{e}"
504
559
  @ui.puts e.backtrace.map{|line| " #{e}"}
505
- return :retry
560
+ next :retry
506
561
  end
562
+ end
507
563
 
508
564
  ### Breakpoint
509
565
 
@@ -528,22 +584,21 @@ module DEBUGGER__
528
584
  # * `b[reak] if: <expr>`
529
585
  # * break if: `<expr>` is true at any lines.
530
586
  # * Note that this feature is super slow.
531
- when 'b', 'break'
532
- check_postmortem
533
-
587
+ register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
534
588
  if arg == nil
535
589
  show_bps
536
- return :retry
590
+ next :retry
537
591
  else
538
592
  case bp = repl_add_breakpoint(arg)
539
593
  when :noretry
540
594
  when nil
541
- return :retry
595
+ next :retry
542
596
  else
543
597
  show_bps bp
544
- return :retry
598
+ next :retry
545
599
  end
546
600
  end
601
+ end
547
602
 
548
603
  # * `catch <Error>`
549
604
  # * Set breakpoint on raising `<Error>`.
@@ -555,16 +610,16 @@ module DEBUGGER__
555
610
  # * stops and run `<command>`, and continue.
556
611
  # * `catch ... path: <path>`
557
612
  # * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
558
- when 'catch'
559
- check_postmortem
560
-
613
+ register_command 'catch', postmortem: false, unsafe: false do |arg|
561
614
  if arg
562
615
  bp = repl_add_catch_breakpoint arg
563
616
  show_bps bp if bp
564
617
  else
565
618
  show_bps
566
619
  end
567
- return :retry
620
+
621
+ :retry
622
+ end
568
623
 
569
624
  # * `watch @ivar`
570
625
  # * Stop the execution when the result of current scope's `@ivar` is changed.
@@ -577,24 +632,20 @@ module DEBUGGER__
577
632
  # * stops and run `<command>`, and continue.
578
633
  # * `watch ... path: <path>`
579
634
  # * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
580
- when 'wat', 'watch'
581
- check_postmortem
582
-
635
+ register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
583
636
  if arg && arg.match?(/\A@\w+/)
584
637
  repl_add_watch_breakpoint(arg)
585
638
  else
586
639
  show_bps
587
- return :retry
640
+ :retry
588
641
  end
642
+ end
589
643
 
590
644
  # * `del[ete]`
591
645
  # * delete all breakpoints.
592
646
  # * `del[ete] <bpnum>`
593
647
  # * delete specified breakpoint.
594
- when 'del', 'delete'
595
- check_postmortem
596
-
597
- bp =
648
+ register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
598
649
  case arg
599
650
  when nil
600
651
  show_bps
@@ -602,12 +653,13 @@ module DEBUGGER__
602
653
  delete_bp
603
654
  end
604
655
  when /\d+/
605
- delete_bp arg.to_i
656
+ bp = delete_bp arg.to_i
606
657
  else
607
658
  nil
608
659
  end
609
660
  @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
610
- return :retry
661
+ :retry
662
+ end
611
663
 
612
664
  ### Information
613
665
 
@@ -619,7 +671,7 @@ module DEBUGGER__
619
671
  # * Only shows frames with method name or location info that matches `/regexp/`.
620
672
  # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
621
673
  # * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
622
- when 'bt', 'backtrace'
674
+ register_command 'bt', 'backtrace', unsafe: false do |arg|
623
675
  case arg
624
676
  when /\A(\d+)\z/
625
677
  request_tc [:show, :backtrace, arg.to_i, nil]
@@ -632,6 +684,7 @@ module DEBUGGER__
632
684
  else
633
685
  request_tc [:show, :backtrace, nil, nil]
634
686
  end
687
+ end
635
688
 
636
689
  # * `l[ist]`
637
690
  # * Show current frame's source code.
@@ -640,7 +693,7 @@ module DEBUGGER__
640
693
  # * Show predecessor lines as opposed to the `list` command.
641
694
  # * `l[ist] <start>` or `l[ist] <start>-<end>`
642
695
  # * Show current frame's source code from the line <start> to <end> if given.
643
- when 'l', 'list'
696
+ register_command 'l', 'list', repeat: true, unsafe: false do |arg|
644
697
  case arg ? arg.strip : nil
645
698
  when /\A(\d+)\z/
646
699
  request_tc [:show, :list, {start_line: arg.to_i - 1}]
@@ -652,45 +705,63 @@ module DEBUGGER__
652
705
  request_tc [:show, :list]
653
706
  else
654
707
  @ui.puts "Can not handle list argument: #{arg}"
655
- return :retry
708
+ :retry
656
709
  end
710
+ end
711
+
712
+ # * `whereami`
713
+ # * Show the current frame with source code.
714
+ register_command 'whereami', unsafe: false do
715
+ request_tc [:show, :whereami]
716
+ end
657
717
 
658
718
  # * `edit`
659
719
  # * Open the current file on the editor (use `EDITOR` environment variable).
660
720
  # * Note that edited file will not be reloaded.
661
721
  # * `edit <file>`
662
722
  # * Open <file> on the editor.
663
- when 'edit'
723
+ register_command 'edit' do |arg|
664
724
  if @ui.remote?
665
725
  @ui.puts "not supported on the remote console."
666
- return :retry
726
+ next :retry
667
727
  end
668
728
 
669
729
  begin
670
730
  arg = resolve_path(arg) if arg
671
731
  rescue Errno::ENOENT
672
732
  @ui.puts "not found: #{arg}"
673
- return :retry
733
+ next :retry
674
734
  end
675
735
 
676
736
  request_tc [:show, :edit, arg]
737
+ end
738
+
739
+ info_subcommands = nil
740
+ info_subcommands_abbrev = nil
677
741
 
678
742
  # * `i[nfo]`
679
- # * Show information about current frame (local/instance variables and defined constants).
680
- # * `i[nfo] l[ocal[s]]`
743
+ # * Show information about current frame (local/instance variables and defined constants).
744
+ # * `i[nfo]` <subcommand>
745
+ # * `info` has the following sub-commands.
746
+ # * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
747
+ # * `i[nfo] l or locals or local_variables`
681
748
  # * Show information about the current frame (local variables)
682
- # * It includes `self` as `%self` and a return value as `%return`.
683
- # * `i[nfo] i[var[s]]` or `i[nfo] instance`
749
+ # * It includes `self` as `%self` and a return value as `_return`.
750
+ # * `i[nfo] i or ivars or instance_variables`
684
751
  # * Show information about instance variables about `self`.
685
- # * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
752
+ # * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
753
+ # * `i[nfo] c or consts or constants`
686
754
  # * Show information about accessible constants except toplevel constants.
687
- # * `i[nfo] g[lobal[s]]`
755
+ # * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
756
+ # * `i[nfo] g or globals or global_variables`
688
757
  # * Show information about global variables
758
+ # * `i[nfo] th or threads`
759
+ # * Show all threads (same as `th[read]`).
760
+ # * `i[nfo] b or breakpoints or w or watchpoints`
761
+ # * Show all breakpoints and watchpoints.
689
762
  # * `i[nfo] ... /regexp/`
690
763
  # * Filter the output with `/regexp/`.
691
- # * `i[nfo] th[read[s]]`
692
- # * Show all threads (same as `th[read]`).
693
- when 'i', 'info'
764
+ register_command 'i', 'info', unsafe: false do |arg|
694
765
  if /\/(.+)\/\z/ =~ arg
695
766
  pat = Regexp.compile($1)
696
767
  sub = $~.pre_match.strip
@@ -698,51 +769,85 @@ module DEBUGGER__
698
769
  sub = arg
699
770
  end
700
771
 
772
+ if /\A(.+?)\b(.+)/ =~ sub
773
+ sub = $1
774
+ opt = $2.strip
775
+ opt = nil if opt.empty?
776
+ end
777
+
778
+ if sub && !info_subcommands
779
+ info_subcommands = {
780
+ locals: %w[ locals local_variables ],
781
+ ivars: %w[ ivars instance_variables ],
782
+ consts: %w[ consts constants ],
783
+ globals:%w[ globals global_variables ],
784
+ threads:%w[ threads ],
785
+ breaks: %w[ breakpoints ],
786
+ watchs: %w[ watchpoints ],
787
+ }
788
+
789
+ require_relative 'abbrev_command'
790
+ info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
791
+ end
792
+
793
+ if sub
794
+ sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
795
+ # note: unreached now
796
+ @ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
797
+ end
798
+ end
799
+
701
800
  case sub
702
801
  when nil
703
802
  request_tc [:show, :default, pat] # something useful
704
- when 'l', /^locals?/
803
+ when :locals
705
804
  request_tc [:show, :locals, pat]
706
- when 'i', /^ivars?/i, /^instance[_ ]variables?/i
707
- request_tc [:show, :ivars, pat]
708
- when 'c', /^consts?/i, /^constants?/i
709
- request_tc [:show, :consts, pat]
710
- when 'g', /^globals?/i, /^global[_ ]variables?/i
805
+ when :ivars
806
+ request_tc [:show, :ivars, pat, opt]
807
+ when :consts
808
+ request_tc [:show, :consts, pat, opt]
809
+ when :globals
711
810
  request_tc [:show, :globals, pat]
712
- when 'th', /threads?/
811
+ when :threads
713
812
  thread_list
714
- return :retry
813
+ :retry
814
+ when :breaks, :watchs
815
+ show_bps
816
+ :retry
715
817
  else
716
818
  @ui.puts "unrecognized argument for info command: #{arg}"
717
819
  show_help 'info'
718
- return :retry
820
+ :retry
719
821
  end
822
+ end
720
823
 
721
824
  # * `o[utline]` or `ls`
722
825
  # * Show you available methods, constants, local variables, and instance variables in the current scope.
723
826
  # * `o[utline] <expr>` or `ls <expr>`
724
827
  # * Show you available methods and instance variables of the given object.
725
828
  # * If the object is a class/module, it also lists its constants.
726
- when 'outline', 'o', 'ls'
829
+ register_command 'outline', 'o', 'ls', unsafe: false do |arg|
727
830
  request_tc [:show, :outline, arg]
831
+ end
728
832
 
729
833
  # * `display`
730
834
  # * Show display setting.
731
835
  # * `display <expr>`
732
836
  # * Show the result of `<expr>` at every suspended timing.
733
- when 'display'
837
+ register_command 'display', postmortem: false do |arg|
734
838
  if arg && !arg.empty?
735
839
  @displays << arg
736
840
  request_tc [:eval, :try_display, @displays]
737
841
  else
738
842
  request_tc [:eval, :display, @displays]
739
843
  end
844
+ end
740
845
 
741
846
  # * `undisplay`
742
847
  # * Remove all display settings.
743
848
  # * `undisplay <displaynum>`
744
849
  # * Remove a specified display setting.
745
- when 'undisplay'
850
+ register_command 'undisplay', postmortem: false, unsafe: false do |arg|
746
851
  case arg
747
852
  when /(\d+)/
748
853
  if @displays[n = $1.to_i]
@@ -753,8 +858,9 @@ module DEBUGGER__
753
858
  if ask "clear all?", 'N'
754
859
  @displays.clear
755
860
  end
756
- return :retry
861
+ :retry
757
862
  end
863
+ end
758
864
 
759
865
  ### Frame control
760
866
 
@@ -762,53 +868,57 @@ module DEBUGGER__
762
868
  # * Show the current frame.
763
869
  # * `f[rame] <framenum>`
764
870
  # * Specify a current frame. Evaluation are run on specified frame.
765
- when 'frame', 'f'
871
+ register_command 'frame', 'f', unsafe: false do |arg|
766
872
  request_tc [:frame, :set, arg]
873
+ end
767
874
 
768
875
  # * `up`
769
876
  # * Specify the upper frame.
770
- when 'up'
877
+ register_command 'up', repeat: true, unsafe: false do |arg|
771
878
  request_tc [:frame, :up]
879
+ end
772
880
 
773
881
  # * `down`
774
882
  # * Specify the lower frame.
775
- when 'down'
883
+ register_command 'down', repeat: true, unsafe: false do |arg|
776
884
  request_tc [:frame, :down]
885
+ end
777
886
 
778
887
  ### Evaluate
779
888
 
780
889
  # * `p <expr>`
781
890
  # * Evaluate like `p <expr>` on the current frame.
782
- when 'p'
891
+ register_command 'p' do |arg|
783
892
  request_tc [:eval, :p, arg.to_s]
893
+ end
784
894
 
785
895
  # * `pp <expr>`
786
896
  # * Evaluate like `pp <expr>` on the current frame.
787
- when 'pp'
897
+ register_command 'pp' do |arg|
788
898
  request_tc [:eval, :pp, arg.to_s]
899
+ end
789
900
 
790
901
  # * `eval <expr>`
791
902
  # * Evaluate `<expr>` on the current frame.
792
- when 'eval', 'call'
903
+ register_command 'eval', 'call' do |arg|
793
904
  if arg == nil || arg.empty?
794
905
  show_help 'eval'
795
906
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
796
- return :retry
907
+ :retry
797
908
  else
798
909
  request_tc [:eval, :call, arg]
799
910
  end
911
+ end
800
912
 
801
913
  # * `irb`
802
914
  # * Invoke `irb` on the current frame.
803
- when 'irb'
915
+ register_command 'irb' do |arg|
804
916
  if @ui.remote?
805
917
  @ui.puts "not supported on the remote console."
806
- return :retry
918
+ :retry
807
919
  end
808
920
  request_tc [:eval, :irb]
809
-
810
- # don't repeat irb command
811
- @repl_prev_line = nil
921
+ end
812
922
 
813
923
  ### Trace
814
924
  # * `trace`
@@ -829,7 +939,7 @@ module DEBUGGER__
829
939
  # * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
830
940
  # * `trace off [line|call|pass]`
831
941
  # * Disable all tracers. If `<type>` is provided, disable specified type tracers.
832
- when 'trace'
942
+ register_command 'trace', postmortem: false, unsafe: false do |arg|
833
943
  if (re = /\s+into:\s*(.+)/) =~ arg
834
944
  into = $1
835
945
  arg.sub!(re, '')
@@ -847,19 +957,19 @@ module DEBUGGER__
847
957
  @ui.puts "* \##{i} #{t}"
848
958
  }
849
959
  @ui.puts
850
- return :retry
960
+ :retry
851
961
 
852
962
  when /\Aline\z/
853
963
  add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
854
- return :retry
964
+ :retry
855
965
 
856
966
  when /\Acall\z/
857
967
  add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
858
- return :retry
968
+ :retry
859
969
 
860
970
  when /\Aexception\z/
861
971
  add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
862
- return :retry
972
+ :retry
863
973
 
864
974
  when /\Aobject\s+(.+)/
865
975
  request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
@@ -871,7 +981,7 @@ module DEBUGGER__
871
981
  else
872
982
  @ui.puts "Unmatched: #{$1}"
873
983
  end
874
- return :retry
984
+ :retry
875
985
 
876
986
  when /\Aoff(\s+(line|call|exception|object))?\z/
877
987
  @tracers.values.each{|t|
@@ -880,12 +990,13 @@ module DEBUGGER__
880
990
  @ui.puts "Disable #{t.to_s}"
881
991
  end
882
992
  }
883
- return :retry
993
+ :retry
884
994
 
885
995
  else
886
996
  @ui.puts "Unknown trace option: #{arg.inspect}"
887
- return :retry
997
+ :retry
888
998
  end
999
+ end
889
1000
 
890
1001
  # Record
891
1002
  # * `record`
@@ -897,14 +1008,15 @@ module DEBUGGER__
897
1008
  # * `s[tep]` does stepping forward with the last log.
898
1009
  # * `step reset`
899
1010
  # * Stop replay .
900
- when 'record'
1011
+ register_command 'record', postmortem: false, unsafe: false do |arg|
901
1012
  case arg
902
1013
  when nil, 'on', 'off'
903
1014
  request_tc [:record, arg&.to_sym]
904
1015
  else
905
1016
  @ui.puts "unknown command: #{arg}"
906
- return :retry
1017
+ :retry
907
1018
  end
1019
+ end
908
1020
 
909
1021
  ### Thread control
910
1022
 
@@ -912,7 +1024,7 @@ module DEBUGGER__
912
1024
  # * Show all threads.
913
1025
  # * `th[read] <thnum>`
914
1026
  # * Switch thread specified by `<thnum>`.
915
- when 'th', 'thread'
1027
+ register_command 'th', 'thread', unsafe: false do |arg|
916
1028
  case arg
917
1029
  when nil, 'list', 'l'
918
1030
  thread_list
@@ -921,7 +1033,8 @@ module DEBUGGER__
921
1033
  else
922
1034
  @ui.puts "unknown thread command: #{arg}"
923
1035
  end
924
- return :retry
1036
+ :retry
1037
+ end
925
1038
 
926
1039
  ### Configuration
927
1040
  # * `config`
@@ -934,13 +1047,14 @@ module DEBUGGER__
934
1047
  # * Append `<val>` to `<name>` if it is an array.
935
1048
  # * `config unset <name>`
936
1049
  # * Set <name> to default.
937
- when 'config'
1050
+ register_command 'config', unsafe: false do |arg|
938
1051
  config_command arg
939
- return :retry
1052
+ :retry
1053
+ end
940
1054
 
941
1055
  # * `source <file>`
942
1056
  # * Evaluate lines in `<file>` as debug commands.
943
- when 'source'
1057
+ register_command 'source' do |arg|
944
1058
  if arg
945
1059
  begin
946
1060
  cmds = File.readlines(path = File.expand_path(arg))
@@ -951,7 +1065,8 @@ module DEBUGGER__
951
1065
  else
952
1066
  show_help 'source'
953
1067
  end
954
- return :retry
1068
+ :retry
1069
+ end
955
1070
 
956
1071
  # * `open`
957
1072
  # * open debuggee port on UNIX domain socket and wait for attaching.
@@ -962,26 +1077,28 @@ module DEBUGGER__
962
1077
  # * open debuggee port for VSCode and launch VSCode if available.
963
1078
  # * `open chrome`
964
1079
  # * open debuggee port for Chrome and wait for attaching.
965
- when 'open'
1080
+ register_command 'open' do |arg|
966
1081
  case arg&.downcase
967
1082
  when '', nil
968
- repl_open
969
- when 'vscode'
970
- repl_open_vscode
971
- when /\A(.+):(\d+)\z/
972
- repl_open_tcp $1, $2.to_i
1083
+ ::DEBUGGER__.open nonstop: true
973
1084
  when /\A(\d+)z/
974
- repl_open_tcp nil, $1.to_i
1085
+ ::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
1086
+ when /\A(.+):(\d+)\z/
1087
+ ::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
975
1088
  when 'tcp'
976
- repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
1089
+ ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
1090
+ when 'vscode'
1091
+ CONFIG[:open] = 'vscode'
1092
+ ::DEBUGGER__.open nonstop: true
977
1093
  when 'chrome', 'cdp'
978
- CONFIG[:open_frontend] = 'chrome'
979
- repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
1094
+ CONFIG[:open] = 'chrome'
1095
+ ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
980
1096
  else
981
1097
  raise "Unknown arg: #{arg}"
982
1098
  end
983
1099
 
984
- return :retry
1100
+ :retry
1101
+ end
985
1102
 
986
1103
  ### Help
987
1104
 
@@ -989,26 +1106,38 @@ module DEBUGGER__
989
1106
  # * Show help for all commands.
990
1107
  # * `h[elp] <command>`
991
1108
  # * Show help for the given command.
992
- when 'h', 'help', '?'
1109
+ register_command 'h', 'help', '?', unsafe: false do |arg|
993
1110
  show_help arg
994
- return :retry
1111
+ :retry
1112
+ end
1113
+ end
995
1114
 
996
- ### END
1115
+ def process_command line
1116
+ if line.empty?
1117
+ if @repl_prev_line
1118
+ line = @repl_prev_line
1119
+ else
1120
+ return :retry
1121
+ end
1122
+ else
1123
+ @repl_prev_line = line
1124
+ end
1125
+
1126
+ /([^\s]+)(?:\s+(.+))?/ =~ line
1127
+ cmd_name, cmd_arg = $1, $2
1128
+
1129
+ if cmd = @commands[cmd_name]
1130
+ check_postmortem if !cmd.postmortem
1131
+ check_unsafe if cmd.unsafe
1132
+ cancel_auto_continue if cmd.cancel_auto_continue
1133
+ @repl_prev_line = nil if !cmd.repeat
1134
+
1135
+ cmd.block.call(cmd_arg)
997
1136
  else
998
- request_tc [:eval, :pp, line]
999
- =begin
1000
1137
  @repl_prev_line = nil
1001
- @ui.puts "unknown command: #{line}"
1002
- begin
1003
- require 'did_you_mean'
1004
- spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
1005
- correction = spell_checker.correct(line.split(/\s/).first || '')
1006
- @ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
1007
- rescue LoadError
1008
- # Don't use D
1009
- end
1010
- return :retry
1011
- =end
1138
+ check_unsafe
1139
+
1140
+ request_tc [:eval, :pp, line]
1012
1141
  end
1013
1142
 
1014
1143
  rescue Interrupt
@@ -1024,31 +1153,12 @@ module DEBUGGER__
1024
1153
  return :retry
1025
1154
  end
1026
1155
 
1027
- def repl_open_setup
1028
- @tp_thread_begin.disable
1029
- @ui.activate self
1030
- if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
1031
- thc.mark_as_management
1156
+ def step_command type, arg
1157
+ if type == :until
1158
+ leave_subsession [:step, type, arg]
1159
+ return
1032
1160
  end
1033
- @tp_thread_begin.enable
1034
- end
1035
-
1036
- def repl_open_tcp host, port, **kw
1037
- DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
1038
- repl_open_setup
1039
- end
1040
1161
 
1041
- def repl_open
1042
- DEBUGGER__.open nonstop: true
1043
- repl_open_setup
1044
- end
1045
-
1046
- def repl_open_vscode
1047
- CONFIG[:open_frontend] = 'vscode'
1048
- repl_open
1049
- end
1050
-
1051
- def step_command type, arg
1052
1162
  case arg
1053
1163
  when nil, /\A\d+\z/
1054
1164
  if type == :in && @tc.recorder&.replaying?
@@ -1056,12 +1166,14 @@ module DEBUGGER__
1056
1166
  else
1057
1167
  leave_subsession [:step, type, arg&.to_i]
1058
1168
  end
1059
- when /\Aback\z/, /\Areset\z/
1169
+ when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
1060
1170
  if type != :in
1061
1171
  @ui.puts "only `step #{arg}` is supported."
1062
1172
  :retry
1063
1173
  else
1064
- request_tc [:step, arg.to_sym]
1174
+ type = $1.to_sym
1175
+ iter = $2&.to_i
1176
+ request_tc [:step, type, iter]
1065
1177
  end
1066
1178
  else
1067
1179
  @ui.puts "Unknown option: #{arg}"
@@ -1209,6 +1321,10 @@ module DEBUGGER__
1209
1321
 
1210
1322
  # breakpoint management
1211
1323
 
1324
+ def bps_pending_until_load?
1325
+ @bps.any?{|key, bp| bp.pending_until_load?}
1326
+ end
1327
+
1212
1328
  def iterate_bps
1213
1329
  deleted_bps = []
1214
1330
  i = 0
@@ -1255,8 +1371,6 @@ module DEBUGGER__
1255
1371
 
1256
1372
  def add_bp bp
1257
1373
  # don't repeat commands that add breakpoints
1258
- @repl_prev_line = nil
1259
-
1260
1374
  if @bps.has_key? bp.key
1261
1375
  if bp.duplicable?
1262
1376
  bp
@@ -1288,7 +1402,7 @@ module DEBUGGER__
1288
1402
 
1289
1403
  BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
1290
1404
 
1291
- def parse_break arg
1405
+ private def parse_break type, arg
1292
1406
  mode = :sig
1293
1407
  expr = Hash.new{|h, k| h[k] = []}
1294
1408
  arg.split(' ').each{|w|
@@ -1305,13 +1419,18 @@ module DEBUGGER__
1305
1419
  expr[:path] = Regexp.compile($1)
1306
1420
  end
1307
1421
 
1422
+ if expr[:do] || expr[:pre]
1423
+ check_unsafe
1424
+ expr[:cmd] = [type, expr[:pre], expr[:do]]
1425
+ end
1426
+
1308
1427
  expr
1309
1428
  end
1310
1429
 
1311
1430
  def repl_add_breakpoint arg
1312
- expr = parse_break arg.strip
1431
+ expr = parse_break 'break', arg.strip
1313
1432
  cond = expr[:if]
1314
- cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1433
+ cmd = expr[:cmd]
1315
1434
  path = expr[:path]
1316
1435
 
1317
1436
  case expr[:sig]
@@ -1332,9 +1451,9 @@ module DEBUGGER__
1332
1451
  end
1333
1452
 
1334
1453
  def repl_add_catch_breakpoint arg
1335
- expr = parse_break arg.strip
1454
+ expr = parse_break 'catch', arg.strip
1336
1455
  cond = expr[:if]
1337
- cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1456
+ cmd = expr[:cmd]
1338
1457
  path = expr[:path]
1339
1458
 
1340
1459
  bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
@@ -1342,9 +1461,9 @@ module DEBUGGER__
1342
1461
  end
1343
1462
 
1344
1463
  def repl_add_watch_breakpoint arg
1345
- expr = parse_break arg.strip
1464
+ expr = parse_break 'watch', arg.strip
1346
1465
  cond = expr[:if]
1347
- cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1466
+ cmd = expr[:cmd]
1348
1467
  path = Regexp.compile(expr[:path]) if expr[:path]
1349
1468
 
1350
1469
  request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
@@ -1405,8 +1524,6 @@ module DEBUGGER__
1405
1524
  # tracers
1406
1525
 
1407
1526
  def add_tracer tracer
1408
- # don't repeat commands that add tracers
1409
- @repl_prev_line = nil
1410
1527
  if @tracers.has_key? tracer.key
1411
1528
  tracer.disable
1412
1529
  @ui.puts "Duplicated tracer: #{tracer}"
@@ -1564,10 +1681,11 @@ module DEBUGGER__
1564
1681
  end
1565
1682
 
1566
1683
  private def enter_subsession
1684
+ @subsession_id += 1
1567
1685
  if !@subsession_stack.empty?
1568
- DEBUGGER__.info "Enter subsession (nested #{@subsession_stack.size})"
1686
+ DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
1569
1687
  else
1570
- DEBUGGER__.info "Enter subsession"
1688
+ DEBUGGER__.debug{ "Enter subsession" }
1571
1689
  stop_all_threads
1572
1690
  @process_group.lock
1573
1691
  end
@@ -1580,11 +1698,11 @@ module DEBUGGER__
1580
1698
  @subsession_stack.pop
1581
1699
 
1582
1700
  if @subsession_stack.empty?
1583
- DEBUGGER__.info "Leave subsession"
1701
+ DEBUGGER__.debug{ "Leave subsession" }
1584
1702
  @process_group.unlock
1585
1703
  restart_all_threads
1586
1704
  else
1587
- DEBUGGER__.info "Leave subsession (nested #{@subsession_stack.size})"
1705
+ DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
1588
1706
  end
1589
1707
 
1590
1708
  request_tc type if type
@@ -1723,6 +1841,12 @@ module DEBUGGER__
1723
1841
  end
1724
1842
  end
1725
1843
 
1844
+ def check_unsafe
1845
+ if @unsafe_context
1846
+ raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
1847
+ end
1848
+ end
1849
+
1726
1850
  def enter_postmortem_session exc
1727
1851
  return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
1728
1852
 
@@ -1802,6 +1926,17 @@ module DEBUGGER__
1802
1926
  end
1803
1927
  end
1804
1928
 
1929
+ def set_no_sigint_hook old, new
1930
+ return unless old != new
1931
+ return unless @ui.respond_to? :activate_sigint
1932
+
1933
+ if old # no -> yes
1934
+ @ui.activate_sigint
1935
+ else
1936
+ @ui.deactivate_sigint
1937
+ end
1938
+ end
1939
+
1805
1940
  def save_int_trap cmd
1806
1941
  prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
1807
1942
  prev
@@ -1906,7 +2041,7 @@ module DEBUGGER__
1906
2041
  end
1907
2042
 
1908
2043
  def locked?
1909
- # DEBUGGER__.info "locked? #{@lock_level}"
2044
+ # DEBUGGER__.debug{ "locked? #{@lock_level}" }
1910
2045
  @lock_level > 0
1911
2046
  end
1912
2047
 
@@ -1991,6 +2126,10 @@ module DEBUGGER__
1991
2126
  end
1992
2127
  end
1993
2128
 
2129
+ def ignore_output_on_suspend?
2130
+ false
2131
+ end
2132
+
1994
2133
  def flush
1995
2134
  end
1996
2135
  end
@@ -2027,19 +2166,22 @@ module DEBUGGER__
2027
2166
  def self.start nonstop: false, **kw
2028
2167
  CONFIG.set_config(**kw)
2029
2168
 
2030
- unless defined? SESSION
2031
- require_relative 'local'
2032
- initialize_session{ UI_LocalConsole.new }
2169
+ if CONFIG[:open]
2170
+ open nonstop: nonstop, **kw
2171
+ else
2172
+ unless defined? SESSION
2173
+ require_relative 'local'
2174
+ initialize_session{ UI_LocalConsole.new }
2175
+ end
2176
+ setup_initial_suspend unless nonstop
2033
2177
  end
2034
-
2035
- setup_initial_suspend unless nonstop
2036
2178
  end
2037
2179
 
2038
2180
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
2039
2181
  CONFIG.set_config(**kw)
2040
2182
  require_relative 'server'
2041
2183
 
2042
- if port || CONFIG[:open_frontend] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
2184
+ if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
2043
2185
  open_tcp host: host, port: (port || 0), nonstop: nonstop
2044
2186
  else
2045
2187
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
@@ -2101,6 +2243,18 @@ module DEBUGGER__
2101
2243
  end
2102
2244
  end
2103
2245
 
2246
+ # Exiting control
2247
+
2248
+ class << self
2249
+ def skip_all
2250
+ @skip_all = true
2251
+ end
2252
+
2253
+ def skip?
2254
+ @skip_all
2255
+ end
2256
+ end
2257
+
2104
2258
  def self.load_rc
2105
2259
  [[File.expand_path('~/.rdbgrc'), true],
2106
2260
  [File.expand_path('~/.rdbgrc.rb'), true],
@@ -2200,6 +2354,7 @@ module DEBUGGER__
2200
2354
  def self.log level, msg
2201
2355
  if check_loglevel level
2202
2356
  @logfile = STDERR unless defined? @logfile
2357
+ return if @logfile.closed?
2203
2358
 
2204
2359
  if defined? SESSION
2205
2360
  pi = SESSION.process_info
@@ -2282,8 +2437,24 @@ module DEBUGGER__
2282
2437
  end
2283
2438
  end
2284
2439
 
2285
- private def __fork_setup_for_debugger
2286
- fork_mode = CONFIG[:fork_mode]
2440
+ module DaemonInterceptor
2441
+ def daemon
2442
+ return super unless defined?(SESSION) && SESSION.active?
2443
+
2444
+ _, child_hook = __fork_setup_for_debugger(:child)
2445
+
2446
+ unless SESSION.remote?
2447
+ DEBUGGER__.warn "Can't debug the code after Process.daemon locally. Use the remote debugging feature."
2448
+ end
2449
+
2450
+ super.tap do
2451
+ child_hook.call
2452
+ end
2453
+ end
2454
+ end
2455
+
2456
+ private def __fork_setup_for_debugger fork_mode = nil
2457
+ fork_mode ||= CONFIG[:fork_mode]
2287
2458
 
2288
2459
  if fork_mode == :both && CONFIG[:parent_on_fork]
2289
2460
  fork_mode = :parent
@@ -2298,19 +2469,19 @@ module DEBUGGER__
2298
2469
  # Do nothing
2299
2470
  }
2300
2471
  child_hook = -> {
2301
- DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
2472
+ DEBUGGER__.info "Detaching after fork from child process #{Process.pid}"
2302
2473
  SESSION.deactivate
2303
2474
  }
2304
2475
  when :child
2305
2476
  SESSION.before_fork false
2306
2477
 
2307
2478
  parent_hook = -> child_pid {
2308
- DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
2479
+ DEBUGGER__.info "Detaching after fork from parent process #{Process.pid}"
2309
2480
  SESSION.after_fork_parent
2310
2481
  SESSION.deactivate
2311
2482
  }
2312
2483
  child_hook = -> {
2313
- DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2484
+ DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2314
2485
  SESSION.activate on_fork: true
2315
2486
  }
2316
2487
  when :both
@@ -2321,7 +2492,7 @@ module DEBUGGER__
2321
2492
  SESSION.after_fork_parent
2322
2493
  }
2323
2494
  child_hook = -> {
2324
- DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2495
+ DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2325
2496
  SESSION.process_group.after_fork child: true
2326
2497
  SESSION.activate on_fork: true
2327
2498
  }
@@ -2348,6 +2519,7 @@ module DEBUGGER__
2348
2519
  module ::Process
2349
2520
  class << self
2350
2521
  prepend ForkInterceptor
2522
+ prepend DaemonInterceptor
2351
2523
  end
2352
2524
  end
2353
2525
 
@@ -2383,6 +2555,7 @@ module DEBUGGER__
2383
2555
  module ::Process
2384
2556
  class << self
2385
2557
  prepend ForkInterceptor
2558
+ prepend DaemonInterceptor
2386
2559
  end
2387
2560
  end
2388
2561
  end
@@ -2399,10 +2572,17 @@ module Kernel
2399
2572
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
2400
2573
 
2401
2574
  if pre || (do_expr = binding.local_variable_get(:do))
2402
- cmds = ['binding.break', pre, do_expr]
2575
+ cmds = ['#debugger', pre, do_expr]
2403
2576
  end
2404
2577
 
2405
- loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
2578
+ if ::DEBUGGER__::SESSION.in_subsession?
2579
+ if cmds
2580
+ commands = [*cmds[1], *cmds[2]].map{|c| c.split(';;').join("\n")}
2581
+ ::DEBUGGER__::SESSION.add_preset_commands cmds[0], commands, kick: false, continue: false
2582
+ end
2583
+ else
2584
+ loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
2585
+ end
2406
2586
  self
2407
2587
  end
2408
2588