debug 1.0.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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|