debug 1.0.0 → 1.2.2

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,7 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # skip to load debugger for bundle exec
4
- return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
4
+
5
+ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
6
+ trace_var(:$0) do |file|
7
+ trace_var(:$0, nil)
8
+ if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
9
+ lib = $1
10
+ $LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
11
+ ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
12
+ require lib
13
+ ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = nil
14
+ end
15
+ end
16
+
17
+ return
18
+ end
5
19
 
6
20
  require_relative 'config'
7
21
  require_relative 'thread_client'
@@ -9,6 +23,10 @@ require_relative 'source_repository'
9
23
  require_relative 'breakpoint'
10
24
  require_relative 'tracer'
11
25
 
26
+ # To prevent loading old lib/debug.rb in Ruby 2.6 to 3.0
27
+ $LOADED_FEATURES << 'debug.rb'
28
+ require 'debug' # invalidate the $LOADED_FEATURE cache
29
+
12
30
  require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
13
31
 
14
32
  class RubyVM::InstructionSequence
@@ -68,7 +86,7 @@ module DEBUGGER__
68
86
  # [:watch, ivar] => WatchIVarBreakpoint
69
87
  # [:check, expr] => CheckBreakpoint
70
88
  #
71
- @tracers = []
89
+ @tracers = {}
72
90
  @th_clients = nil # {Thread => ThreadClient}
73
91
  @q_evt = Queue.new
74
92
  @displays = []
@@ -78,17 +96,22 @@ module DEBUGGER__
78
96
  @postmortem_hook = nil
79
97
  @postmortem = false
80
98
  @thread_stopper = nil
99
+ @intercept_trap_sigint = false
100
+ @intercepted_sigint_cmd = 'DEFAULT'
81
101
 
82
102
  @frame_map = {} # {id => [threadId, frame_depth]} for DAP
83
103
  @var_map = {1 => [:globals], } # {id => ...} for DAP
84
104
  @src_map = {} # {id => src}
85
105
 
106
+ @tp_thread_begin = nil
86
107
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
87
108
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
88
109
  }
89
110
  @tp_load_script.enable
90
111
 
91
112
  activate
113
+
114
+ self.postmortem = CONFIG[:postmortem]
92
115
  end
93
116
 
94
117
  def active?
@@ -99,40 +122,41 @@ module DEBUGGER__
99
122
  @bps.has_key? [file, line]
100
123
  end
101
124
 
102
- def check_forked
103
- unless active?
104
- # TODO: Support it
105
- raise 'DEBUGGER: stop at forked process is not supported yet.'
125
+ def activate on_fork: false
126
+ @tp_thread_begin&.disable
127
+ @tp_thread_begin = nil
128
+
129
+ if on_fork
130
+ @ui.activate self, on_fork: true
131
+ else
132
+ @ui.activate self, on_fork: false
106
133
  end
107
- end
108
134
 
109
- def activate on_fork: false
135
+ q = Queue.new
110
136
  @session_server = Thread.new do
111
137
  Thread.current.name = 'DEBUGGER__::SESSION@server'
112
138
  Thread.current.abort_on_exception = true
113
- session_server_main
114
- end
115
139
 
116
- setup_threads
140
+ # Thread management
141
+ setup_threads
142
+ thc = thread_client Thread.current
143
+ thc.is_management
117
144
 
118
- thc = thread_client @session_server
119
- thc.is_management
145
+ if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
146
+ thc.is_management
147
+ end
120
148
 
121
- if on_fork
122
- @tp_thread_begin.disable
123
- @tp_thread_begin = nil
124
- @ui.activate on_fork: true
125
- end
149
+ @tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
150
+ thread_client
151
+ end
152
+ @tp_thread_begin.enable
126
153
 
127
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
128
- thc.is_management
154
+ # session start
155
+ q << true
156
+ session_server_main
129
157
  end
130
158
 
131
- @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
132
- th = Thread.current
133
- ThreadClient.current.on_thread_begin th
134
- }
135
- @tp_thread_begin.enable
159
+ q.pop
136
160
  end
137
161
 
138
162
  def deactivate
@@ -142,14 +166,14 @@ module DEBUGGER__
142
166
  @tp_thread_begin.disable
143
167
  @bps.each{|k, bp| bp.disable}
144
168
  @th_clients.each{|th, thc| thc.close}
145
- @tracers.each{|t| t.disable}
169
+ @tracers.values.each{|t| t.disable}
146
170
  @q_evt.close
147
171
  @ui&.deactivate
148
172
  @ui = nil
149
173
  end
150
174
 
151
175
  def reset_ui ui
152
- @ui.close
176
+ @ui.deactivate
153
177
  @ui = ui
154
178
  end
155
179
 
@@ -159,11 +183,18 @@ module DEBUGGER__
159
183
 
160
184
  def session_server_main
161
185
  while evt = pop_event
162
- # varible `@internal_info` is only used for test
186
+ # variable `@internal_info` is only used for test
163
187
  tc, output, ev, @internal_info, *ev_args = evt
164
188
  output.each{|str| @ui.puts str}
165
189
 
166
190
  case ev
191
+
192
+ when :thread_begin # special event, tc is nil
193
+ th = ev_args.shift
194
+ q = ev_args.shift
195
+ on_thread_begin th
196
+ q << true
197
+
167
198
  when :init
168
199
  wait_command_loop tc
169
200
 
@@ -175,26 +206,25 @@ module DEBUGGER__
175
206
 
176
207
  when :trace
177
208
  trace_id, msg = ev_args
178
- if t = @tracers.find{|t| t.object_id == trace_id}
209
+ if t = @tracers.values.find{|t| t.object_id == trace_id}
179
210
  t.puts msg
180
211
  end
181
212
  tc << :continue
182
213
 
183
- when :thread_begin
184
- th = ev_args.shift
185
- on_thread_begin th
186
- @ui.event :thread_begin, th
187
- tc << :continue
188
-
189
214
  when :suspend
190
215
  case ev_args.first
191
216
  when :breakpoint
192
217
  bp, i = bp_index ev_args[1]
193
- @ui.event :suspend_bp, i, bp
218
+ @ui.event :suspend_bp, i, bp, tc.id
194
219
  when :trap
195
- @ui.event :suspend_trap, ev_args[1]
220
+ @ui.event :suspend_trap, sig = ev_args[1], tc.id
221
+
222
+ if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
223
+ @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
224
+ @ui.puts "`sigint` command execute it."
225
+ end
196
226
  else
197
- @ui.event :suspended
227
+ @ui.event :suspended, tc.id
198
228
  end
199
229
 
200
230
  if @displays.empty?
@@ -228,8 +258,7 @@ module DEBUGGER__
228
258
  obj_id = ev_args[1]
229
259
  obj_inspect = ev_args[2]
230
260
  opt = ev_args[3]
231
- @tracers << t = ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
232
- @ui.puts "Enable #{t.to_s}"
261
+ add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
233
262
  else
234
263
  # ignore
235
264
  end
@@ -414,6 +443,29 @@ module DEBUGGER__
414
443
  when 'kill!'
415
444
  exit! (arg || 1).to_i
416
445
 
446
+ # * `sigint`
447
+ # * Execute SIGINT handler registerred by the debuggee.
448
+ # * Note that this command should be used just after stop by `SIGINT`.
449
+ when 'sigint'
450
+ begin
451
+ case cmd = @intercepted_sigint_cmd
452
+ when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
453
+ # ignore
454
+ when String
455
+ eval(cmd)
456
+ when Proc
457
+ cmd.call
458
+ end
459
+
460
+ @tc << :continue
461
+ restart_all_threads
462
+
463
+ rescue Exception => e
464
+ @ui.puts "Exception: #{e}"
465
+ @ui.puts e.backtrace.map{|line| " #{e}"}
466
+ return :retry
467
+ end
468
+
417
469
  ### Breakpoint
418
470
 
419
471
  # * `b[reak]`
@@ -598,7 +650,7 @@ module DEBUGGER__
598
650
  # * Show information about the current frame (local variables)
599
651
  # * It includes `self` as `%self` and a return value as `%return`.
600
652
  # * `i[nfo] i[var[s]]` or `i[nfo] instance`
601
- # * Show information about insttance variables about `self`.
653
+ # * Show information about instance variables about `self`.
602
654
  # * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
603
655
  # * Show information about accessible constants except toplevel constants.
604
656
  # * `i[nfo] g[lobal[s]]`
@@ -722,7 +774,7 @@ module DEBUGGER__
722
774
  @ui.puts "not supported on the remote console."
723
775
  return :retry
724
776
  end
725
- @tc << [:eval, :call, 'binding.irb']
777
+ @tc << [:eval, :irb]
726
778
 
727
779
  # don't repeat irb command
728
780
  @repl_prev_line = nil
@@ -760,32 +812,29 @@ module DEBUGGER__
760
812
  case arg
761
813
  when nil
762
814
  @ui.puts 'Tracers:'
763
- @tracers.each_with_index{|t, i|
815
+ @tracers.values.each_with_index{|t, i|
764
816
  @ui.puts "* \##{i} #{t}"
765
817
  }
766
818
  @ui.puts
767
819
  return :retry
768
820
 
769
821
  when /\Aline\z/
770
- @tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
771
- @ui.puts "Enable #{t.to_s}"
822
+ add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
772
823
  return :retry
773
824
 
774
825
  when /\Acall\z/
775
- @tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
776
- @ui.puts "Enable #{t.to_s}"
826
+ add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
777
827
  return :retry
778
828
 
779
829
  when /\Aexception\z/
780
- @tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
781
- @ui.puts "Enable #{t.to_s}"
830
+ add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
782
831
  return :retry
783
832
 
784
833
  when /\Aobject\s+(.+)/
785
834
  @tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
786
835
 
787
836
  when /\Aoff\s+(\d+)\z/
788
- if t = @tracers[$1.to_i]
837
+ if t = @tracers.values[$1.to_i]
789
838
  t.disable
790
839
  @ui.puts "Disable #{t.to_s}"
791
840
  else
@@ -794,7 +843,7 @@ module DEBUGGER__
794
843
  return :retry
795
844
 
796
845
  when /\Aoff(\s+(line|call|exception|object))?\z/
797
- @tracers.each{|t|
846
+ @tracers.values.each{|t|
798
847
  if $2.nil? || t.type == $2
799
848
  t.disable
800
849
  @ui.puts "Disable #{t.to_s}"
@@ -1172,6 +1221,20 @@ module DEBUGGER__
1172
1221
  @ui.puts e.message
1173
1222
  end
1174
1223
 
1224
+ # tracers
1225
+
1226
+ def add_tracer tracer
1227
+ # don't repeat commands that add tracers
1228
+ @repl_prev_line = nil
1229
+ if @tracers.has_key? tracer.key
1230
+ tracer.disable
1231
+ @ui.puts "Duplicated tracer: #{tracer}"
1232
+ else
1233
+ @tracers[tracer.key] = tracer
1234
+ @ui.puts "Enable #{tracer}"
1235
+ end
1236
+ end
1237
+
1175
1238
  # threads
1176
1239
 
1177
1240
  def update_thread_list
@@ -1224,10 +1287,6 @@ module DEBUGGER__
1224
1287
  thread_list
1225
1288
  end
1226
1289
 
1227
- def thread_client_create th
1228
- @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1229
- end
1230
-
1231
1290
  def setup_threads
1232
1291
  @th_clients = {}
1233
1292
 
@@ -1238,18 +1297,38 @@ module DEBUGGER__
1238
1297
 
1239
1298
  def on_thread_begin th
1240
1299
  if @th_clients.has_key? th
1241
- # OK
1242
- else
1243
1300
  # TODO: NG?
1301
+ else
1244
1302
  thread_client_create th
1245
1303
  end
1246
1304
  end
1247
1305
 
1248
- def thread_client thr = Thread.current
1249
- if @th_clients.has_key? thr
1250
- @th_clients[thr]
1306
+ private def thread_client_create th
1307
+ # TODO: Ractor support
1308
+ raise "Only session_server can create thread_client" unless Thread.current == @session_server
1309
+ @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1310
+ end
1311
+
1312
+ private def ask_thread_client th = Thread.current
1313
+ # TODO: Ractor support
1314
+ q2 = Queue.new
1315
+ # tc, output, ev, @internal_info, *ev_args = evt
1316
+ @q_evt << [nil, [], :thread_begin, nil, th, q2]
1317
+ q2.pop
1318
+
1319
+ @th_clients[th] or raise "unexpected error"
1320
+ end
1321
+
1322
+ # can be called by other threads
1323
+ def thread_client th = Thread.current
1324
+ if @th_clients.has_key? th
1325
+ @th_clients[th]
1251
1326
  else
1252
- @th_clients[thr] = thread_client_create(thr)
1327
+ if Thread.current == @session_server
1328
+ thread_client_create th
1329
+ else
1330
+ ask_thread_client th
1331
+ end
1253
1332
  end
1254
1333
  end
1255
1334
 
@@ -1368,7 +1447,10 @@ module DEBUGGER__
1368
1447
  end
1369
1448
  end
1370
1449
 
1371
- def enter_postmortem_session frames
1450
+ def enter_postmortem_session exc
1451
+ return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
1452
+
1453
+ frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
1372
1454
  @postmortem = true
1373
1455
  ThreadClient.current.suspend :postmortem, postmortem_frames: frames
1374
1456
  ensure
@@ -1381,7 +1463,7 @@ module DEBUGGER__
1381
1463
  @postmortem_hook = TracePoint.new(:raise){|tp|
1382
1464
  exc = tp.raised_exception
1383
1465
  frames = DEBUGGER__.capture_frames(__dir__)
1384
- exc.instance_variable_set(:@postmortem_frames, frames)
1466
+ exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
1385
1467
  }
1386
1468
  at_exit{
1387
1469
  @postmortem_hook.disable
@@ -1393,7 +1475,7 @@ module DEBUGGER__
1393
1475
  @ui.puts exc.backtrace.map{|e| ' ' + e}
1394
1476
  @ui.puts "\n"
1395
1477
 
1396
- enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
1478
+ enter_postmortem_session exc
1397
1479
  rescue SystemExit
1398
1480
  exit!
1399
1481
  rescue Exception => e
@@ -1413,6 +1495,30 @@ module DEBUGGER__
1413
1495
  end
1414
1496
  end
1415
1497
  end
1498
+
1499
+ def save_int_trap cmd
1500
+ prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
1501
+ prev
1502
+ end
1503
+
1504
+ attr_reader :intercepted_sigint_cmd
1505
+
1506
+ def intercept_trap_sigint?
1507
+ @intercept_trap_sigint
1508
+ end
1509
+
1510
+ def intercept_trap_sigint flag, &b
1511
+ prev = @intercept_trap_sigint
1512
+ @intercept_trap_sigint = flag
1513
+ yield
1514
+ ensure
1515
+ @intercept_trap_sigint = prev
1516
+ end
1517
+
1518
+ def intercept_trap_sigint_start prev
1519
+ @intercept_trap_sigint = true
1520
+ @intercepted_sigint_cmd = prev
1521
+ end
1416
1522
  end
1417
1523
 
1418
1524
  class UI_Base
@@ -1507,7 +1613,13 @@ module DEBUGGER__
1507
1613
 
1508
1614
  def self.setup_initial_suspend
1509
1615
  if !CONFIG[:nonstop]
1510
- if loc = ::DEBUGGER__.require_location
1616
+ case
1617
+ when CONFIG[:stop_at_load]
1618
+ add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, hook_call: false
1619
+ nil # stop here
1620
+ when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
1621
+ add_line_breakpoint path, 0, oneshot: true, hook_call: false
1622
+ when loc = ::DEBUGGER__.require_location
1511
1623
  # require 'debug/start' or 'debug'
1512
1624
  add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
1513
1625
  else
@@ -1519,29 +1631,8 @@ module DEBUGGER__
1519
1631
 
1520
1632
  class << self
1521
1633
  define_method :initialize_session do |ui|
1522
- DEBUGGER__.warn "Session start (pid: #{Process.pid})"
1523
-
1634
+ DEBUGGER__.info "Session start (pid: #{Process.pid})"
1524
1635
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
1525
-
1526
- # default breakpoints
1527
-
1528
- # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
1529
-
1530
- Binding.module_eval do
1531
- def break pre: nil, do: nil
1532
- return unless SESSION.active?
1533
-
1534
- if pre || (do_expr = binding.local_variable_get(:do))
1535
- cmds = ['binding.break', pre, do_expr]
1536
- end
1537
-
1538
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
1539
- true
1540
- end
1541
- alias b break
1542
- # alias bp break
1543
- end
1544
-
1545
1636
  load_rc
1546
1637
  end
1547
1638
  end
@@ -1599,14 +1690,6 @@ module DEBUGGER__
1599
1690
  end
1600
1691
  end
1601
1692
 
1602
- LOG_LEVELS = {
1603
- UNKNOWN: 0,
1604
- FATAL: 1,
1605
- ERROR: 2,
1606
- WARN: 3,
1607
- INFO: 4,
1608
- }.freeze
1609
-
1610
1693
  def self.warn msg
1611
1694
  log :WARN, msg
1612
1695
  end
@@ -1651,7 +1734,18 @@ module DEBUGGER__
1651
1734
 
1652
1735
  at_exit{
1653
1736
  trap(:SIGINT, :IGNORE)
1654
- Process.waitpid(child_pid)
1737
+
1738
+ # only check child process from its parent
1739
+ if Process.pid == parent_pid
1740
+ begin
1741
+ # sending a null signal to see if the child is still alive
1742
+ Process.kill(0, child_pid)
1743
+ # if the child is still alive, wait for it
1744
+ Process.waitpid(child_pid)
1745
+ rescue Errno::ESRCH
1746
+ # if the child process has died, do nothing
1747
+ end
1748
+ end
1655
1749
  }
1656
1750
  }
1657
1751
  child_hook = -> {
@@ -1683,8 +1777,36 @@ module DEBUGGER__
1683
1777
  end
1684
1778
  end
1685
1779
 
1686
- class ::Object
1687
- include ForkInterceptor
1780
+ module TrapInterceptor
1781
+ def trap sig, *command, &command_proc
1782
+ case sig&.to_sym
1783
+ when :INT, :SIGINT
1784
+ if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
1785
+ return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
1786
+ end
1787
+ end
1788
+
1789
+ super
1790
+ end
1791
+ end
1792
+
1793
+ if RUBY_VERSION >= '3.0.0'
1794
+ module ::Kernel
1795
+ prepend ForkInterceptor
1796
+ prepend TrapInterceptor
1797
+ end
1798
+ else
1799
+ class ::Object
1800
+ include ForkInterceptor
1801
+ include TrapInterceptor
1802
+ end
1803
+ end
1804
+
1805
+ module ::Kernel
1806
+ class << self
1807
+ prepend ForkInterceptor
1808
+ prepend TrapInterceptor
1809
+ end
1688
1810
  end
1689
1811
 
1690
1812
  module ::Process
@@ -1692,5 +1814,29 @@ module DEBUGGER__
1692
1814
  prepend ForkInterceptor
1693
1815
  end
1694
1816
  end
1817
+
1818
+ module ::Signal
1819
+ class << self
1820
+ prepend TrapInterceptor
1821
+ end
1822
+ end
1823
+ end
1824
+
1825
+ module Kernel
1826
+ def debugger pre: nil, do: nil
1827
+ return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
1828
+
1829
+ if pre || (do_expr = binding.local_variable_get(:do))
1830
+ cmds = ['binding.break', pre, do_expr]
1831
+ end
1832
+
1833
+ ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
1834
+ self
1835
+ end
1836
+ end
1837
+
1838
+ class Binding
1839
+ alias break debugger
1840
+ alias b debugger
1695
1841
  end
1696
1842
 
@@ -20,9 +20,11 @@ module DEBUGGER__
20
20
 
21
21
  class ThreadClient
22
22
  def self.current
23
- Thread.current[:DEBUGGER__ThreadClient] || begin
24
- tc = ::DEBUGGER__::SESSION.thread_client
25
- Thread.current[:DEBUGGER__ThreadClient] = tc
23
+ if thc = Thread.current[:DEBUGGER__ThreadClient]
24
+ thc
25
+ else
26
+ thc = SESSION.thread_client
27
+ Thread.current[:DEBUGGER__ThreadClient] = thc
26
28
  end
27
29
  end
28
30
 
@@ -115,7 +117,7 @@ module DEBUGGER__
115
117
  # TODO: there is waiting -> waiting
116
118
  # raise "#{mode} is given, but #{mode}" unless self.running?
117
119
  else
118
- raise
120
+ raise "unknown mode: #{mode}"
119
121
  end
120
122
 
121
123
  @mode = mode
@@ -138,7 +140,11 @@ module DEBUGGER__
138
140
  end
139
141
 
140
142
  def inspect
141
- "#<DBG:TC #{self.id}:#{@mode}@#{@thread.backtrace[-1]}>"
143
+ if bt = @thread.backtrace
144
+ "#<DBG:TC #{self.id}:#{@mode}@#{bt[-1]}>"
145
+ else # bt can be nil
146
+ "#<DBG:TC #{self.id}:#{@mode}>"
147
+ end
142
148
  end
143
149
 
144
150
  def to_s
@@ -194,10 +200,6 @@ module DEBUGGER__
194
200
  wait_next_action
195
201
  end
196
202
 
197
- def on_thread_begin th
198
- wait_reply [:thread_begin, th]
199
- end
200
-
201
203
  def on_load iseq, eval_src
202
204
  wait_reply [:load, iseq, eval_src]
203
205
  end
@@ -340,7 +342,8 @@ module DEBUGGER__
340
342
  begin
341
343
  @success_last_eval = false
342
344
 
343
- b = current_frame.eval_binding
345
+ b = current_frame&.eval_binding || TOPLEVEL_BINDING
346
+
344
347
  result = if b
345
348
  f, _l = b.source_location
346
349
  b.eval(src, "(rdbg)/#{f}")
@@ -659,18 +662,17 @@ module DEBUGGER__
659
662
 
660
663
  def wait_next_action_
661
664
  # assertions
662
- raise "@mode is #{@mode}" unless @mode == :waiting
665
+ raise "@mode is #{@mode}" if !waiting?
663
666
 
664
667
  unless SESSION.active?
665
668
  pp caller
666
669
  set_mode :running
667
670
  return
668
671
  end
669
- # SESSION.check_forked
670
672
 
671
673
  while true
672
674
  begin
673
- set_mode :waiting if @mode != :waiting
675
+ set_mode :waiting if !waiting?
674
676
  cmds = @q_cmd.pop
675
677
  # pp [self, cmds: cmds]
676
678
  break unless cmds
@@ -779,6 +781,13 @@ module DEBUGGER__
779
781
  end
780
782
  when :call
781
783
  result = frame_eval(eval_src)
784
+ when :irb
785
+ begin
786
+ result = frame_eval('binding.irb')
787
+ ensure
788
+ # workaround: https://github.com/ruby/debug/issues/308
789
+ Reline.prompt_proc = nil if defined? Reline
790
+ end
782
791
  when :display, :try_display
783
792
  failed_results = []
784
793
  eval_src.each_with_index{|src, i|