debug 1.0.0.alpha1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/session.rb CHANGED
@@ -1,11 +1,15 @@
1
- require 'json'
2
- require 'pp'
3
- require 'debug_inspector'
4
- require 'iseq_collector'
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
6
+
7
+ require_relative 'debug.so'
5
8
 
6
9
  require_relative 'source_repository'
7
10
  require_relative 'breakpoint'
8
11
  require_relative 'thread_client'
12
+ require_relative 'config'
9
13
 
10
14
  class RubyVM::InstructionSequence
11
15
  def traceable_lines_norec lines
@@ -39,6 +43,10 @@ class RubyVM::InstructionSequence
39
43
  def locals
40
44
  self.to_a[10]
41
45
  end
46
+
47
+ def last_line
48
+ self.to_a[4][:code_location][2]
49
+ end
42
50
  end
43
51
 
44
52
  module DEBUGGER__
@@ -46,12 +54,18 @@ module DEBUGGER__
46
54
  def initialize ui
47
55
  @ui = ui
48
56
  @sr = SourceRepository.new
49
- @reserved_bps = []
50
- @bps = {} # [file, line] => LineBreakpoint || "Error" => CatchBreakpoint
57
+ @bps = {} # bp.key => bp
58
+ # [file, line] => LineBreakpoint
59
+ # "Error" => CatchBreakpoint
60
+ # Method => MethodBreakpoint
61
+ # [:watch, expr] => WatchExprBreakpoint
62
+ # [:check, expr] => CheckBreakpoint
51
63
  @th_clients = {} # {Thread => ThreadClient}
52
64
  @q_evt = Queue.new
53
65
  @displays = []
54
66
  @tc = nil
67
+ @tc_id = 0
68
+ @initial_commands = []
55
69
 
56
70
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
57
71
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
@@ -69,13 +83,53 @@ module DEBUGGER__
69
83
  iseq, src = ev_args
70
84
  on_load iseq, src
71
85
  tc << :continue
86
+ when :thread_begin
87
+ th = ev_args.shift
88
+ on_thread_begin th
89
+ tc << :continue
72
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
101
+
73
102
  if @displays.empty?
74
103
  wait_command_loop tc
75
104
  else
76
105
  tc << [:eval, :display, @displays]
77
106
  end
78
107
  when :result
108
+ case ev_args.first
109
+ when :watch
110
+ bp = ev_args[1]
111
+ @bps[bp.key] = bp
112
+ 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
+ else
130
+ # ignore
131
+ end
132
+
79
133
  wait_command_loop tc
80
134
  end
81
135
  end
@@ -85,6 +139,17 @@ module DEBUGGER__
85
139
  @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
86
140
 
87
141
  setup_threads
142
+
143
+ @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
144
+ ThreadClient.current.on_thread_begin Thread.current
145
+ }.enable
146
+ end
147
+
148
+ def add_initial_commands cmds
149
+ cmds.each{|c|
150
+ c.gsub('#.*', '').strip!
151
+ @initial_commands << c unless c.empty?
152
+ }
88
153
  end
89
154
 
90
155
  def source path
@@ -109,10 +174,17 @@ module DEBUGGER__
109
174
  retry
110
175
  end
111
176
  end
177
+ ensure
178
+ @tc = nil
112
179
  end
113
180
 
114
181
  def wait_command
115
- line = @ui.readline
182
+ if @initial_commands.empty?
183
+ line = @ui.readline
184
+ else
185
+ line = @initial_commands.shift.strip
186
+ @ui.puts "(rdbg:init) #{line}"
187
+ end
116
188
 
117
189
  if line.empty?
118
190
  if @repl_prev_line
@@ -130,40 +202,83 @@ module DEBUGGER__
130
202
  # p cmd: [cmd, *arg]
131
203
 
132
204
  case cmd
205
+ ### Control flow
133
206
 
134
- # control
207
+ # * `s[tep]`
208
+ # * Step in. Resume the program until next breakable point.
135
209
  when 's', 'step'
136
210
  @tc << [:step, :in]
211
+
212
+ # * `n[ext]`
213
+ # * Step over. Resume the program until next line.
137
214
  when 'n', 'next'
138
215
  @tc << [:step, :next]
216
+
217
+ # * `fin[ish]`
218
+ # * Finish this frame. Resume the program until the current frame is finished.
139
219
  when 'fin', 'finish'
140
220
  @tc << [:step, :finish]
221
+
222
+ # * `c[ontinue]`
223
+ # * Resume the program.
141
224
  when 'c', 'continue'
142
225
  @tc << :continue
143
- when 'q', 'quit'
226
+
227
+ # * `q[uit]` or exit or `Ctrl-D`
228
+ # * Finish debugger (with the debuggee process on non-remote debugging).
229
+ when 'q', 'quit', 'exit'
144
230
  if ask 'Really quit?'
145
231
  @ui.quit arg.to_i
146
232
  @tc << :continue
147
233
  else
148
234
  return :retry
149
235
  end
150
- when 'kill'
151
- if ask 'Really quit?'
236
+
237
+ # * `kill` or `q[uit]!`
238
+ # * Stop the debuggee process.
239
+ when 'kill', 'quit!', 'q!'
240
+ if ask 'Really kill?'
152
241
  exit! (arg || 1).to_i
153
242
  else
154
243
  return :retry
155
244
  end
156
245
 
157
- # breakpoints
246
+ ### Breakpoint
247
+
248
+ # * `b[reak]`
249
+ # * Show all breakpoints.
250
+ # * `b[reak] <line>`
251
+ # * Set breakpoint on `<line>` at the current frame's file.
252
+ # * `b[reak] <file>:<line>` or `<file> <line>`
253
+ # * Set breakpoint on `<file>:<line>`.
254
+ # * `b[reak] <class>#<name>`
255
+ # * Set breakpoint on the method `<class>#<name>`.
256
+ # * `b[reak] <expr>.<name>`
257
+ # * Set breakpoint on the method `<expr>.<name>`.
258
+ # * `b[reak] ... if <expr>`
259
+ # * break if `<expr>` is true at specified location.
260
+ # * `b[reak] if <expr>`
261
+ # * break if `<expr>` is true at any lines.
262
+ # * Note that this feature is super slow.
158
263
  when 'b', 'break'
159
264
  if arg == nil
160
265
  show_bps
266
+ return :retry
161
267
  else
162
- bp = repl_add_breakpoint arg
163
- show_bps bp if bp
268
+ case bp = repl_add_breakpoint(arg)
269
+ when :noretry
270
+ when nil
271
+ return :retry
272
+ else
273
+ show_bps bp
274
+ return :retry
275
+ end
164
276
  end
165
- return :retry
277
+
278
+ # skip
166
279
  when 'bv'
280
+ require 'json'
281
+
167
282
  h = Hash.new{|h, k| h[k] = []}
168
283
  @bps.each{|key, bp|
169
284
  if LineBreakpoint === bp
@@ -184,12 +299,33 @@ module DEBUGGER__
184
299
  end
185
300
 
186
301
  return :retry
302
+
303
+ # * `catch <Error>`
304
+ # * Set breakpoint on raising `<Error>`.
187
305
  when 'catch'
188
306
  if arg
189
307
  bp = add_catch_breakpoint arg
190
308
  show_bps bp if bp
309
+ else
310
+ show_bps
191
311
  end
192
312
  return :retry
313
+
314
+ # * `watch <expr>`
315
+ # * Stop the execution when the result of <expr> is changed.
316
+ # * Note that this feature is super slow.
317
+ when 'wat', 'watch'
318
+ if arg
319
+ @tc << [:eval, :watch, arg]
320
+ else
321
+ show_bps
322
+ return :retry
323
+ end
324
+
325
+ # * `del[ete]`
326
+ # * delete all breakpoints.
327
+ # * `del[ete] <bpnum>`
328
+ # * delete specified breakpoint.
193
329
  when 'del', 'delete'
194
330
  bp =
195
331
  case arg
@@ -206,42 +342,84 @@ module DEBUGGER__
206
342
  @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
207
343
  return :retry
208
344
 
209
- # evaluate
210
- when 'p'
211
- @tc << [:eval, :p, arg.to_s]
212
- when 'pp'
213
- @tc << [:eval, :pp, arg.to_s]
214
- when 'e', 'eval', 'call'
215
- @tc << [:eval, :call, arg]
216
- when 'irb'
217
- @tc << [:eval, :call, 'binding.irb']
345
+ ### Information
218
346
 
219
- # evaluate/frame selector
220
- when 'up'
221
- @tc << [:frame, :up]
222
- when 'down'
223
- @tc << [:frame, :down]
224
- when 'frame', 'f'
225
- @tc << [:frame, :set, arg]
226
-
227
- # information
347
+ # * `bt` or `backtrace`
348
+ # * Show backtrace (frame) information.
228
349
  when 'bt', 'backtrace'
229
350
  @tc << [:show, :backtrace]
230
- when 'list'
231
- @tc << [:show, :list]
232
- when 'info'
233
- case arg
234
- when 'l', 'local', 'locals'
235
- @tc << [:show, :locals]
236
- when 'i', 'instance', 'ivars'
237
- @tc << [:show, :ivars]
351
+
352
+ # * `l[ist]`
353
+ # * Show current frame's source code.
354
+ # * Next `list` command shows the successor lines.
355
+ # * `l[ist] -`
356
+ # * Show predecessor lines as opposed to the `list` command.
357
+ # * `l[ist] <start>` or `l[ist] <start>-<end>`
358
+ # * Show current frame's source code from the line <start> to <end> if given.
359
+ when 'l', 'list'
360
+ case arg ? arg.strip : nil
361
+ when /\A(\d+)\z/
362
+ @tc << [:show, :list, {start_line: arg.to_i - 1}]
363
+ when /\A-\z/
364
+ @tc << [:show, :list, {dir: -1}]
365
+ when /\A(\d+)-(\d+)\z/
366
+ @tc << [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
367
+ when nil
368
+ @tc << [:show, :list]
238
369
  else
239
- @ui.puts "unknown info argument: #{arg}"
370
+ @ui.puts "Can not handle list argument: #{arg}"
240
371
  return :retry
241
372
  end
373
+
374
+ # * `edit`
375
+ # * Open the current file on the editor (use `EDITOR` environment variable).
376
+ # * Note that editted file will not be reloaded.
377
+ # * `edit <file>`
378
+ # * Open <file> on the editor.
379
+ when 'edit'
380
+ if @ui.remote?
381
+ @ui.puts "not supported on the remote console."
382
+ return :retry
383
+ end
384
+
385
+ begin
386
+ arg = resolve_path(arg) if arg
387
+ rescue Errno::ENOENT
388
+ @ui.puts "not found: #{arg}"
389
+ return :retry
390
+ end
391
+
392
+ @tc << [:show, :edit, arg]
393
+
394
+ # * `i[nfo]`
395
+ # * Show information about the current frame (local variables)
396
+ # * It includes `self` as `%self` and a return value as `%return`.
397
+ # * `i[nfo] <expr>`
398
+ # * Show information about the result of <expr>.
399
+ when 'i', 'info'
400
+ case arg
401
+ when nil
402
+ @tc << [:show, :local]
403
+ else
404
+ @tc << [:show, :object_info, arg]
405
+ end
406
+
407
+ # * `display`
408
+ # * Show display setting.
409
+ # * `display <expr>`
410
+ # * Show the result of `<expr>` at every suspended timing.
242
411
  when 'display'
243
- @displays << arg if arg && !arg.empty?
244
- @tc << [:eval, :display, @displays]
412
+ if arg && !arg.empty?
413
+ @displays << arg
414
+ @tc << [:eval, :try_display, @displays]
415
+ else
416
+ @tc << [:eval, :display, @displays]
417
+ end
418
+
419
+ # * `undisplay`
420
+ # * Remove all display settings.
421
+ # * `undisplay <displaynum>`
422
+ # * Remove a specified display setting.
245
423
  when 'undisplay'
246
424
  case arg
247
425
  when /(\d+)/
@@ -258,12 +436,14 @@ module DEBUGGER__
258
436
  end
259
437
  return :retry
260
438
 
261
- # trace
439
+ # * `trace [on|off]`
440
+ # * enable or disable line tracer.
262
441
  when 'trace'
263
442
  case arg
264
443
  when 'on'
444
+ dir = __dir__
265
445
  @tracer ||= TracePoint.new(){|tp|
266
- next if tp.path == __FILE__
446
+ next if File.dirname(tp.path) == dir
267
447
  next if tp.path == '<internal:trace_point>'
268
448
  # next if tp.event != :line
269
449
  @ui.puts pretty_tp(tp)
@@ -271,13 +451,62 @@ module DEBUGGER__
271
451
  @tracer.enable
272
452
  when 'off'
273
453
  @tracer && @tracer.disable
274
- else
275
- enabled = (@tracer && @tracer.enabled?) ? true : false
276
- @ui.puts "Trace #{enabled ? 'on' : 'off'}"
277
454
  end
455
+ enabled = (@tracer && @tracer.enabled?) ? true : false
456
+ @ui.puts "Trace #{enabled ? 'on' : 'off'}"
278
457
  return :retry
279
458
 
280
- # threads
459
+ ### Frame control
460
+
461
+ # * `f[rame]`
462
+ # * Show current frame.
463
+ # * `f[rame] <framenum>`
464
+ # * Specify frame. Evaluation are run on this frame environement.
465
+ when 'frame', 'f'
466
+ @tc << [:frame, :set, arg]
467
+
468
+ # * `up`
469
+ # * Specify upper frame.
470
+ when 'up'
471
+ @tc << [:frame, :up]
472
+
473
+ # * `down`
474
+ # * Specify down frame.
475
+ when 'down'
476
+ @tc << [:frame, :down]
477
+
478
+ ### Evaluate
479
+
480
+ # * `p <expr>`
481
+ # * Evaluate like `p <expr>` on the current frame.
482
+ when 'p'
483
+ @tc << [:eval, :p, arg.to_s]
484
+
485
+ # * `pp <expr>`
486
+ # * Evaluate like `pp <expr>` on the current frame.
487
+ when 'pp'
488
+ @tc << [:eval, :pp, arg.to_s]
489
+
490
+ # * `e[val] <expr>`
491
+ # * Evaluate `<expr>` on the current frame.
492
+ when 'e', 'eval', 'call'
493
+ @tc << [:eval, :call, arg]
494
+
495
+ # * `irb`
496
+ # * Invoke `irb` on the current frame.
497
+ when 'irb'
498
+ if @ui.remote?
499
+ @ui.puts "not supported on the remote console."
500
+ return :retry
501
+ end
502
+ @tc << [:eval, :call, 'binding.irb']
503
+
504
+ ### Thread control
505
+
506
+ # * `th[read]`
507
+ # * Show all threads.
508
+ # * `th[read] <thnum>`
509
+ # * Switch thread specified by `<thnum>`.
281
510
  when 'th', 'thread'
282
511
  case arg
283
512
  when nil, 'list', 'l'
@@ -289,6 +518,21 @@ module DEBUGGER__
289
518
  end
290
519
  return :retry
291
520
 
521
+ ### Help
522
+
523
+ # * `h[elp]`
524
+ # * Show help for all commands.
525
+ # * `h[elp] <command>`
526
+ # * Show help for the given command.
527
+ when 'h', 'help'
528
+ if arg
529
+ show_help arg
530
+ else
531
+ @ui.puts DEBUGGER__.help
532
+ end
533
+ return :retry
534
+
535
+ ### END
292
536
  else
293
537
  @ui.puts "unknown command: #{line}"
294
538
  @repl_prev_line = nil
@@ -305,6 +549,18 @@ module DEBUGGER__
305
549
  return :retry
306
550
  end
307
551
 
552
+ def show_help arg
553
+ DEBUGGER__.helps.each{|cat, cs|
554
+ cs.each{|ws, desc|
555
+ if ws.include? arg
556
+ @ui.puts desc
557
+ return
558
+ end
559
+ }
560
+ }
561
+ @ui.puts "not found: #{arg}"
562
+ end
563
+
308
564
  def ask msg, default = 'Y'
309
565
  opts = '[y/n]'.tr(default.downcase, default)
310
566
  input = @ui.ask("#{msg} #{opts} ")
@@ -363,35 +619,84 @@ module DEBUGGER__
363
619
  exit!
364
620
  end
365
621
 
366
- def show_bps specified_bp = nil
367
- @bps.each_with_index{|(key, bp), i|
368
- if !specified_bp || bp == specified_bp
369
- @ui.puts "#%d %s" % [i, bp.to_s]
622
+ def iterate_bps
623
+ deleted_bps = []
624
+ i = 0
625
+ @bps.each{|key, bp|
626
+ if !bp.deleted?
627
+ yield key, bp, i
628
+ i += 1
629
+ else
630
+ deleted_bps << bp
370
631
  end
371
632
  }
633
+ ensure
634
+ deleted_bps.each{|bp| @bps.delete bp}
372
635
  end
373
636
 
374
- def thread_list
375
- thcs, unmanaged_ths = update_thread_list
376
- thcs.each_with_index{|thc, i|
377
- @ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
378
- }
637
+ def show_bps specific_bp = nil
638
+ iterate_bps do |key, bp, i|
639
+ @ui.puts "#%d %s" % [i, bp.to_s] if !specific_bp || bp == specific_bp
640
+ end
641
+ end
379
642
 
380
- if !unmanaged_ths.empty?
381
- @ui.puts "The following threads are not managed yet by the debugger:"
382
- unmanaged_ths.each{|th|
383
- @ui.puts " " + th.to_s
384
- }
643
+ def bp_index specific_bp_key
644
+ iterate_bps do |key, bp, i|
645
+ if key == specific_bp_key
646
+ return [bp, i]
647
+ end
385
648
  end
649
+ nil
386
650
  end
387
651
 
388
- def thread_switch n
389
- if th = @th_clients.keys[n]
390
- @tc = @th_clients[th]
652
+ def delete_breakpoint arg = nil
653
+ case arg
654
+ when nil
655
+ @bps.each{|key, bp| bp.delete}
656
+ @bps.clear
657
+ else
658
+ del_bp = nil
659
+ iterate_bps{|key, bp, i| del_bp = bp if i == arg}
660
+ if del_bp
661
+ del_bp.delete
662
+ @bps.delete del_bp.key
663
+ return [arg, del_bp]
664
+ end
665
+ end
666
+ end
667
+
668
+ def repl_add_breakpoint arg
669
+ arg.strip!
670
+
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
680
+
681
+ case sig
682
+ when /\A(\d+)\z/
683
+ add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
684
+ when /\A(.+)[:\s+](\d+)\z/
685
+ add_line_breakpoint $1, $2.to_i, cond: cond
686
+ when /\A(.+)([\.\#])(.+)\z/
687
+ @tc << [:breakpoint, :method, $1, $2, $3, cond]
688
+ return :noretry
689
+ when nil
690
+ add_check_breakpoint cond
691
+ else
692
+ @ui.puts "Unknown breakpoint format: #{arg}"
693
+ @ui.puts
694
+ show_help 'b'
391
695
  end
392
- thread_list
393
696
  end
394
697
 
698
+ # threads
699
+
395
700
  def update_thread_list
396
701
  list = Thread.list
397
702
  thcs = []
@@ -407,60 +712,64 @@ module DEBUGGER__
407
712
  unmanaged << th
408
713
  end
409
714
  }
410
- return thcs, unmanaged
715
+ return thcs.sort_by{|thc| thc.id}, unmanaged
411
716
  end
412
717
 
413
- def delete_breakpoint arg = nil
414
- case arg
415
- when nil
416
- @bps.each{|key, bp| bp.disable}
417
- @bps.clear
418
- else
419
- if bp = @bps[key = @bps.keys[arg]]
420
- bp.disable
421
- @bps.delete key
422
- return [arg, bp]
423
- end
718
+ def thread_list
719
+ thcs, unmanaged_ths = update_thread_list
720
+ thcs.each_with_index{|thc, i|
721
+ @ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
722
+ }
723
+
724
+ if !unmanaged_ths.empty?
725
+ @ui.puts "The following threads are not managed yet by the debugger:"
726
+ unmanaged_ths.each{|th|
727
+ @ui.puts " " + th.to_s
728
+ }
424
729
  end
425
730
  end
426
731
 
427
- def repl_add_breakpoint arg
428
- arg.strip!
429
-
430
- if /(.+?)\s+if\s+(.+)\z/ =~ arg
431
- sig = $1
432
- cond = $2
433
- else
434
- sig = arg
435
- end
732
+ def thread_switch n
733
+ thcs, unmanaged_ths = update_thread_list
436
734
 
437
- case sig
438
- when /\A(\d+)\z/
439
- add_line_breakpoint @tc.location.path, $1.to_i, cond
440
- when /\A(.+):(\d+)\z/
441
- add_line_breakpoint $1, $2.to_i, cond
442
- when /\A(.+)[\.\#](.+)\z/
443
- add_method_breakpoint arg, cond
444
- else
445
- raise "unknown breakpoint format: #{arg}"
735
+ if tc = thcs[n]
736
+ if tc.mode
737
+ @tc = tc
738
+ else
739
+ @ui.puts "#{tc.thread} is not controllable yet."
740
+ end
446
741
  end
742
+ thread_list
447
743
  end
448
744
 
449
- def break? file, line
450
- @bps.has_key? [file, line]
745
+ def thread_client_create th
746
+ @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
451
747
  end
452
748
 
453
749
  def setup_threads
454
750
  stop_all_threads do
455
751
  Thread.list.each{|th|
456
- @th_clients[th] = ThreadClient.new(@q_evt, Queue.new, th)
752
+ thread_client_create(th)
457
753
  }
458
754
  end
459
755
  end
460
756
 
757
+ def on_thread_begin th
758
+ if @th_clients.has_key? th
759
+ # OK
760
+ else
761
+ # TODO: NG?
762
+ thread_client_create th
763
+ end
764
+ end
765
+
461
766
  def thread_client
462
767
  thr = Thread.current
463
- @th_clients[thr] ||= ThreadClient.new(@q_evt, Queue.new)
768
+ if @th_clients.has_key? thr
769
+ @th_clients[thr]
770
+ else
771
+ @th_clients[thr] = thread_client_create(thr)
772
+ end
464
773
  end
465
774
 
466
775
  def stop_all_threads
@@ -496,99 +805,120 @@ module DEBUGGER__
496
805
 
497
806
  def on_load iseq, src
498
807
  @sr.add iseq, src
499
- @reserved_bps.each{|(path, line, cond)|
500
- if path == iseq.absolute_path
501
- add_line_breakpoint(path, line, cond)
808
+
809
+ pending_line_breakpoints do |bp|
810
+ if bp.path == (iseq.absolute_path || iseq.path)
811
+ bp.try_activate
502
812
  end
813
+ end
814
+ end
815
+
816
+ # breakpoint management
817
+
818
+ def add_breakpoint bp
819
+ if @bps.has_key? bp.key
820
+ @ui.puts "duplicated breakpoint: #{bp}"
821
+ else
822
+ @bps[bp.key] = bp
823
+ end
824
+ end
825
+
826
+ def rehash_bps
827
+ bps = @bps.values
828
+ @bps.clear
829
+ bps.each{|bp|
830
+ add_breakpoint bp
503
831
  }
504
832
  end
505
833
 
506
- # configuration
834
+ def break? file, line
835
+ @bps.has_key? [file, line]
836
+ end
507
837
 
508
838
  def add_catch_breakpoint arg
509
839
  bp = CatchBreakpoint.new(arg)
510
- @bps[bp.key] = bp
511
- bp
840
+ add_braekpoint bp
512
841
  end
513
842
 
514
- def add_line_breakpoint_exact iseq, events, file, line, cond
515
- if @bps[[file, line]]
516
- return nil # duplicated
517
- end
518
-
519
- bp = case
520
- when events.include?(:RUBY_EVENT_CALL)
521
- # "def foo" line set bp on the beggining of method foo
522
- LineBreakpoint.new(:call, iseq, line, cond)
523
- when events.include?(:RUBY_EVENT_LINE)
524
- LineBreakpoint.new(:line, iseq, line, cond)
525
- when events.include?(:RUBY_EVENT_RETURN)
526
- LineBreakpoint.new(:return, iseq, line, cond)
527
- when events.include?(:RUBY_EVENT_B_RETURN)
528
- LineBreakpoint.new(:b_return, iseq, line, cond)
529
- when events.include?(:RUBY_EVENT_END)
530
- LineBreakpoint.new(:end, iseq, line, cond)
531
- else
532
- nil
533
- end
534
- @bps[bp.key] = bp if bp
843
+ def add_check_breakpoint expr
844
+ bp = CheckBreakpoint.new(expr)
845
+ add_breakpoint bp
535
846
  end
536
847
 
537
- NearestISeq = Struct.new(:iseq, :line, :events)
538
-
539
- def add_line_breakpoint_nearest file, line, cond
540
- nearest = nil # NearestISeq
848
+ def resolve_path file
849
+ File.realpath(File.expand_path(file))
850
+ rescue Errno::ENOENT
851
+ return file if file == '-e'
852
+ raise
853
+ end
541
854
 
542
- ObjectSpace.each_iseq{|iseq|
543
- if iseq.absolute_path == file && iseq.first_lineno <= line
544
- iseq.traceable_lines_norec(line_events = {})
545
- lines = line_events.keys.sort
855
+ def add_line_breakpoint file, line, **kw
856
+ file = resolve_path(file)
857
+ bp = LineBreakpoint.new(file, line, **kw)
858
+ add_breakpoint bp
859
+ rescue Errno::ENOENT => e
860
+ @ui.puts e.message
861
+ end
546
862
 
547
- if !lines.empty? && lines.last >= line
548
- nline = lines.bsearch{|l| line <= l}
549
- events = line_events[nline]
863
+ def pending_line_breakpoints
864
+ @bps.find_all do |key, bp|
865
+ LineBreakpoint === bp && !bp.iseq
866
+ end.each do |key, bp|
867
+ yield bp
868
+ end
869
+ end
550
870
 
551
- if !nearest
552
- nearest = NearestISeq.new(iseq, nline, events)
553
- else
554
- if nearest.iseq.first_lineno <= iseq.first_lineno
555
- if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
556
- events.include?(:RUBY_EVENT_CALL)
557
- nearest = NearestISeq.new(iseq, nline, events)
558
- end
871
+ def method_added tp
872
+ b = tp.binding
873
+ if var_name = b.local_variables.first
874
+ mid = b.local_variable_get(var_name)
875
+ found = false
876
+
877
+ @bps.each{|k, bp|
878
+ case bp
879
+ when MethodBreakpoint
880
+ if bp.method.nil?
881
+ found = true
882
+ if bp.sig_method_name == mid.to_s
883
+ bp.enable(quiet: true)
559
884
  end
560
885
  end
561
886
  end
887
+ }
888
+ unless found
889
+ METHOD_ADDED_TRACKER.disable
562
890
  end
563
- }
564
-
565
- if nearest
566
- add_line_breakpoint_exact nearest.iseq, nearest.events, file, nearest.line, cond
567
- else
568
- return nil
569
891
  end
570
892
  end
893
+ end
571
894
 
572
- def resolve_path file
573
- File.realpath(File.expand_path(file))
574
- rescue Errno::ENOENT
575
- file
576
- end
895
+ # String for requring location
896
+ # nil for -r
897
+ def self.require_location
898
+ locs = caller_locations
899
+ dir_prefix = /#{__dir__}/
577
900
 
578
- def add_line_breakpoint file, line, cond = nil
579
- file = resolve_path(file)
580
- bp = add_line_breakpoint_nearest file, line, cond
581
- @reserved_bps << [file, line, cond] unless bp
582
- bp
901
+ locs.each do |loc|
902
+ case loc.absolute_path
903
+ when dir_prefix
904
+ when %r{rubygems/core_ext/kernel_require\.rb}
905
+ else
906
+ return loc
907
+ end
583
908
  end
909
+ nil
910
+ end
584
911
 
585
- def add_method_breakpoint signature
586
- raise
587
- end
912
+ def self.console
913
+ initialize_session UI_Console.new
914
+
915
+ @prev_handler = trap(:SIGINT){
916
+ ThreadClient.current.on_trap :SIGINT
917
+ }
588
918
  end
589
919
 
590
- def self.add_line_breakpoint file, line, if: if_not_given = true
591
- ::DEBUGGER__::SESSION.add_line_breakpoint file, line, if_not_given ? nil : binding.local_variable_get(:if)
920
+ def self.add_line_breakpoint file, line, **kw
921
+ ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
592
922
  end
593
923
 
594
924
  def self.add_catch_breakpoint pat
@@ -600,12 +930,117 @@ module DEBUGGER__
600
930
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
601
931
 
602
932
  # default breakpoints
603
- ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
933
+
934
+ # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
604
935
 
605
936
  Binding.module_eval do
606
937
  ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
607
938
  def bp; nil; end
608
939
  end
940
+
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
948
+ end
949
+ end
950
+
951
+ load_rc
952
+ end
953
+ end
954
+
955
+ 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|
966
+ next unless path
967
+
968
+ if File.file? path
969
+ ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
970
+ end
971
+ }
972
+
973
+ # given debug commands
974
+ if ::DEBUGGER__::CONFIG[:commands]
975
+ cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
976
+ ::DEBUGGER__::SESSION.add_initial_commands cmds
977
+ end
978
+ end
979
+
980
+ def self.parse_help
981
+ helps = Hash.new{|h, k| h[k] = []}
982
+ desc = cat = nil
983
+ cmds = []
984
+
985
+ File.read(__FILE__).each_line do |line|
986
+ case line
987
+ when /\A\s*### (.+)/
988
+ cat = $1
989
+ break if $1 == 'END'
990
+ when /\A when (.+)/
991
+ next unless cat
992
+ next unless desc
993
+ ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
994
+ helps[cat] << [ws, desc]
995
+ desc = nil
996
+ cmds.concat ws
997
+ when /\A\s+# (\s*\*.+)/
998
+ if desc
999
+ desc << "\n" + $1
1000
+ else
1001
+ desc = $1
1002
+ end
1003
+ end
609
1004
  end
1005
+ @commands = cmds
1006
+ @helps = helps
610
1007
  end
1008
+
1009
+ def self.helps
1010
+ (defined?(@helps) && @helps) || parse_help
1011
+ end
1012
+
1013
+ def self.commands
1014
+ (defined?(@commands) && @commands) || (parse_help; @commands)
1015
+ end
1016
+
1017
+ def self.help
1018
+ r = []
1019
+ self.helps.each{|cat, cmds|
1020
+ r << "### #{cat}"
1021
+ r << ''
1022
+ cmds.each{|ws, desc|
1023
+ r << desc
1024
+ }
1025
+ r << ''
1026
+ }
1027
+ r.join("\n")
1028
+ end
1029
+
1030
+ CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
1031
+
1032
+ class ::Module
1033
+ def method_added mid; end
1034
+ def singleton_method_added mid; end
1035
+ end
1036
+
1037
+ def self.method_added tp
1038
+ begin
1039
+ SESSION.method_added tp
1040
+ rescue Exception => e
1041
+ p e
1042
+ end
1043
+ end
1044
+
1045
+ METHOD_ADDED_TRACKER = self.create_method_added_tracker
611
1046
  end