debug 1.0.0.beta2 → 1.0.0.beta7

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,15 +1,14 @@
1
- module DEBUGGER__
2
- # used in thread_client.c
3
- FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
- :has_return_value, :return_value, :show_line)
5
- end
1
+ # frozen_string_literal: true
6
2
 
7
- require "debug/debug"
3
+ # skip to load debugger for bundle exec
4
+ return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
8
5
 
6
+ require_relative 'config'
7
+ require_relative 'thread_client'
9
8
  require_relative 'source_repository'
10
9
  require_relative 'breakpoint'
11
- require_relative 'thread_client'
12
- require_relative 'config'
10
+
11
+ require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
13
12
 
14
13
  class RubyVM::InstructionSequence
15
14
  def traceable_lines_norec lines
@@ -47,9 +46,15 @@ class RubyVM::InstructionSequence
47
46
  def last_line
48
47
  self.to_a[4][:code_location][2]
49
48
  end
49
+
50
+ def first_line
51
+ self.to_a[4][:code_location][0]
52
+ end
50
53
  end
51
54
 
52
55
  module DEBUGGER__
56
+ PresetCommand = Struct.new(:commands, :source, :auto_continue)
57
+
53
58
  class Session
54
59
  def initialize ui
55
60
  @ui = ui
@@ -57,103 +62,149 @@ module DEBUGGER__
57
62
  @bps = {} # bp.key => bp
58
63
  # [file, line] => LineBreakpoint
59
64
  # "Error" => CatchBreakpoint
60
- # Method => MethodBreakpoint
61
- # [:watch, expr] => WatchExprBreakpoint
65
+ # "Foo#bar" => MethodBreakpoint
66
+ # [:watch, ivar] => WatchIVarBreakpoint
62
67
  # [:check, expr] => CheckBreakpoint
63
68
  @th_clients = {} # {Thread => ThreadClient}
64
69
  @q_evt = Queue.new
65
70
  @displays = []
66
71
  @tc = nil
67
72
  @tc_id = 0
68
- @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}
69
78
 
70
79
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
71
- ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
72
- }.enable
80
+ unless @management_threads.include? Thread.current
81
+ ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
82
+ end
83
+ }
84
+ @tp_load_script.enable
73
85
 
74
86
  @session_server = Thread.new do
75
87
  Thread.current.abort_on_exception = true
88
+ session_server_main
89
+ end
76
90
 
77
- while evt = @q_evt.pop
78
- tc, output, ev, *ev_args = evt
79
- output.each{|str| @ui.puts str}
80
-
81
- case ev
82
- when :load
83
- iseq, src = ev_args
84
- on_load iseq, src
85
- tc << :continue
86
- when :thread_begin
87
- th = ev_args.shift
88
- on_thread_begin th
89
- tc << :continue
90
- when :suspend
91
- case ev_args.first
92
- when :breakpoint
93
- bp, i = bp_index ev_args[1]
94
- if bp
95
- @ui.puts "\nStop by \##{i} #{bp}"
96
- end
97
- when :trap
98
- @ui.puts ''
99
- @ui.puts "\nStop by #{ev_args[1]}"
100
- end
91
+ @management_threads = [@session_server]
92
+ @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
101
93
 
102
- if @displays.empty?
103
- wait_command_loop tc
104
- else
105
- tc << [:eval, :display, @displays]
94
+ setup_threads
95
+
96
+ @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
97
+ unless @management_threads.include?(th = Thread.current)
98
+ ThreadClient.current.on_thread_begin th
99
+ end
100
+ }
101
+ @tp_thread_begin.enable
102
+ end
103
+
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
112
+ end
113
+
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
106
158
  end
107
- when :result
108
- case ev_args.first
109
- when :watch
110
- bp = ev_args[1]
111
- @bps[bp.key] = bp
159
+ when :method_breakpoint, :watch_breakpoint
160
+ bp = ev_args[1]
161
+ if bp
162
+ add_breakpoint(bp)
112
163
  show_bps bp
113
- when :try_display
114
- failed_results = ev_args[1]
115
- if failed_results.size > 0
116
- i, msg = failed_results.last
117
- if i+1 == @displays.size
118
- @ui.puts "canceled: #{@displays.pop}"
119
- end
120
- end
121
- when :method_breakpoint
122
- bp = ev_args[1]
123
- if bp
124
- @bps[bp.key] = bp
125
- show_bps bp
126
- else
127
- # can't make a bp
128
- end
129
164
  else
130
- # ignore
165
+ # can't make a bp
131
166
  end
132
-
133
- wait_command_loop tc
167
+ else
168
+ # ignore
134
169
  end
135
- end
136
- end
137
170
 
138
- @management_threads = [@session_server]
139
- @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
171
+ wait_command_loop tc
140
172
 
141
- setup_threads
142
-
143
- @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
144
- ThreadClient.current.on_thread_begin Thread.current
145
- }.enable
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
146
184
  end
147
185
 
148
- def add_initial_commands cmds
149
- cmds.each{|c|
150
- c.gsub('#.*', '').strip!
151
- @initial_commands << c unless c.empty?
152
- }
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
153
200
  end
154
201
 
155
- def source path
156
- @sr.get(path)
202
+ def source iseq
203
+ if !CONFIG[:no_color]
204
+ @sr.get_colored(iseq)
205
+ else
206
+ @sr.get(iseq)
207
+ end
157
208
  end
158
209
 
159
210
  def inspect
@@ -179,13 +230,36 @@ module DEBUGGER__
179
230
  end
180
231
 
181
232
  def wait_command
182
- 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']
183
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
184
257
  else
185
- line = @initial_commands.shift.strip
186
- @ui.puts "(rdbg:init) #{line}"
258
+ raise "unexpected input: #{line.inspect}"
187
259
  end
260
+ end
188
261
 
262
+ def process_command line
189
263
  if line.empty?
190
264
  if @repl_prev_line
191
265
  line = @repl_prev_line
@@ -207,26 +281,30 @@ module DEBUGGER__
207
281
  # * `s[tep]`
208
282
  # * Step in. Resume the program until next breakable point.
209
283
  when 's', 'step'
284
+ cancel_auto_continue
210
285
  @tc << [:step, :in]
211
286
 
212
287
  # * `n[ext]`
213
288
  # * Step over. Resume the program until next line.
214
289
  when 'n', 'next'
290
+ cancel_auto_continue
215
291
  @tc << [:step, :next]
216
292
 
217
293
  # * `fin[ish]`
218
294
  # * Finish this frame. Resume the program until the current frame is finished.
219
295
  when 'fin', 'finish'
296
+ cancel_auto_continue
220
297
  @tc << [:step, :finish]
221
298
 
222
299
  # * `c[ontinue]`
223
300
  # * Resume the program.
224
301
  when 'c', 'continue'
302
+ cancel_auto_continue
225
303
  @tc << :continue
226
304
 
227
- # * `q[uit]` or exit or `Ctrl-D`
305
+ # * `q[uit]` or `Ctrl-D`
228
306
  # * Finish debugger (with the debuggee process on non-remote debugging).
229
- when 'q', 'quit', 'exit'
307
+ when 'q', 'quit'
230
308
  if ask 'Really quit?'
231
309
  @ui.quit arg.to_i
232
310
  @tc << :continue
@@ -234,15 +312,26 @@ module DEBUGGER__
234
312
  return :retry
235
313
  end
236
314
 
237
- # * `kill` or `q[uit]!`
238
- # * Stop the debuggee process.
239
- when 'kill', 'quit!', 'q!'
315
+ # * `q[uit]!`
316
+ # * Same as q[uit] but without the confirmation prompt.
317
+ when 'q!', 'quit!'
318
+ @ui.quit arg.to_i
319
+ @tc << :continue
320
+
321
+ # * `kill`
322
+ # * Stop the debuggee process with `Kernal#exit!`.
323
+ when 'kill'
240
324
  if ask 'Really kill?'
241
325
  exit! (arg || 1).to_i
242
326
  else
243
327
  return :retry
244
328
  end
245
329
 
330
+ # * `kill!`
331
+ # * Same as kill but without the confirmation prompt.
332
+ when 'kill!'
333
+ exit! (arg || 1).to_i
334
+
246
335
  ### Breakpoint
247
336
 
248
337
  # * `b[reak]`
@@ -255,10 +344,14 @@ module DEBUGGER__
255
344
  # * Set breakpoint on the method `<class>#<name>`.
256
345
  # * `b[reak] <expr>.<name>`
257
346
  # * Set breakpoint on the method `<expr>.<name>`.
258
- # * `b[reak] ... if <expr>`
347
+ # * `b[reak] ... if: <expr>`
259
348
  # * break if `<expr>` is true at specified location.
260
- # * `b[reak] if <expr>`
261
- # * 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.
262
355
  # * Note that this feature is super slow.
263
356
  when 'b', 'break'
264
357
  if arg == nil
@@ -311,12 +404,12 @@ module DEBUGGER__
311
404
  end
312
405
  return :retry
313
406
 
314
- # * `watch <expr>`
315
- # * 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.
316
409
  # * Note that this feature is super slow.
317
410
  when 'wat', 'watch'
318
- if arg
319
- @tc << [:eval, :watch, arg]
411
+ if arg && arg.match?(/\A@\w+/)
412
+ @tc << [:breakpoint, :watch, arg]
320
413
  else
321
414
  show_bps
322
415
  return :retry
@@ -346,8 +439,25 @@ module DEBUGGER__
346
439
 
347
440
  # * `bt` or `backtrace`
348
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/`.
349
448
  when 'bt', 'backtrace'
350
- @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
351
461
 
352
462
  # * `l[ist]`
353
463
  # * Show current frame's source code.
@@ -391,17 +501,23 @@ module DEBUGGER__
391
501
 
392
502
  @tc << [:show, :edit, arg]
393
503
 
394
- # * `i[nfo]`
504
+ # * `i[nfo]`, `i[nfo] l[ocal[s]]`
395
505
  # * Show information about the current frame (local variables)
396
506
  # * It includes `self` as `%self` and a return value as `%return`.
397
- # * `i[nfo] <expr>`
398
- # * Show information about the result of <expr>.
507
+ # * `i[nfo] th[read[s]]`
508
+ # * Show all threads (same as `th[read]`).
399
509
  when 'i', 'info'
400
510
  case arg
401
511
  when nil
402
512
  @tc << [:show, :local]
513
+ when 'l', /locals?/
514
+ @tc << [:show, :local]
515
+ when 'th', /threads?/
516
+ thread_list
517
+ return :retry
403
518
  else
404
- @tc << [:show, :object_info, arg]
519
+ show_help 'info'
520
+ return :retry
405
521
  end
406
522
 
407
523
  # * `display`
@@ -410,7 +526,7 @@ module DEBUGGER__
410
526
  # * Show the result of `<expr>` at every suspended timing.
411
527
  when 'display'
412
528
  if arg && !arg.empty?
413
- @displays << arg
529
+ @displays << arg
414
530
  @tc << [:eval, :try_display, @displays]
415
531
  else
416
532
  @tc << [:eval, :display, @displays]
@@ -424,9 +540,7 @@ module DEBUGGER__
424
540
  case arg
425
541
  when /(\d+)/
426
542
  if @displays[n = $1.to_i]
427
- if ask "clear \##{n} #{@displays[n]}?"
428
- @displays.delete_at n
429
- end
543
+ @displays.delete_at n
430
544
  end
431
545
  @tc << [:eval, :display, @displays]
432
546
  when nil
@@ -442,9 +556,11 @@ module DEBUGGER__
442
556
  case arg
443
557
  when 'on'
444
558
  dir = __dir__
445
- @tracer ||= TracePoint.new(){|tp|
559
+ @tracer ||= TracePoint.new(:call, :return, :b_call, :b_return, :line, :class, :end){|tp|
446
560
  next if File.dirname(tp.path) == dir
447
561
  next if tp.path == '<internal:trace_point>'
562
+ # Skip when `JSON.generate` is called during tests
563
+ next if tp.binding.eval('self').to_s == 'JSON' and ENV['RUBY_DEBUG_TEST_MODE']
448
564
  # next if tp.event != :line
449
565
  @ui.puts pretty_tp(tp)
450
566
  }
@@ -459,19 +575,19 @@ module DEBUGGER__
459
575
  ### Frame control
460
576
 
461
577
  # * `f[rame]`
462
- # * Show current frame.
578
+ # * Show the current frame.
463
579
  # * `f[rame] <framenum>`
464
- # * Specify frame. Evaluation are run on this frame environment.
580
+ # * Specify a current frame. Evaluation are run on specified frame.
465
581
  when 'frame', 'f'
466
582
  @tc << [:frame, :set, arg]
467
583
 
468
584
  # * `up`
469
- # * Specify upper frame.
585
+ # * Specify the upper frame.
470
586
  when 'up'
471
587
  @tc << [:frame, :up]
472
588
 
473
589
  # * `down`
474
- # * Specify down frame.
590
+ # * Specify the lower frame.
475
591
  when 'down'
476
592
  @tc << [:frame, :down]
477
593
 
@@ -501,6 +617,9 @@ module DEBUGGER__
501
617
  end
502
618
  @tc << [:eval, :call, 'binding.irb']
503
619
 
620
+ # don't repeat irb command
621
+ @repl_prev_line = nil
622
+
504
623
  ### Thread control
505
624
 
506
625
  # * `th[read]`
@@ -518,6 +637,21 @@ module DEBUGGER__
518
637
  end
519
638
  return :retry
520
639
 
640
+ ### Configuration
641
+ # * `config`
642
+ # * Show all configuration with description.
643
+ # * `config <name>`
644
+ # * Show current configuration of <name>.
645
+ # * `config set <name> <val>` or `config <name> = <val>`
646
+ # * Set <name> to <val>.
647
+ # * `config append <name> <val>` or `config <name> << <val>`
648
+ # * Append `<val>` to `<name>` if it is an array.
649
+ # * `config unset <name>`
650
+ # * Set <name> to default.
651
+ when 'config'
652
+ config_command arg
653
+ return :retry
654
+
521
655
  ### Help
522
656
 
523
657
  # * `h[elp]`
@@ -549,6 +683,79 @@ module DEBUGGER__
549
683
  return :retry
550
684
  end
551
685
 
686
+ def config_show key
687
+ key = key.to_sym
688
+ if CONFIG_SET[key]
689
+ v = CONFIG[key]
690
+ kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
691
+ desc = CONFIG_SET[key][1]
692
+ line = "%-30s \# %s" % [kv, desc]
693
+ if line.size > SESSION.width
694
+ @ui.puts "\# #{desc}\n#{kv}"
695
+ else
696
+ @ui.puts line
697
+ end
698
+ else
699
+ @ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
700
+ end
701
+ end
702
+
703
+ def config_set key, val, append: false
704
+ if CONFIG_SET[key = key.to_sym]
705
+ begin
706
+ if append
707
+ DEBUGGER__.append_config(key, val)
708
+ else
709
+ DEBUGGER__.set_config({key => val})
710
+ end
711
+ rescue => e
712
+ @ui.puts e.message
713
+ end
714
+ end
715
+
716
+ config_show key
717
+ end
718
+
719
+ def config_command arg
720
+ case arg
721
+ when nil
722
+ CONFIG_SET.each do |k, _|
723
+ config_show k
724
+ end
725
+
726
+ when /\Aunset\s+(.+)\z/
727
+ if CONFIG_SET[key = $1.to_sym]
728
+ DEBUGGER__.set_config({key => nil})
729
+ end
730
+ config_show key
731
+
732
+ when /\A(\w+)\s*=\s*(.+)\z/
733
+ config_set $1, $2
734
+
735
+ when /\A\s*set\s+(\w+)\s+(.+)\z/
736
+ config_set $1, $2
737
+
738
+ when /\A(\w+)\s*<<\s*(.+)\z/
739
+ config_set $1, $2, append: true
740
+
741
+ when /\A\s*append\s+(\w+)\s+(.+)\z/
742
+ config_set $1, $2
743
+
744
+ when /\A(\w+)\z/
745
+ config_show $1
746
+
747
+ else
748
+ @ui.puts "Can not parse parameters: #{arg}"
749
+ end
750
+ end
751
+
752
+
753
+ def cancel_auto_continue
754
+ if @preset_command&.auto_continue
755
+ @preset_command.auto_continue = false
756
+ end
757
+ end
758
+
552
759
  def show_help arg
553
760
  DEBUGGER__.helps.each{|cat, cs|
554
761
  cs.each{|ws, desc|
@@ -665,29 +872,37 @@ module DEBUGGER__
665
872
  end
666
873
  end
667
874
 
668
- def repl_add_breakpoint arg
669
- arg.strip!
875
+ BREAK_KEYWORDS = %w(if: do: pre:).freeze
670
876
 
671
- case arg
672
- when /\Aif\s+(.+)\z/
673
- cond = $1
674
- when /(.+?)\s+if\s+(.+)\z/
675
- sig = $1
676
- cond = $2
677
- else
678
- sig = arg
679
- end
877
+ def parse_break arg
878
+ mode = :sig
879
+ expr = Hash.new{|h, k| h[k] = []}
880
+ arg.split(' ').each{|w|
881
+ if BREAK_KEYWORDS.any?{|pat| w == pat}
882
+ mode = w[0..-2].to_sym
883
+ else
884
+ expr[mode] << w
885
+ end
886
+ }
887
+ expr.default_proc = nil
888
+ expr.transform_values{|v| v.join(' ')}
889
+ end
890
+
891
+ def repl_add_breakpoint arg
892
+ expr = parse_break arg.strip
893
+ cond = expr[:if]
894
+ cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
680
895
 
681
- case sig
896
+ case expr[:sig]
682
897
  when /\A(\d+)\z/
683
- add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
898
+ add_line_breakpoint @tc.location.path, $1.to_i, cond: expr[:if], command: cmd
684
899
  when /\A(.+)[:\s+](\d+)\z/
685
- add_line_breakpoint $1, $2.to_i, cond: cond
900
+ add_line_breakpoint $1, $2.to_i, cond: expr[:if], command: cmd
686
901
  when /\A(.+)([\.\#])(.+)\z/
687
- @tc << [:breakpoint, :method, $1, $2, $3, cond]
902
+ @tc << [:breakpoint, :method, $1, $2, $3, expr[:if], cmd]
688
903
  return :noretry
689
904
  when nil
690
- add_check_breakpoint cond
905
+ add_check_breakpoint expr[:if]
691
906
  else
692
907
  @ui.puts "Unknown breakpoint format: #{arg}"
693
908
  @ui.puts
@@ -706,6 +921,8 @@ module DEBUGGER__
706
921
  case
707
922
  when th == Thread.current
708
923
  # ignore
924
+ when @management_threads.include?(th)
925
+ # ignore
709
926
  when @th_clients.has_key?(th)
710
927
  thcs << @th_clients[th]
711
928
  else
@@ -729,8 +946,13 @@ module DEBUGGER__
729
946
  end
730
947
  end
731
948
 
949
+ def managed_thread_clients
950
+ thcs, _unmanaged_ths = update_thread_list
951
+ thcs
952
+ end
953
+
732
954
  def thread_switch n
733
- thcs, unmanaged_ths = update_thread_list
955
+ thcs, _unmanaged_ths = update_thread_list
734
956
 
735
957
  if tc = thcs[n]
736
958
  if tc.mode
@@ -801,9 +1023,10 @@ module DEBUGGER__
801
1023
  end
802
1024
  end
803
1025
 
804
- ## event
1026
+ ## event
805
1027
 
806
1028
  def on_load iseq, src
1029
+ DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
807
1030
  @sr.add iseq, src
808
1031
 
809
1032
  pending_line_breakpoints do |bp|
@@ -816,8 +1039,14 @@ module DEBUGGER__
816
1039
  # breakpoint management
817
1040
 
818
1041
  def add_breakpoint bp
1042
+ # don't repeat commands that add breakpoints
1043
+ @repl_prev_line = nil
1044
+
819
1045
  if @bps.has_key? bp.key
820
- @ui.puts "duplicated breakpoint: #{bp}"
1046
+ unless bp.duplicable?
1047
+ @ui.puts "duplicated breakpoint: #{bp}"
1048
+ bp.disable
1049
+ end
821
1050
  else
822
1051
  @bps[bp.key] = bp
823
1052
  end
@@ -837,7 +1066,7 @@ module DEBUGGER__
837
1066
 
838
1067
  def add_catch_breakpoint arg
839
1068
  bp = CatchBreakpoint.new(arg)
840
- add_braekpoint bp
1069
+ add_breakpoint bp
841
1070
  end
842
1071
 
843
1072
  def add_check_breakpoint expr
@@ -848,13 +1077,25 @@ module DEBUGGER__
848
1077
  def resolve_path file
849
1078
  File.realpath(File.expand_path(file))
850
1079
  rescue Errno::ENOENT
851
- return file if file == '-e'
1080
+ case file
1081
+ when '-e', '-'
1082
+ return file
1083
+ else
1084
+ $LOAD_PATH.each do |lp|
1085
+ libpath = File.join(lp, file)
1086
+ return File.realpath(libpath)
1087
+ rescue Errno::ENOENT
1088
+ # next
1089
+ end
1090
+ end
1091
+
852
1092
  raise
853
1093
  end
854
1094
 
855
1095
  def add_line_breakpoint file, line, **kw
856
1096
  file = resolve_path(file)
857
1097
  bp = LineBreakpoint.new(file, line, **kw)
1098
+
858
1099
  add_breakpoint bp
859
1100
  rescue Errno::ENOENT => e
860
1101
  @ui.puts e.message
@@ -872,24 +1113,58 @@ module DEBUGGER__
872
1113
  b = tp.binding
873
1114
  if var_name = b.local_variables.first
874
1115
  mid = b.local_variable_get(var_name)
875
- found = false
1116
+ unresolved = false
876
1117
 
877
1118
  @bps.each{|k, bp|
878
1119
  case bp
879
1120
  when MethodBreakpoint
880
1121
  if bp.method.nil?
881
- found = true
882
1122
  if bp.sig_method_name == mid.to_s
883
- bp.enable(quiet: true)
1123
+ bp.try_enable(added: true)
884
1124
  end
885
1125
  end
1126
+
1127
+ unresolved = true unless bp.enabled?
886
1128
  end
887
1129
  }
888
- unless found
1130
+ unless unresolved
889
1131
  METHOD_ADDED_TRACKER.disable
890
1132
  end
891
1133
  end
892
1134
  end
1135
+
1136
+ def width
1137
+ @ui.width
1138
+ end
1139
+
1140
+ def check_forked
1141
+ unless @session_server.status
1142
+ # TODO: Support it
1143
+ raise 'DEBUGGER: stop at forked process is not supported yet.'
1144
+ end
1145
+ end
1146
+ end
1147
+
1148
+ class UI_Base
1149
+ def event type, *args
1150
+ case type
1151
+ when :suspend_bp
1152
+ i, bp = *args
1153
+ puts "\nStop by \##{i} #{bp}" if bp
1154
+ when :suspend_trap
1155
+ puts "\nStop by #{args.first}"
1156
+ end
1157
+ end
1158
+ end
1159
+
1160
+ # manual configuration methods
1161
+
1162
+ def self.add_line_breakpoint file, line, **kw
1163
+ ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
1164
+ end
1165
+
1166
+ def self.add_catch_breakpoint pat
1167
+ ::DEBUGGER__::SESSION.add_catch_breakpoint pat
893
1168
  end
894
1169
 
895
1170
  # String for requring location
@@ -903,30 +1178,79 @@ module DEBUGGER__
903
1178
  when dir_prefix
904
1179
  when %r{rubygems/core_ext/kernel_require\.rb}
905
1180
  else
906
- return loc
1181
+ return loc if loc.absolute_path
907
1182
  end
908
1183
  end
909
1184
  nil
910
1185
  end
911
1186
 
912
- def self.console
913
- initialize_session UI_Console.new
1187
+ # start methods
914
1188
 
915
- @prev_handler = trap(:SIGINT){
916
- ThreadClient.current.on_trap :SIGINT
917
- }
1189
+ def self.start nonstop: false, **kw
1190
+ set_config(kw)
1191
+
1192
+ unless defined? SESSION
1193
+ require_relative 'console'
1194
+ initialize_session UI_Console.new
1195
+ end
1196
+
1197
+ setup_initial_suspend unless nonstop
918
1198
  end
919
1199
 
920
- def self.add_line_breakpoint file, line, **kw
921
- ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
1200
+ def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
1201
+ set_config(kw)
1202
+
1203
+ if port
1204
+ open_tcp host: host, port: port, nonstop: nonstop
1205
+ else
1206
+ open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
1207
+ end
922
1208
  end
923
1209
 
924
- def self.add_catch_breakpoint pat
925
- ::DEBUGGER__::SESSION.add_catch_breakpoint pat
1210
+ def self.open_tcp host: nil, port:, nonstop: false, **kw
1211
+ set_config(kw)
1212
+ require_relative 'server'
1213
+
1214
+ if defined? SESSION
1215
+ SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
1216
+ else
1217
+ initialize_session UI_TcpServer.new(host: host, port: port)
1218
+ end
1219
+
1220
+ setup_initial_suspend unless nonstop
1221
+ end
1222
+
1223
+ def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
1224
+ set_config(kw)
1225
+ require_relative 'server'
1226
+
1227
+ if defined? SESSION
1228
+ SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1229
+ else
1230
+ initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1231
+ end
1232
+
1233
+ setup_initial_suspend unless nonstop
1234
+ end
1235
+
1236
+ # boot utilities
1237
+
1238
+ def self.setup_initial_suspend
1239
+ if !::DEBUGGER__::CONFIG[:nonstop]
1240
+ if loc = ::DEBUGGER__.require_location
1241
+ # require 'debug/console' or 'debug'
1242
+ add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
1243
+ else
1244
+ # -r
1245
+ add_line_breakpoint $0, 1, oneshot: true, hook_call: false
1246
+ end
1247
+ end
926
1248
  end
927
1249
 
928
1250
  class << self
929
1251
  define_method :initialize_session do |ui|
1252
+ DEBUGGER__.warn "Session start (pid: #{Process.pid})"
1253
+
930
1254
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
931
1255
 
932
1256
  # default breakpoints
@@ -934,18 +1258,18 @@ module DEBUGGER__
934
1258
  # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
935
1259
 
936
1260
  Binding.module_eval do
937
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
938
- def bp; nil; end
939
- end
1261
+ def break pre: nil, do: nil
1262
+ return unless SESSION.active?
940
1263
 
941
- if ::DEBUGGER__::CONFIG[:nonstop] != '1'
942
- if loc = ::DEBUGGER__.require_location
943
- # require 'debug/console' or 'debug'
944
- add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
945
- else
946
- # -r
947
- add_line_breakpoint $0, 1, oneshot: true, hook_call: false
1264
+ if pre || (do_expr = binding.local_variable_get(:do))
1265
+ cmds = ['binding.break', pre, do_expr]
1266
+ end
1267
+
1268
+ ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
1269
+ true
948
1270
  end
1271
+ alias b break
1272
+ # alias bp break
949
1273
  end
950
1274
 
951
1275
  load_rc
@@ -953,27 +1277,29 @@ module DEBUGGER__
953
1277
  end
954
1278
 
955
1279
  def self.load_rc
956
- ['./rdbgrc.rb', File.expand_path('~/.rdbgrc.rb')].each{|path|
957
- if File.file? path
958
- load path
959
- end
960
- }
961
-
962
- # debug commands file
963
- [::DEBUGGER__::CONFIG[:init_script],
964
- './.rdbgrc',
965
- File.expand_path('~/.rdbgrc')].each{|path|
1280
+ [[File.expand_path('~/.rdbgrc'), true],
1281
+ [File.expand_path('~/.rdbgrc.rb'), true],
1282
+ # ['./.rdbgrc', true], # disable because of security concern
1283
+ [::DEBUGGER__::CONFIG[:init_script], false],
1284
+ ].each{|(path, rc)|
966
1285
  next unless path
1286
+ next if rc && ::DEBUGGER__::CONFIG[:no_rc] # ignore rc
967
1287
 
968
1288
  if File.file? path
969
- ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
1289
+ if path.end_with?('.rb')
1290
+ load path
1291
+ else
1292
+ ::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
1293
+ end
1294
+ elsif !rc
1295
+ warn "Not found: #{path}"
970
1296
  end
971
1297
  }
972
1298
 
973
1299
  # given debug commands
974
1300
  if ::DEBUGGER__::CONFIG[:commands]
975
1301
  cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
976
- ::DEBUGGER__::SESSION.add_initial_commands cmds
1302
+ ::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
977
1303
  end
978
1304
  end
979
1305
 
@@ -1027,9 +1353,8 @@ module DEBUGGER__
1027
1353
  r.join("\n")
1028
1354
  end
1029
1355
 
1030
- CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
1031
-
1032
1356
  class ::Module
1357
+ undef method_added
1033
1358
  def method_added mid; end
1034
1359
  def singleton_method_added mid; end
1035
1360
  end
@@ -1043,4 +1368,44 @@ module DEBUGGER__
1043
1368
  end
1044
1369
 
1045
1370
  METHOD_ADDED_TRACKER = self.create_method_added_tracker
1371
+
1372
+ SHORT_INSPECT_LENGTH = 40
1373
+ def self.short_inspect obj, use_short = true
1374
+ str = obj.inspect
1375
+ if use_short && str.length > SHORT_INSPECT_LENGTH
1376
+ str[0...SHORT_INSPECT_LENGTH] + '...'
1377
+ else
1378
+ str
1379
+ end
1380
+ end
1381
+
1382
+ LOG_LEVELS = {
1383
+ UNKNOWN: 0,
1384
+ FATAL: 1,
1385
+ ERROR: 2,
1386
+ WARN: 3,
1387
+ INFO: 4,
1388
+ }.freeze
1389
+
1390
+ def self.warn msg
1391
+ log :WARN, msg
1392
+ end
1393
+
1394
+ def self.info msg
1395
+ log :INFO, msg
1396
+ end
1397
+
1398
+ def self.log level, msg
1399
+ lv = LOG_LEVELS[level]
1400
+ config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
1401
+
1402
+ if lv <= config_lv
1403
+ if level == :WARN
1404
+ # :WARN on debugger is general information
1405
+ STDERR.puts "DEBUGGER: #{msg}"
1406
+ else
1407
+ STDERR.puts "DEBUGGER (#{level}): #{msg}"
1408
+ end
1409
+ end
1410
+ end
1046
1411
  end