debug 1.6.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,23 @@ module DEBUGGER__
6
6
  class SourceRepository
7
7
  include Color
8
8
 
9
- if RubyVM.respond_to? :keep_script_lines
9
+ def file_src iseq
10
+ if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
11
+ File.readlines(path, chomp: true)
12
+ end
13
+ end
14
+
15
+ def get iseq
16
+ return unless iseq
17
+
18
+ if CONFIG[:show_evaledsrc]
19
+ orig_src(iseq) || file_src(iseq)
20
+ else
21
+ file_src(iseq) || orig_src(iseq)
22
+ end
23
+ end
24
+
25
+ if defined?(RubyVM.keep_script_lines)
10
26
  # Ruby 3.1 and later
11
27
  RubyVM.keep_script_lines = true
12
28
  require 'objspace'
@@ -18,7 +34,7 @@ module DEBUGGER__
18
34
  end
19
35
 
20
36
  def add iseq, src
21
- # do nothing
37
+ # only manage loaded file names
22
38
  if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
23
39
  if @loaded_file_map.has_key? path
24
40
  return path, true # reloaded
@@ -29,17 +45,13 @@ module DEBUGGER__
29
45
  end
30
46
  end
31
47
 
32
- def get iseq
33
- return unless iseq
34
-
35
- if lines = iseq.script_lines&.map(&:chomp)
36
- lines
48
+ def orig_src iseq
49
+ lines = iseq.script_lines&.map(&:chomp)
50
+ line = iseq.first_line
51
+ if line > 1
52
+ [*([''] * (line - 1)), *lines]
37
53
  else
38
- if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
39
- File.readlines(path, chomp: true)
40
- else
41
- nil
42
- end
54
+ lines
43
55
  end
44
56
  end
45
57
 
@@ -63,15 +75,22 @@ module DEBUGGER__
63
75
  end
64
76
 
65
77
  def add iseq, src
66
- if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
78
+ path = (iseq.absolute_path || iseq.path)
79
+
80
+ if path && File.exist?(path)
67
81
  reloaded = @files.has_key? path
68
- add_path path
69
- return path, reloaded
70
- elsif src
82
+
83
+ if src
84
+ add_iseq iseq, src
85
+ return path, reloaded
86
+ else
87
+ add_path path
88
+ return path, reloaded
89
+ end
90
+ else
71
91
  add_iseq iseq, src
92
+ nil
72
93
  end
73
-
74
- nil
75
94
  end
76
95
 
77
96
  private def all_iseq iseq, rs = []
@@ -87,7 +106,8 @@ module DEBUGGER__
87
106
  if line > 1
88
107
  src = ("\n" * (line - 1)) + src
89
108
  end
90
- si = SrcInfo.new(src.lines)
109
+
110
+ si = SrcInfo.new(src.each_line.map{|l| l.chomp})
91
111
  all_iseq(iseq).each{|e|
92
112
  e.instance_variable_set(:@debugger_si, si)
93
113
  e.freeze
@@ -102,7 +122,7 @@ module DEBUGGER__
102
122
 
103
123
  private def get_si iseq
104
124
  return unless iseq
105
-
125
+
106
126
  if iseq.instance_variable_defined?(:@debugger_si)
107
127
  iseq.instance_variable_get(:@debugger_si)
108
128
  elsif @files.has_key?(path = (iseq.absolute_path || iseq.path))
@@ -112,7 +132,7 @@ module DEBUGGER__
112
132
  end
113
133
  end
114
134
 
115
- def get iseq
135
+ def orig_src iseq
116
136
  if si = get_si(iseq)
117
137
  si.src
118
138
  end
data/lib/debug/start.rb CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require_relative 'session'
4
4
  return unless defined?(DEBUGGER__)
5
- DEBUGGER__.start
5
+ DEBUGGER__.start no_sigint_hook: false
@@ -5,6 +5,10 @@ require 'pp'
5
5
 
6
6
  require_relative 'color'
7
7
 
8
+ class ::Thread
9
+ attr_accessor :debug_thread_client
10
+ end
11
+
8
12
  module DEBUGGER__
9
13
  M_INSTANCE_VARIABLES = method(:instance_variables).unbind
10
14
  M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
@@ -14,11 +18,12 @@ module DEBUGGER__
14
18
  M_RESPOND_TO_P = method(:respond_to?).unbind
15
19
  M_METHOD = method(:method).unbind
16
20
  M_OBJECT_ID = method(:object_id).unbind
21
+ M_NAME = method(:name).unbind
17
22
 
18
23
  module SkipPathHelper
19
24
  def skip_path?(path)
20
25
  !path ||
21
- CONFIG.skip? ||
26
+ DEBUGGER__.skip? ||
22
27
  ThreadClient.current.management? ||
23
28
  skip_internal_path?(path) ||
24
29
  skip_config_skip_path?(path)
@@ -29,7 +34,7 @@ module DEBUGGER__
29
34
  end
30
35
 
31
36
  def skip_internal_path?(path)
32
- path.start_with?(__dir__) || path.start_with?('<internal:')
37
+ path.start_with?(__dir__) || path.delete_prefix('!eval:').start_with?('<internal:')
33
38
  end
34
39
 
35
40
  def skip_location?(loc)
@@ -38,18 +43,21 @@ module DEBUGGER__
38
43
  end
39
44
  end
40
45
 
46
+ module GlobalVariablesHelper
47
+ SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
48
+ def safe_global_variables
49
+ global_variables.reject{|name| SKIP_GLOBAL_LIST.include? name }
50
+ end
51
+ end
52
+
41
53
  class ThreadClient
42
54
  def self.current
43
- if thc = Thread.current[:DEBUGGER__ThreadClient]
44
- thc
45
- else
46
- thc = SESSION.get_thread_client
47
- Thread.current[:DEBUGGER__ThreadClient] = thc
48
- end
55
+ Thread.current.debug_thread_client ||= SESSION.get_thread_client
49
56
  end
50
57
 
51
58
  include Color
52
59
  include SkipPathHelper
60
+ include GlobalVariablesHelper
53
61
 
54
62
  attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
55
63
 
@@ -291,8 +299,10 @@ module DEBUGGER__
291
299
  end
292
300
 
293
301
  if event != :pause
294
- show_src
295
- show_frames CONFIG[:show_frames]
302
+ unless bp&.skip_src
303
+ show_src
304
+ show_frames CONFIG[:show_frames]
305
+ end
296
306
 
297
307
  set_mode :waiting
298
308
 
@@ -328,11 +338,15 @@ module DEBUGGER__
328
338
  @step_tp.disable if @step_tp
329
339
 
330
340
  thread = Thread.current
341
+ subsession_id = SESSION.subsession_id
331
342
 
332
343
  if SUPPORT_TARGET_THREAD
333
344
  @step_tp = TracePoint.new(*events){|tp|
334
- next if SESSION.break_at? tp.path, tp.lineno
335
- next if !yield(tp.event)
345
+ if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
346
+ tp.disable
347
+ next
348
+ end
349
+ next if !yield(tp)
336
350
  next if tp.path.start_with?(__dir__)
337
351
  next if tp.path.start_with?('<internal:trace_point>')
338
352
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -347,8 +361,11 @@ module DEBUGGER__
347
361
  else
348
362
  @step_tp = TracePoint.new(*events){|tp|
349
363
  next if thread != Thread.current
350
- next if SESSION.break_at? tp.path, tp.lineno
351
- next if !yield(tp.event)
364
+ if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
365
+ tp.disable
366
+ next
367
+ end
368
+ next if !yield(tp)
352
369
  next if tp.path.start_with?(__dir__)
353
370
  next if tp.path.start_with?('<internal:trace_point>')
354
371
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -384,15 +401,19 @@ module DEBUGGER__
384
401
  end
385
402
  end
386
403
 
387
- def frame_eval_core src, b
404
+ def frame_eval_core src, b, binding_location: false
388
405
  saved_target_frames = @target_frames
389
406
  saved_current_frame_index = @current_frame_index
390
407
 
391
408
  if b
392
- f, _l = b.source_location
409
+ file, lineno = b.source_location
393
410
 
394
411
  tp_allow_reentry do
395
- b.eval(src, "(rdbg)/#{f}")
412
+ if binding_location
413
+ b.eval(src, file, lineno)
414
+ else
415
+ b.eval(src, "(rdbg)/#{file}")
416
+ end
396
417
  end
397
418
  else
398
419
  frame_self = current_frame.self
@@ -411,16 +432,16 @@ module DEBUGGER__
411
432
  [:return_value, "_return"],
412
433
  ]
413
434
 
414
- def frame_eval src, re_raise: false
435
+ def frame_eval src, re_raise: false, binding_location: false
415
436
  @success_last_eval = false
416
437
 
417
- b = current_frame.eval_binding
438
+ b = current_frame&.eval_binding || TOPLEVEL_BINDING
418
439
 
419
440
  special_local_variables current_frame do |name, var|
420
441
  b.local_variable_set(name, var) if /\%/ !~ name
421
442
  end
422
443
 
423
- result = frame_eval_core(src, b)
444
+ result = frame_eval_core(src, b, binding_location: binding_location)
424
445
 
425
446
  @success_last_eval = true
426
447
  result
@@ -447,10 +468,14 @@ module DEBUGGER__
447
468
  if file_lines = frame.file_lines
448
469
  frame_line = frame.location.lineno - 1
449
470
 
450
- lines = file_lines.map.with_index do |e, i|
451
- cur = i == frame_line ? '=>' : ' '
452
- line = colorize_dim('%4d|' % (i+1))
453
- "#{cur}#{line} #{e}"
471
+ if CONFIG[:no_lineno]
472
+ lines = file_lines
473
+ else
474
+ lines = file_lines.map.with_index do |e, i|
475
+ cur = i == frame_line ? '=>' : ' '
476
+ line = colorize_dim('%4d|' % (i+1))
477
+ "#{cur}#{line} #{e}"
478
+ end
454
479
  end
455
480
 
456
481
  unless start_line
@@ -482,19 +507,28 @@ module DEBUGGER__
482
507
  exit!
483
508
  end
484
509
 
485
- def show_src(frame_index: @current_frame_index, update_line: false, max_lines: CONFIG[:show_src_lines], **options)
510
+ def show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: CONFIG[:show_src_lines], **options)
486
511
  if frame = get_frame(frame_index)
487
- start_line, end_line, lines = *get_src(frame, max_lines: max_lines, **options)
488
-
489
- if start_line
490
- if update_line
491
- frame.show_line = end_line
512
+ begin
513
+ if ignore_show_line
514
+ prev_show_line = frame.show_line
515
+ frame.show_line = nil
492
516
  end
493
517
 
494
- puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
495
- puts lines[start_line...end_line]
496
- else
497
- puts "# No sourcefile available for #{frame.path}"
518
+ start_line, end_line, lines = *get_src(frame, max_lines: max_lines, **options)
519
+
520
+ if start_line
521
+ if update_line
522
+ frame.show_line = end_line
523
+ end
524
+
525
+ puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
526
+ puts lines[start_line...end_line]
527
+ else
528
+ puts "# No sourcefile available for #{frame.path}"
529
+ end
530
+ ensure
531
+ frame.show_line = prev_show_line if prev_show_line
498
532
  end
499
533
  end
500
534
  end
@@ -546,48 +580,81 @@ module DEBUGGER__
546
580
  end
547
581
  end
548
582
 
549
- def show_ivars pat
550
- if s = current_frame&.self
551
- M_INSTANCE_VARIABLES.bind_call(s).sort.each{|iv|
552
- value = M_INSTANCE_VARIABLE_GET.bind_call(s, iv)
583
+ def show_ivars pat, expr = nil
584
+ if expr && !expr.empty?
585
+ _self = frame_eval(expr);
586
+ elsif _self = current_frame&.self
587
+ else
588
+ _self = nil
589
+ end
590
+
591
+ if _self
592
+ M_INSTANCE_VARIABLES.bind_call(_self).sort.each{|iv|
593
+ value = M_INSTANCE_VARIABLE_GET.bind_call(_self, iv)
553
594
  puts_variable_info iv, value, pat
554
595
  }
555
596
  end
556
597
  end
557
598
 
558
- def show_consts pat, only_self: false
559
- if s = current_frame&.self
599
+ def iter_consts c, names = {}
600
+ c.constants(false).sort.each{|name|
601
+ next if names.has_key? name
602
+ names[name] = nil
603
+ begin
604
+ value = c.const_get(name)
605
+ rescue Exception => e
606
+ value = e
607
+ end
608
+ yield name, value
609
+ }
610
+ end
611
+
612
+ def get_consts expr = nil, only_self: false, &block
613
+ if expr && !expr.empty?
614
+ begin
615
+ _self = frame_eval(expr, re_raise: true)
616
+ rescue Exception
617
+ # ignore
618
+ else
619
+ if M_KIND_OF_P.bind_call(_self, Module)
620
+ iter_consts _self, &block
621
+ return
622
+ else
623
+ puts "#{_self.inspect} (by #{expr}) is not a Module."
624
+ end
625
+ end
626
+ elsif _self = current_frame&.self
560
627
  cs = {}
561
- if M_KIND_OF_P.bind_call(s, Module)
562
- cs[s] = :self
628
+ if M_KIND_OF_P.bind_call(_self, Module)
629
+ cs[_self] = :self
563
630
  else
564
- s = M_CLASS.bind_call(s)
565
- cs[s] = :self unless only_self
631
+ _self = M_CLASS.bind_call(_self)
632
+ cs[_self] = :self unless only_self
566
633
  end
567
634
 
568
635
  unless only_self
569
- s.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
636
+ _self.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
570
637
  if b = current_frame&.binding
571
- b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
638
+ b.eval('::Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
572
639
  end
573
640
  end
574
641
 
575
642
  names = {}
576
643
 
577
644
  cs.each{|c, _|
578
- c.constants(false).sort.each{|name|
579
- next if names.has_key? name
580
- names[name] = nil
581
- value = c.const_get(name)
582
- puts_variable_info name, value, pat
583
- }
645
+ iter_consts c, names, &block
584
646
  }
585
647
  end
586
648
  end
587
649
 
588
- SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
650
+ def show_consts pat, expr = nil, only_self: false
651
+ get_consts expr, only_self: only_self do |name, value|
652
+ puts_variable_info name, value, pat
653
+ end
654
+ end
655
+
589
656
  def show_globals pat
590
- global_variables.sort.each{|name|
657
+ safe_global_variables.sort.each{|name|
591
658
  next if SKIP_GLOBAL_LIST.include? name
592
659
 
593
660
  value = eval(name.to_s)
@@ -643,7 +710,8 @@ module DEBUGGER__
643
710
  if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
644
711
  puts "command: #{editor}"
645
712
  puts " path: #{path}"
646
- system(editor, path)
713
+ require 'shellwords'
714
+ system(*Shellwords.split(editor), path)
647
715
  else
648
716
  puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
649
717
  end
@@ -758,7 +826,7 @@ module DEBUGGER__
758
826
  case args.first
759
827
  when :method
760
828
  klass_name, op, method_name, cond, cmd, path = args[1..]
761
- bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
829
+ bp = MethodBreakpoint.new(current_frame&.eval_binding || TOPLEVEL_BINDING, klass_name, op, method_name, cond: cond, command: cmd, path: path)
762
830
  begin
763
831
  bp.enable
764
832
  rescue NameError => e
@@ -793,8 +861,18 @@ module DEBUGGER__
793
861
  class SuspendReplay < Exception
794
862
  end
795
863
 
864
+ if ::Fiber.respond_to?(:blocking)
865
+ private def fiber_blocking
866
+ ::Fiber.blocking{yield}
867
+ end
868
+ else
869
+ private def fiber_blocking
870
+ yield
871
+ end
872
+ end
873
+
796
874
  def wait_next_action
797
- wait_next_action_
875
+ fiber_blocking{wait_next_action_}
798
876
  rescue SuspendReplay
799
877
  replay_suspend
800
878
  end
@@ -814,6 +892,7 @@ module DEBUGGER__
814
892
  set_mode :waiting if !waiting?
815
893
  cmds = @q_cmd.pop
816
894
  # pp [self, cmds: cmds]
895
+
817
896
  break unless cmds
818
897
  ensure
819
898
  set_mode :running
@@ -831,8 +910,9 @@ module DEBUGGER__
831
910
 
832
911
  case step_type
833
912
  when :in
913
+ iter = iter || 1
834
914
  if @recorder&.replaying?
835
- @recorder.step_forward
915
+ @recorder.step_forward iter
836
916
  raise SuspendReplay
837
917
  else
838
918
  step_tp iter do
@@ -845,6 +925,7 @@ module DEBUGGER__
845
925
  frame = @target_frames.first
846
926
  path = frame.location.absolute_path || "!eval:#{frame.path}"
847
927
  line = frame.location.lineno
928
+ label = frame.location.base_label
848
929
 
849
930
  if frame.iseq
850
931
  frame.iseq.traceable_lines_norec(lines = {})
@@ -856,35 +937,89 @@ module DEBUGGER__
856
937
 
857
938
  depth = @target_frames.first.frame_depth
858
939
 
859
- step_tp iter do
940
+ step_tp iter do |tp|
860
941
  loc = caller_locations(2, 1).first
861
942
  loc_path = loc.absolute_path || "!eval:#{loc.path}"
943
+ loc_label = loc.base_label
944
+ loc_depth = DEBUGGER__.frame_depth - 3
862
945
 
863
- # same stack depth
864
- (DEBUGGER__.frame_depth - 3 <= depth) ||
865
-
866
- # different frame
867
- (next_line && loc_path == path &&
868
- (loc_lineno = loc.lineno) > line &&
869
- loc_lineno <= next_line)
946
+ case
947
+ when loc_depth == depth && loc_label == label
948
+ true
949
+ when loc_depth < depth
950
+ # lower stack depth
951
+ true
952
+ when (next_line &&
953
+ loc_path == path &&
954
+ (loc_lineno = loc.lineno) > line &&
955
+ loc_lineno <= next_line)
956
+ # different frame (maybe block) but the line is before next_line
957
+ true
958
+ end
870
959
  end
871
960
  break
872
961
 
873
962
  when :finish
874
963
  finish_frames = (iter || 1) - 1
875
- goal_depth = @target_frames.first.frame_depth - finish_frames
964
+ frame = @target_frames.first
965
+ goal_depth = frame.frame_depth - finish_frames - (frame.has_return_value ? 1 : 0)
876
966
 
877
967
  step_tp nil, [:return, :b_return] do
878
968
  DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
879
969
  end
880
970
  break
881
971
 
972
+ when :until
973
+ location = iter&.strip
974
+ frame = @target_frames.first
975
+ depth = frame.frame_depth - (frame.has_return_value ? 1 : 0)
976
+ target_location_label = frame.location.base_label
977
+
978
+ case location
979
+ when nil, /\A(?:(.+):)?(\d+)\z/
980
+ no_loc = !location
981
+ file = $1 || frame.location.path
982
+ line = ($2 || frame.location.lineno + 1).to_i
983
+
984
+ step_tp nil, [:line, :return] do |tp|
985
+ if tp.event == :line
986
+ next false if no_loc && depth < DEBUGGER__.frame_depth - 3
987
+ next false unless tp.path.end_with?(file)
988
+ next false unless tp.lineno >= line
989
+ true
990
+ else
991
+ true if depth >= DEBUGGER__.frame_depth - 3 &&
992
+ caller_locations(2, 1).first.label == target_location_label
993
+ # TODO: imcomplete condition
994
+ end
995
+ end
996
+ else
997
+ pat = location
998
+ if /\A\/(.+)\/\z/ =~ pat
999
+ pat = Regexp.new($1)
1000
+ end
1001
+
1002
+ step_tp nil, [:call, :c_call, :return] do |tp|
1003
+ case tp.event
1004
+ when :call, :c_call
1005
+ true if pat === tp.callee_id.to_s
1006
+ else # :return, :b_return
1007
+ true if depth >= DEBUGGER__.frame_depth - 3 &&
1008
+ caller_locations(2, 1).first.label == target_location_label
1009
+ # TODO: imcomplete condition
1010
+ end
1011
+ end
1012
+ end
1013
+
1014
+ break
1015
+
882
1016
  when :back
1017
+ iter = iter || 1
883
1018
  if @recorder&.can_step_back?
884
1019
  unless @recorder.backup_frames
885
1020
  @recorder.backup_frames = @target_frames
886
1021
  end
887
- @recorder.step_back
1022
+ @recorder.step_back iter
888
1023
  raise SuspendReplay
889
1024
  else
890
1025
  puts "Can not step back more."
@@ -922,12 +1057,8 @@ module DEBUGGER__
922
1057
  when :call
923
1058
  result = frame_eval(eval_src)
924
1059
  when :irb
925
- begin
926
- result = frame_eval('binding.irb')
927
- ensure
928
- # workaround: https://github.com/ruby/debug/issues/308
929
- Reline.prompt_proc = nil if defined? Reline
930
- end
1060
+ require_relative "irb_integration"
1061
+ activate_irb_integration
931
1062
  when :display, :try_display
932
1063
  failed_results = []
933
1064
  eval_src.each_with_index{|src, i|
@@ -988,6 +1119,10 @@ module DEBUGGER__
988
1119
  when :list
989
1120
  show_src(update_line: true, **(args.first || {}))
990
1121
 
1122
+ when :whereami
1123
+ show_src ignore_show_line: true
1124
+ show_frames CONFIG[:show_frames]
1125
+
991
1126
  when :edit
992
1127
  show_by_editor(args.first)
993
1128
 
@@ -1003,11 +1138,13 @@ module DEBUGGER__
1003
1138
 
1004
1139
  when :ivars
1005
1140
  pat = args.shift
1006
- show_ivars pat
1141
+ expr = args.shift
1142
+ show_ivars pat, expr
1007
1143
 
1008
1144
  when :consts
1009
1145
  pat = args.shift
1010
- show_consts pat
1146
+ expr = args.shift
1147
+ show_consts pat, expr
1011
1148
 
1012
1149
  when :globals
1013
1150
  pat = args.shift
@@ -1093,6 +1230,8 @@ module DEBUGGER__
1093
1230
  end
1094
1231
  event! :result, nil
1095
1232
 
1233
+ when :quit
1234
+ sleep # wait for SystemExit
1096
1235
  when :dap
1097
1236
  process_dap args
1098
1237
  when :cdp
@@ -1105,8 +1244,18 @@ module DEBUGGER__
1105
1244
  rescue SuspendReplay, SystemExit, Interrupt
1106
1245
  raise
1107
1246
  rescue Exception => e
1108
- pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1247
+ STDERR.puts e.cause.inspect
1248
+ STDERR.puts e.inspect
1249
+ Thread.list.each{|th|
1250
+ STDERR.puts "@@@ #{th}"
1251
+ th.backtrace.each{|b|
1252
+ STDERR.puts " > #{b}"
1253
+ }
1254
+ }
1255
+ p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1109
1256
  raise
1257
+ ensure
1258
+ @returning = false
1110
1259
  end
1111
1260
 
1112
1261
  def debug_event(ev, args)
@@ -1165,10 +1314,14 @@ module DEBUGGER__
1165
1314
  frame._callee = b.eval('__callee__')
1166
1315
  end
1167
1316
  }
1168
- @log << frames
1317
+ append(frames)
1169
1318
  }
1170
1319
  end
1171
1320
 
1321
+ def append frames
1322
+ @log << frames
1323
+ end
1324
+
1172
1325
  def enable
1173
1326
  unless @tp_recorder.enabled?
1174
1327
  @log.clear
@@ -1187,12 +1340,18 @@ module DEBUGGER__
1187
1340
  @tp_recorder.enabled?
1188
1341
  end
1189
1342
 
1190
- def step_back
1191
- @index += 1
1343
+ def step_back iter
1344
+ @index += iter
1345
+ if @index > @log.size
1346
+ @index = @log.size
1347
+ end
1192
1348
  end
1193
1349
 
1194
- def step_forward
1195
- @index -= 1
1350
+ def step_forward iter
1351
+ @index -= iter
1352
+ if @index < 0
1353
+ @index = 0
1354
+ end
1196
1355
  end
1197
1356
 
1198
1357
  def step_reset