debug 1.0.0.beta4 → 1.0.0.beta8

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.
data/lib/debug/session.rb CHANGED
@@ -1,8 +1,14 @@
1
- 
1
+ # frozen_string_literal: true
2
+
3
+ # skip to load debugger for bundle exec
4
+ return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
5
+
6
+ require_relative 'config'
2
7
  require_relative 'thread_client'
3
8
  require_relative 'source_repository'
4
9
  require_relative 'breakpoint'
5
- require_relative 'config'
10
+
11
+ require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
6
12
 
7
13
  class RubyVM::InstructionSequence
8
14
  def traceable_lines_norec lines
@@ -40,9 +46,15 @@ class RubyVM::InstructionSequence
40
46
  def last_line
41
47
  self.to_a[4][:code_location][2]
42
48
  end
49
+
50
+ def first_line
51
+ self.to_a[4][:code_location][0]
52
+ end
43
53
  end
44
54
 
45
55
  module DEBUGGER__
56
+ PresetCommand = Struct.new(:commands, :source, :auto_continue)
57
+
46
58
  class Session
47
59
  def initialize ui
48
60
  @ui = ui
@@ -51,85 +63,29 @@ module DEBUGGER__
51
63
  # [file, line] => LineBreakpoint
52
64
  # "Error" => CatchBreakpoint
53
65
  # "Foo#bar" => MethodBreakpoint
54
- # [:watch, expr] => WatchExprBreakpoint
66
+ # [:watch, ivar] => WatchIVarBreakpoint
55
67
  # [:check, expr] => CheckBreakpoint
56
68
  @th_clients = {} # {Thread => ThreadClient}
57
69
  @q_evt = Queue.new
58
70
  @displays = []
59
71
  @tc = nil
60
72
  @tc_id = 0
61
- @initial_commands = []
73
+ @preset_command = nil
74
+
75
+ @frame_map = {} # {id => [threadId, frame_depth]} for DAP
76
+ @var_map = {1 => [:globals], } # {id => ...} for DAP
77
+ @src_map = {} # {id => src}
62
78
 
63
79
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
64
- ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
80
+ unless @management_threads.include? Thread.current
81
+ ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
82
+ end
65
83
  }
66
84
  @tp_load_script.enable
67
85
 
68
86
  @session_server = Thread.new do
69
87
  Thread.current.abort_on_exception = true
70
-
71
- while evt = @q_evt.pop
72
- tc, output, ev, *ev_args = evt
73
- output.each{|str| @ui.puts str}
74
-
75
- case ev
76
- when :load
77
- iseq, src = ev_args
78
- on_load iseq, src
79
- tc << :continue
80
- when :thread_begin
81
- th = ev_args.shift
82
- on_thread_begin th
83
- tc << :continue
84
- when :suspend
85
- case ev_args.first
86
- when :breakpoint
87
- bp, i = bp_index ev_args[1]
88
- if bp
89
- @ui.puts "\nStop by \##{i} #{bp}"
90
- end
91
- when :trap
92
- @ui.puts ''
93
- @ui.puts "\nStop by #{ev_args[1]}"
94
- end
95
-
96
- if @displays.empty?
97
- wait_command_loop tc
98
- else
99
- tc << [:eval, :display, @displays]
100
- end
101
- when :result
102
- case ev_args.first
103
- when :watch
104
- bp = ev_args[1]
105
- @bps[bp.key] = bp
106
- show_bps bp
107
- when :try_display
108
- failed_results = ev_args[1]
109
- if failed_results.size > 0
110
- i, msg = failed_results.last
111
- if i+1 == @displays.size
112
- @ui.puts "canceled: #{@displays.pop}"
113
- end
114
- end
115
- when :method_breakpoint
116
- bp = ev_args[1]
117
- if bp
118
- @bps[bp.key] = bp
119
- show_bps bp
120
- else
121
- # can't make a bp
122
- end
123
- else
124
- # ignore
125
- end
126
-
127
- wait_command_loop tc
128
- end
129
- end
130
- ensure
131
- @bps.each{|k, bp| bp.disable}
132
- @th_clients.each{|th, thc| thc.close}
88
+ session_server_main
133
89
  end
134
90
 
135
91
  @management_threads = [@session_server]
@@ -138,23 +94,116 @@ module DEBUGGER__
138
94
  setup_threads
139
95
 
140
96
  @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
141
- ThreadClient.current.on_thread_begin Thread.current
97
+ unless @management_threads.include?(th = Thread.current)
98
+ ThreadClient.current.on_thread_begin th
99
+ end
142
100
  }
143
101
  @tp_thread_begin.enable
144
102
  end
145
103
 
146
- def add_initial_commands cmds
147
- cmds.each{|c|
148
- c.gsub('#.*', '').strip!
149
- @initial_commands << c unless c.empty?
150
- }
104
+ def active?
105
+ @ui ? true : false
106
+ end
107
+
108
+ def reset_ui ui
109
+ @ui.close
110
+ @ui = ui
111
+ @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
151
112
  end
152
113
 
153
- def source path
154
- if CONFIG[:use_colorize]
155
- @sr.get_colored(path)
114
+ def session_server_main
115
+ while evt = @q_evt.pop
116
+ # varible `@internal_info` is only used for test
117
+ tc, output, ev, @internal_info, *ev_args = evt
118
+ output.each{|str| @ui.puts str}
119
+
120
+ case ev
121
+ when :init
122
+ wait_command_loop tc
123
+ when :load
124
+ iseq, src = ev_args
125
+ on_load iseq, src
126
+ @ui.event :load
127
+ tc << :continue
128
+ when :thread_begin
129
+ th = ev_args.shift
130
+ on_thread_begin th
131
+ @ui.event :thread_begin, th
132
+ tc << :continue
133
+ when :suspend
134
+ case ev_args.first
135
+ when :breakpoint
136
+ bp, i = bp_index ev_args[1]
137
+ @ui.event :suspend_bp, i, bp
138
+ when :trap
139
+ @ui.event :suspend_trap, ev_args[1]
140
+ else
141
+ @ui.event :suspended
142
+ end
143
+
144
+ if @displays.empty?
145
+ wait_command_loop tc
146
+ else
147
+ tc << [:eval, :display, @displays]
148
+ end
149
+ when :result
150
+ case ev_args.first
151
+ when :try_display
152
+ failed_results = ev_args[1]
153
+ if failed_results.size > 0
154
+ i, _msg = failed_results.last
155
+ if i+1 == @displays.size
156
+ @ui.puts "canceled: #{@displays.pop}"
157
+ end
158
+ end
159
+ when :method_breakpoint, :watch_breakpoint
160
+ bp = ev_args[1]
161
+ if bp
162
+ add_breakpoint(bp)
163
+ show_bps bp
164
+ else
165
+ # can't make a bp
166
+ end
167
+ else
168
+ # ignore
169
+ end
170
+
171
+ wait_command_loop tc
172
+
173
+ when :dap_result
174
+ dap_event ev_args # server.rb
175
+ wait_command_loop tc
176
+ end
177
+ end
178
+ ensure
179
+ @tp_load_script.disable
180
+ @tp_thread_begin.disable
181
+ @bps.each{|k, bp| bp.disable}
182
+ @th_clients.each{|th, thc| thc.close}
183
+ @ui = nil
184
+ end
185
+
186
+ def add_preset_commands name, cmds, kick: true, continue: true
187
+ cs = cmds.map{|c|
188
+ c = c.strip.gsub(/\A\s*\#.*/, '').strip
189
+ c unless c.empty?
190
+ }.compact
191
+
192
+ unless cs.empty?
193
+ if @preset_command
194
+ @preset_command.commands += cs
195
+ else
196
+ @preset_command = PresetCommand.new(cs, name, continue)
197
+ end
198
+ ThreadClient.current.on_init name if kick
199
+ end
200
+ end
201
+
202
+ def source iseq
203
+ if !CONFIG[:no_color]
204
+ @sr.get_colored(iseq)
156
205
  else
157
- @sr.get(path)
206
+ @sr.get(iseq)
158
207
  end
159
208
  end
160
209
 
@@ -181,13 +230,36 @@ module DEBUGGER__
181
230
  end
182
231
 
183
232
  def wait_command
184
- if @initial_commands.empty?
233
+ if @preset_command
234
+ if @preset_command.commands.empty?
235
+ if @preset_command.auto_continue
236
+ @preset_command = nil
237
+ @tc << :continue
238
+ return
239
+ else
240
+ @preset_command = nil
241
+ return :retry
242
+ end
243
+ else
244
+ line = @preset_command.commands.shift
245
+ @ui.puts "(rdbg:#{@preset_command.source}) #{line}"
246
+ end
247
+ else
248
+ @ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
185
249
  line = @ui.readline
250
+ end
251
+
252
+ case line
253
+ when String
254
+ process_command line
255
+ when Hash
256
+ process_dap_request line # defined in server.rb
186
257
  else
187
- line = @initial_commands.shift.strip
188
- @ui.puts "(rdbg:init) #{line}"
258
+ raise "unexpected input: #{line.inspect}"
189
259
  end
260
+ end
190
261
 
262
+ def process_command line
191
263
  if line.empty?
192
264
  if @repl_prev_line
193
265
  line = @repl_prev_line
@@ -209,21 +281,25 @@ module DEBUGGER__
209
281
  # * `s[tep]`
210
282
  # * Step in. Resume the program until next breakable point.
211
283
  when 's', 'step'
284
+ cancel_auto_continue
212
285
  @tc << [:step, :in]
213
286
 
214
287
  # * `n[ext]`
215
288
  # * Step over. Resume the program until next line.
216
289
  when 'n', 'next'
290
+ cancel_auto_continue
217
291
  @tc << [:step, :next]
218
292
 
219
293
  # * `fin[ish]`
220
294
  # * Finish this frame. Resume the program until the current frame is finished.
221
295
  when 'fin', 'finish'
296
+ cancel_auto_continue
222
297
  @tc << [:step, :finish]
223
298
 
224
299
  # * `c[ontinue]`
225
300
  # * Resume the program.
226
301
  when 'c', 'continue'
302
+ cancel_auto_continue
227
303
  @tc << :continue
228
304
 
229
305
  # * `q[uit]` or `Ctrl-D`
@@ -268,10 +344,14 @@ module DEBUGGER__
268
344
  # * Set breakpoint on the method `<class>#<name>`.
269
345
  # * `b[reak] <expr>.<name>`
270
346
  # * Set breakpoint on the method `<expr>.<name>`.
271
- # * `b[reak] ... if <expr>`
347
+ # * `b[reak] ... if: <expr>`
272
348
  # * break if `<expr>` is true at specified location.
273
- # * `b[reak] if <expr>`
274
- # * break if `<expr>` is true at any lines.
349
+ # * `b[reak] ... pre: <command>`
350
+ # * break and run `<command>` before stopping.
351
+ # * `b[reak] ... do: <command>`
352
+ # * break and run `<command>`, and continue.
353
+ # * `b[reak] if: <expr>`
354
+ # * break if: `<expr>` is true at any lines.
275
355
  # * Note that this feature is super slow.
276
356
  when 'b', 'break'
277
357
  if arg == nil
@@ -324,12 +404,12 @@ module DEBUGGER__
324
404
  end
325
405
  return :retry
326
406
 
327
- # * `watch <expr>`
328
- # * Stop the execution when the result of `<expr>` is changed.
407
+ # * `watch @ivar`
408
+ # * Stop the execution when the result of current scope's `@ivar` is changed.
329
409
  # * Note that this feature is super slow.
330
410
  when 'wat', 'watch'
331
- if arg
332
- @tc << [:eval, :watch, arg]
411
+ if arg && arg.match?(/\A@\w+/)
412
+ @tc << [:breakpoint, :watch, arg]
333
413
  else
334
414
  show_bps
335
415
  return :retry
@@ -359,8 +439,25 @@ module DEBUGGER__
359
439
 
360
440
  # * `bt` or `backtrace`
361
441
  # * Show backtrace (frame) information.
442
+ # * `bt <num>` or `backtrace <num>`
443
+ # * Only shows first `<num>` frames.
444
+ # * `bt /regexp/` or `backtrace /regexp/`
445
+ # * Only shows frames with method name or location info that matches `/regexp/`.
446
+ # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
447
+ # * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
362
448
  when 'bt', 'backtrace'
363
- @tc << [:show, :backtrace]
449
+ case arg
450
+ when /\A(\d+)\z/
451
+ @tc << [:show, :backtrace, arg.to_i, nil]
452
+ when /\A\/(.*)\/\z/
453
+ pattern = $1
454
+ @tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
455
+ when /\A(\d+)\s+\/(.*)\/\z/
456
+ max, pattern = $1, $2
457
+ @tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
458
+ else
459
+ @tc << [:show, :backtrace, nil, nil]
460
+ end
364
461
 
365
462
  # * `l[ist]`
366
463
  # * Show current frame's source code.
@@ -407,7 +504,7 @@ module DEBUGGER__
407
504
  # * `i[nfo]`, `i[nfo] l[ocal[s]]`
408
505
  # * Show information about the current frame (local variables)
409
506
  # * It includes `self` as `%self` and a return value as `%return`.
410
- # * `i[nfo] th[read[s]]
507
+ # * `i[nfo] th[read[s]]`
411
508
  # * Show all threads (same as `th[read]`).
412
509
  when 'i', 'info'
413
510
  case arg
@@ -443,9 +540,7 @@ module DEBUGGER__
443
540
  case arg
444
541
  when /(\d+)/
445
542
  if @displays[n = $1.to_i]
446
- if ask "clear \##{n} #{@displays[n]}?"
447
- @displays.delete_at n
448
- end
543
+ @displays.delete_at n
449
544
  end
450
545
  @tc << [:eval, :display, @displays]
451
546
  when nil
@@ -455,26 +550,6 @@ module DEBUGGER__
455
550
  end
456
551
  return :retry
457
552
 
458
- # * `trace [on|off]`
459
- # * enable or disable line tracer.
460
- when 'trace'
461
- case arg
462
- when 'on'
463
- dir = __dir__
464
- @tracer ||= TracePoint.new(){|tp|
465
- next if File.dirname(tp.path) == dir
466
- next if tp.path == '<internal:trace_point>'
467
- # next if tp.event != :line
468
- @ui.puts pretty_tp(tp)
469
- }
470
- @tracer.enable
471
- when 'off'
472
- @tracer && @tracer.disable
473
- end
474
- enabled = (@tracer && @tracer.enabled?) ? true : false
475
- @ui.puts "Trace #{enabled ? 'on' : 'off'}"
476
- return :retry
477
-
478
553
  ### Frame control
479
554
 
480
555
  # * `f[rame]`
@@ -520,6 +595,9 @@ module DEBUGGER__
520
595
  end
521
596
  @tc << [:eval, :call, 'binding.irb']
522
597
 
598
+ # don't repeat irb command
599
+ @repl_prev_line = nil
600
+
523
601
  ### Thread control
524
602
 
525
603
  # * `th[read]`
@@ -537,6 +615,21 @@ module DEBUGGER__
537
615
  end
538
616
  return :retry
539
617
 
618
+ ### Configuration
619
+ # * `config`
620
+ # * Show all configuration with description.
621
+ # * `config <name>`
622
+ # * Show current configuration of <name>.
623
+ # * `config set <name> <val>` or `config <name> = <val>`
624
+ # * Set <name> to <val>.
625
+ # * `config append <name> <val>` or `config <name> << <val>`
626
+ # * Append `<val>` to `<name>` if it is an array.
627
+ # * `config unset <name>`
628
+ # * Set <name> to default.
629
+ when 'config'
630
+ config_command arg
631
+ return :retry
632
+
540
633
  ### Help
541
634
 
542
635
  # * `h[elp]`
@@ -568,6 +661,79 @@ module DEBUGGER__
568
661
  return :retry
569
662
  end
570
663
 
664
+ def config_show key
665
+ key = key.to_sym
666
+ if CONFIG_SET[key]
667
+ v = CONFIG[key]
668
+ kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
669
+ desc = CONFIG_SET[key][1]
670
+ line = "%-30s \# %s" % [kv, desc]
671
+ if line.size > SESSION.width
672
+ @ui.puts "\# #{desc}\n#{kv}"
673
+ else
674
+ @ui.puts line
675
+ end
676
+ else
677
+ @ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
678
+ end
679
+ end
680
+
681
+ def config_set key, val, append: false
682
+ if CONFIG_SET[key = key.to_sym]
683
+ begin
684
+ if append
685
+ DEBUGGER__.append_config(key, val)
686
+ else
687
+ DEBUGGER__.set_config({key => val})
688
+ end
689
+ rescue => e
690
+ @ui.puts e.message
691
+ end
692
+ end
693
+
694
+ config_show key
695
+ end
696
+
697
+ def config_command arg
698
+ case arg
699
+ when nil
700
+ CONFIG_SET.each do |k, _|
701
+ config_show k
702
+ end
703
+
704
+ when /\Aunset\s+(.+)\z/
705
+ if CONFIG_SET[key = $1.to_sym]
706
+ DEBUGGER__.set_config({key => nil})
707
+ end
708
+ config_show key
709
+
710
+ when /\A(\w+)\s*=\s*(.+)\z/
711
+ config_set $1, $2
712
+
713
+ when /\A\s*set\s+(\w+)\s+(.+)\z/
714
+ config_set $1, $2
715
+
716
+ when /\A(\w+)\s*<<\s*(.+)\z/
717
+ config_set $1, $2, append: true
718
+
719
+ when /\A\s*append\s+(\w+)\s+(.+)\z/
720
+ config_set $1, $2
721
+
722
+ when /\A(\w+)\z/
723
+ config_show $1
724
+
725
+ else
726
+ @ui.puts "Can not parse parameters: #{arg}"
727
+ end
728
+ end
729
+
730
+
731
+ def cancel_auto_continue
732
+ if @preset_command&.auto_continue
733
+ @preset_command.auto_continue = false
734
+ end
735
+ end
736
+
571
737
  def show_help arg
572
738
  DEBUGGER__.helps.each{|cat, cs|
573
739
  cs.each{|ws, desc|
@@ -684,29 +850,37 @@ module DEBUGGER__
684
850
  end
685
851
  end
686
852
 
687
- def repl_add_breakpoint arg
688
- arg.strip!
853
+ BREAK_KEYWORDS = %w(if: do: pre:).freeze
689
854
 
690
- case arg
691
- when /\Aif\s+(.+)\z/
692
- cond = $1
693
- when /(.+?)\s+if\s+(.+)\z/
694
- sig = $1
695
- cond = $2
696
- else
697
- sig = arg
698
- end
855
+ def parse_break arg
856
+ mode = :sig
857
+ expr = Hash.new{|h, k| h[k] = []}
858
+ arg.split(' ').each{|w|
859
+ if BREAK_KEYWORDS.any?{|pat| w == pat}
860
+ mode = w[0..-2].to_sym
861
+ else
862
+ expr[mode] << w
863
+ end
864
+ }
865
+ expr.default_proc = nil
866
+ expr.transform_values{|v| v.join(' ')}
867
+ end
868
+
869
+ def repl_add_breakpoint arg
870
+ expr = parse_break arg.strip
871
+ cond = expr[:if]
872
+ cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
699
873
 
700
- case sig
874
+ case expr[:sig]
701
875
  when /\A(\d+)\z/
702
- add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
876
+ add_line_breakpoint @tc.location.path, $1.to_i, cond: expr[:if], command: cmd
703
877
  when /\A(.+)[:\s+](\d+)\z/
704
- add_line_breakpoint $1, $2.to_i, cond: cond
878
+ add_line_breakpoint $1, $2.to_i, cond: expr[:if], command: cmd
705
879
  when /\A(.+)([\.\#])(.+)\z/
706
- @tc << [:breakpoint, :method, $1, $2, $3, cond]
880
+ @tc << [:breakpoint, :method, $1, $2, $3, expr[:if], cmd]
707
881
  return :noretry
708
882
  when nil
709
- add_check_breakpoint cond
883
+ add_check_breakpoint expr[:if]
710
884
  else
711
885
  @ui.puts "Unknown breakpoint format: #{arg}"
712
886
  @ui.puts
@@ -725,6 +899,8 @@ module DEBUGGER__
725
899
  case
726
900
  when th == Thread.current
727
901
  # ignore
902
+ when @management_threads.include?(th)
903
+ # ignore
728
904
  when @th_clients.has_key?(th)
729
905
  thcs << @th_clients[th]
730
906
  else
@@ -748,8 +924,13 @@ module DEBUGGER__
748
924
  end
749
925
  end
750
926
 
927
+ def managed_thread_clients
928
+ thcs, _unmanaged_ths = update_thread_list
929
+ thcs
930
+ end
931
+
751
932
  def thread_switch n
752
- thcs, unmanaged_ths = update_thread_list
933
+ thcs, _unmanaged_ths = update_thread_list
753
934
 
754
935
  if tc = thcs[n]
755
936
  if tc.mode
@@ -823,6 +1004,7 @@ module DEBUGGER__
823
1004
  ## event
824
1005
 
825
1006
  def on_load iseq, src
1007
+ DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
826
1008
  @sr.add iseq, src
827
1009
 
828
1010
  pending_line_breakpoints do |bp|
@@ -835,8 +1017,14 @@ module DEBUGGER__
835
1017
  # breakpoint management
836
1018
 
837
1019
  def add_breakpoint bp
1020
+ # don't repeat commands that add breakpoints
1021
+ @repl_prev_line = nil
1022
+
838
1023
  if @bps.has_key? bp.key
839
- @ui.puts "duplicated breakpoint: #{bp}"
1024
+ unless bp.duplicable?
1025
+ @ui.puts "duplicated breakpoint: #{bp}"
1026
+ bp.disable
1027
+ end
840
1028
  else
841
1029
  @bps[bp.key] = bp
842
1030
  end
@@ -867,12 +1055,16 @@ module DEBUGGER__
867
1055
  def resolve_path file
868
1056
  File.realpath(File.expand_path(file))
869
1057
  rescue Errno::ENOENT
870
- return file if file == '-e'
871
- $LOAD_PATH.each do |lp|
872
- libpath = File.join(lp, file)
873
- return File.realpath(libpath)
874
- rescue Errno::ENOENT
875
- # next
1058
+ case file
1059
+ when '-e', '-'
1060
+ return file
1061
+ else
1062
+ $LOAD_PATH.each do |lp|
1063
+ libpath = File.join(lp, file)
1064
+ return File.realpath(libpath)
1065
+ rescue Errno::ENOENT
1066
+ # next
1067
+ end
876
1068
  end
877
1069
 
878
1070
  raise
@@ -881,6 +1073,7 @@ module DEBUGGER__
881
1073
  def add_line_breakpoint file, line, **kw
882
1074
  file = resolve_path(file)
883
1075
  bp = LineBreakpoint.new(file, line, **kw)
1076
+
884
1077
  add_breakpoint bp
885
1078
  rescue Errno::ENOENT => e
886
1079
  @ui.puts e.message
@@ -905,7 +1098,7 @@ module DEBUGGER__
905
1098
  when MethodBreakpoint
906
1099
  if bp.method.nil?
907
1100
  if bp.sig_method_name == mid.to_s
908
- bp.try_enable(quiet: true)
1101
+ bp.try_enable(added: true)
909
1102
  end
910
1103
  end
911
1104
 
@@ -917,6 +1110,29 @@ module DEBUGGER__
917
1110
  end
918
1111
  end
919
1112
  end
1113
+
1114
+ def width
1115
+ @ui.width
1116
+ end
1117
+
1118
+ def check_forked
1119
+ unless @session_server.status
1120
+ # TODO: Support it
1121
+ raise 'DEBUGGER: stop at forked process is not supported yet.'
1122
+ end
1123
+ end
1124
+ end
1125
+
1126
+ class UI_Base
1127
+ def event type, *args
1128
+ case type
1129
+ when :suspend_bp
1130
+ i, bp = *args
1131
+ puts "\nStop by \##{i} #{bp}" if bp
1132
+ when :suspend_trap
1133
+ puts "\nStop by #{args.first}"
1134
+ end
1135
+ end
920
1136
  end
921
1137
 
922
1138
  # manual configuration methods
@@ -940,7 +1156,7 @@ module DEBUGGER__
940
1156
  when dir_prefix
941
1157
  when %r{rubygems/core_ext/kernel_require\.rb}
942
1158
  else
943
- return loc
1159
+ return loc if loc.absolute_path
944
1160
  end
945
1161
  end
946
1162
  nil
@@ -948,43 +1164,71 @@ module DEBUGGER__
948
1164
 
949
1165
  # start methods
950
1166
 
951
- def self.console **kw
1167
+ def self.start nonstop: false, **kw
952
1168
  set_config(kw)
953
1169
 
954
- require_relative 'console'
955
- initialize_session UI_Console.new
1170
+ unless defined? SESSION
1171
+ require_relative 'console'
1172
+ initialize_session UI_Console.new
1173
+ end
956
1174
 
957
- @prev_handler = trap(:SIGINT){
958
- ThreadClient.current.on_trap :SIGINT
959
- }
1175
+ setup_initial_suspend unless nonstop
960
1176
  end
961
1177
 
962
- def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, **kw
1178
+ def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
963
1179
  set_config(kw)
964
1180
 
965
1181
  if port
966
- open_tcp host: host, port: port
1182
+ open_tcp host: host, port: port, nonstop: nonstop
967
1183
  else
968
- open_unix sock_path: sock_path, sock_dir: sock_dir
1184
+ open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
969
1185
  end
970
1186
  end
971
1187
 
972
- def self.open_tcp host: nil, port:, **kw
1188
+ def self.open_tcp host: nil, port:, nonstop: false, **kw
973
1189
  set_config(kw)
974
1190
  require_relative 'server'
975
- initialize_session UI_TcpServer.new(host: host, port: port)
1191
+
1192
+ if defined? SESSION
1193
+ SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
1194
+ else
1195
+ initialize_session UI_TcpServer.new(host: host, port: port)
1196
+ end
1197
+
1198
+ setup_initial_suspend unless nonstop
976
1199
  end
977
1200
 
978
- def self.open_unix sock_path: nil, sock_dir: nil, **kw
1201
+ def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
979
1202
  set_config(kw)
980
1203
  require_relative 'server'
981
- initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1204
+
1205
+ if defined? SESSION
1206
+ SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1207
+ else
1208
+ initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1209
+ end
1210
+
1211
+ setup_initial_suspend unless nonstop
982
1212
  end
983
1213
 
984
1214
  # boot utilities
985
1215
 
1216
+ def self.setup_initial_suspend
1217
+ if !::DEBUGGER__::CONFIG[:nonstop]
1218
+ if loc = ::DEBUGGER__.require_location
1219
+ # require 'debug/console' or 'debug'
1220
+ add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
1221
+ else
1222
+ # -r
1223
+ add_line_breakpoint $0, 1, oneshot: true, hook_call: false
1224
+ end
1225
+ end
1226
+ end
1227
+
986
1228
  class << self
987
1229
  define_method :initialize_session do |ui|
1230
+ DEBUGGER__.warn "Session start (pid: #{Process.pid})"
1231
+
988
1232
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
989
1233
 
990
1234
  # default breakpoints
@@ -992,18 +1236,18 @@ module DEBUGGER__
992
1236
  # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
993
1237
 
994
1238
  Binding.module_eval do
995
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
996
- def bp; nil; end
997
- end
1239
+ def break pre: nil, do: nil
1240
+ return unless SESSION.active?
998
1241
 
999
- if !::DEBUGGER__::CONFIG[:nonstop]
1000
- if loc = ::DEBUGGER__.require_location
1001
- # require 'debug/console' or 'debug'
1002
- add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
1003
- else
1004
- # -r
1005
- add_line_breakpoint $0, 1, oneshot: true, hook_call: false
1242
+ if pre || (do_expr = binding.local_variable_get(:do))
1243
+ cmds = ['binding.break', pre, do_expr]
1244
+ end
1245
+
1246
+ ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
1247
+ true
1006
1248
  end
1249
+ alias b break
1250
+ # alias bp break
1007
1251
  end
1008
1252
 
1009
1253
  load_rc
@@ -1011,27 +1255,29 @@ module DEBUGGER__
1011
1255
  end
1012
1256
 
1013
1257
  def self.load_rc
1014
- ['./rdbgrc.rb', File.expand_path('~/.rdbgrc.rb')].each{|path|
1015
- if File.file? path
1016
- load path
1017
- end
1018
- }
1019
-
1020
- # debug commands file
1021
- [::DEBUGGER__::CONFIG[:init_script],
1022
- './.rdbgrc',
1023
- File.expand_path('~/.rdbgrc')].each{|path|
1258
+ [[File.expand_path('~/.rdbgrc'), true],
1259
+ [File.expand_path('~/.rdbgrc.rb'), true],
1260
+ # ['./.rdbgrc', true], # disable because of security concern
1261
+ [::DEBUGGER__::CONFIG[:init_script], false],
1262
+ ].each{|(path, rc)|
1024
1263
  next unless path
1264
+ next if rc && ::DEBUGGER__::CONFIG[:no_rc] # ignore rc
1025
1265
 
1026
1266
  if File.file? path
1027
- ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
1267
+ if path.end_with?('.rb')
1268
+ load path
1269
+ else
1270
+ ::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
1271
+ end
1272
+ elsif !rc
1273
+ warn "Not found: #{path}"
1028
1274
  end
1029
1275
  }
1030
1276
 
1031
1277
  # given debug commands
1032
1278
  if ::DEBUGGER__::CONFIG[:commands]
1033
1279
  cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
1034
- ::DEBUGGER__::SESSION.add_initial_commands cmds
1280
+ ::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
1035
1281
  end
1036
1282
  end
1037
1283
 
@@ -1086,6 +1332,7 @@ module DEBUGGER__
1086
1332
  end
1087
1333
 
1088
1334
  class ::Module
1335
+ undef method_added
1089
1336
  def method_added mid; end
1090
1337
  def singleton_method_added mid; end
1091
1338
  end
@@ -1099,4 +1346,44 @@ module DEBUGGER__
1099
1346
  end
1100
1347
 
1101
1348
  METHOD_ADDED_TRACKER = self.create_method_added_tracker
1349
+
1350
+ SHORT_INSPECT_LENGTH = 40
1351
+ def self.short_inspect obj, use_short = true
1352
+ str = obj.inspect
1353
+ if use_short && str.length > SHORT_INSPECT_LENGTH
1354
+ str[0...SHORT_INSPECT_LENGTH] + '...'
1355
+ else
1356
+ str
1357
+ end
1358
+ end
1359
+
1360
+ LOG_LEVELS = {
1361
+ UNKNOWN: 0,
1362
+ FATAL: 1,
1363
+ ERROR: 2,
1364
+ WARN: 3,
1365
+ INFO: 4,
1366
+ }.freeze
1367
+
1368
+ def self.warn msg
1369
+ log :WARN, msg
1370
+ end
1371
+
1372
+ def self.info msg
1373
+ log :INFO, msg
1374
+ end
1375
+
1376
+ def self.log level, msg
1377
+ lv = LOG_LEVELS[level]
1378
+ config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
1379
+
1380
+ if lv <= config_lv
1381
+ if level == :WARN
1382
+ # :WARN on debugger is general information
1383
+ STDERR.puts "DEBUGGER: #{msg}"
1384
+ else
1385
+ STDERR.puts "DEBUGGER (#{level}): #{msg}"
1386
+ end
1387
+ end
1388
+ end
1102
1389
  end