debug 1.2.2 → 1.3.1

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,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  # skip to load debugger for bundle exec
4
4
 
@@ -17,6 +17,7 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
17
17
  return
18
18
  end
19
19
 
20
+ require_relative 'frame_info'
20
21
  require_relative 'config'
21
22
  require_relative 'thread_client'
22
23
  require_relative 'source_repository'
@@ -25,6 +26,7 @@ require_relative 'tracer'
25
26
 
26
27
  # To prevent loading old lib/debug.rb in Ruby 2.6 to 3.0
27
28
  $LOADED_FEATURES << 'debug.rb'
29
+ $LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
28
30
  require 'debug' # invalidate the $LOADED_FEATURE cache
29
31
 
30
32
  require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
@@ -76,6 +78,8 @@ module DEBUGGER__
76
78
  class PostmortemError < RuntimeError; end
77
79
 
78
80
  class Session
81
+ attr_reader :intercepted_sigint_cmd, :process_group
82
+
79
83
  def initialize ui
80
84
  @ui = ui
81
85
  @sr = SourceRepository.new
@@ -98,11 +102,15 @@ module DEBUGGER__
98
102
  @thread_stopper = nil
99
103
  @intercept_trap_sigint = false
100
104
  @intercepted_sigint_cmd = 'DEFAULT'
105
+ @process_group = ProcessGroup.new
106
+ @subsession = nil
101
107
 
102
108
  @frame_map = {} # {id => [threadId, frame_depth]} for DAP
103
109
  @var_map = {1 => [:globals], } # {id => ...} for DAP
104
110
  @src_map = {} # {id => src}
105
111
 
112
+ @script_paths = [File.absolute_path($0)] # for CDP
113
+
106
114
  @tp_thread_begin = nil
107
115
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
108
116
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
@@ -183,95 +191,105 @@ module DEBUGGER__
183
191
 
184
192
  def session_server_main
185
193
  while evt = pop_event
186
- # variable `@internal_info` is only used for test
187
- tc, output, ev, @internal_info, *ev_args = evt
194
+ process_event evt
195
+ end
196
+ ensure
197
+ deactivate
198
+ end
199
+
200
+ def process_event evt
201
+ # variable `@internal_info` is only used for test
202
+ tc, output, ev, @internal_info, *ev_args = evt
203
+ output.each{|str| @ui.puts str} if ev != :suspend
204
+
205
+ case ev
206
+
207
+ when :thread_begin # special event, tc is nil
208
+ th = ev_args.shift
209
+ q = ev_args.shift
210
+ on_thread_begin th
211
+ q << true
212
+
213
+ when :init
214
+ wait_command_loop tc
215
+
216
+ when :load
217
+ iseq, src = ev_args
218
+ on_load iseq, src
219
+ @ui.event :load
220
+ tc << :continue
221
+
222
+ when :trace
223
+ trace_id, msg = ev_args
224
+ if t = @tracers.values.find{|t| t.object_id == trace_id}
225
+ t.puts msg
226
+ end
227
+ tc << :continue
228
+
229
+ when :suspend
230
+ enter_subsession if ev_args.first != :replay
188
231
  output.each{|str| @ui.puts str}
189
232
 
190
- case ev
233
+ case ev_args.first
234
+ when :breakpoint
235
+ bp, i = bp_index ev_args[1]
236
+ @ui.event :suspend_bp, i, bp, tc.id
237
+ when :trap
238
+ @ui.event :suspend_trap, sig = ev_args[1], tc.id
191
239
 
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
240
+ if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
241
+ @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
242
+ @ui.puts "`sigint` command execute it."
243
+ end
244
+ else
245
+ @ui.event :suspended, tc.id
246
+ end
197
247
 
198
- when :init
248
+ if @displays.empty?
199
249
  wait_command_loop tc
250
+ else
251
+ tc << [:eval, :display, @displays]
252
+ end
200
253
 
201
- when :load
202
- iseq, src = ev_args
203
- on_load iseq, src
204
- @ui.event :load
205
- tc << :continue
254
+ when :result
255
+ raise "[BUG] not in subsession" unless @subsession
206
256
 
207
- when :trace
208
- trace_id, msg = ev_args
209
- if t = @tracers.values.find{|t| t.object_id == trace_id}
210
- t.puts msg
211
- end
212
- tc << :continue
213
-
214
- when :suspend
215
- case ev_args.first
216
- when :breakpoint
217
- bp, i = bp_index ev_args[1]
218
- @ui.event :suspend_bp, i, bp, tc.id
219
- when :trap
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."
257
+ case ev_args.first
258
+ when :try_display
259
+ failed_results = ev_args[1]
260
+ if failed_results.size > 0
261
+ i, _msg = failed_results.last
262
+ if i+1 == @displays.size
263
+ @ui.puts "canceled: #{@displays.pop}"
225
264
  end
226
- else
227
- @ui.event :suspended, tc.id
228
- end
229
-
230
- if @displays.empty?
231
- stop_all_threads
232
- wait_command_loop tc
233
- else
234
- tc << [:eval, :display, @displays]
235
265
  end
236
266
 
237
- when :result
238
- case ev_args.first
239
- when :try_display
240
- failed_results = ev_args[1]
241
- if failed_results.size > 0
242
- i, _msg = failed_results.last
243
- if i+1 == @displays.size
244
- @ui.puts "canceled: #{@displays.pop}"
245
- end
246
- end
247
- stop_all_threads
248
-
249
- when :method_breakpoint, :watch_breakpoint
250
- bp = ev_args[1]
251
- if bp
252
- add_bp(bp)
253
- show_bps bp
254
- else
255
- # can't make a bp
256
- end
257
- when :trace_pass
258
- obj_id = ev_args[1]
259
- obj_inspect = ev_args[2]
260
- opt = ev_args[3]
261
- add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
267
+ when :method_breakpoint, :watch_breakpoint
268
+ bp = ev_args[1]
269
+ if bp
270
+ add_bp(bp)
271
+ show_bps bp
262
272
  else
263
- # ignore
273
+ # can't make a bp
264
274
  end
275
+ when :trace_pass
276
+ obj_id = ev_args[1]
277
+ obj_inspect = ev_args[2]
278
+ opt = ev_args[3]
279
+ add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
280
+ else
281
+ # ignore
282
+ end
265
283
 
266
- wait_command_loop tc
284
+ wait_command_loop tc
267
285
 
268
- when :dap_result
269
- dap_event ev_args # server.rb
270
- wait_command_loop tc
271
- end
286
+ when :dap_result
287
+ dap_event ev_args # server.rb
288
+ wait_command_loop tc
289
+ when :cdp_result
290
+ cdp_event ev_args
291
+ wait_command_loop tc
272
292
  end
273
- ensure
274
- deactivate
275
293
  end
276
294
 
277
295
  def add_preset_commands name, cmds, kick: true, continue: true
@@ -322,6 +340,8 @@ module DEBUGGER__
322
340
  def prompt
323
341
  if @postmortem
324
342
  '(rdbg:postmortem) '
343
+ elsif @process_group.multi?
344
+ "(rdbg@#{process_info}) "
325
345
  else
326
346
  '(rdbg) '
327
347
  end
@@ -333,8 +353,7 @@ module DEBUGGER__
333
353
  if @preset_command.auto_continue
334
354
  @preset_command = nil
335
355
 
336
- @tc << :continue
337
- restart_all_threads
356
+ leave_subsession :continue
338
357
  return
339
358
  else
340
359
  @preset_command = nil
@@ -353,7 +372,7 @@ module DEBUGGER__
353
372
  when String
354
373
  process_command line
355
374
  when Hash
356
- process_dap_request line # defined in server.rb
375
+ process_protocol_request line # defined in server.rb
357
376
  else
358
377
  raise "unexpected input: #{line.inspect}"
359
378
  end
@@ -409,16 +428,14 @@ module DEBUGGER__
409
428
  # * Resume the program.
410
429
  when 'c', 'continue'
411
430
  cancel_auto_continue
412
- @tc << :continue
413
- restart_all_threads
431
+ leave_subsession :continue
414
432
 
415
433
  # * `q[uit]` or `Ctrl-D`
416
434
  # * Finish debugger (with the debuggee process on non-remote debugging).
417
435
  when 'q', 'quit'
418
436
  if ask 'Really quit?'
419
437
  @ui.quit arg.to_i
420
- @tc << :continue
421
- restart_all_threads
438
+ leave_subsession :continue
422
439
  else
423
440
  return :retry
424
441
  end
@@ -427,7 +444,7 @@ module DEBUGGER__
427
444
  # * Same as q[uit] but without the confirmation prompt.
428
445
  when 'q!', 'quit!'
429
446
  @ui.quit arg.to_i
430
- restart_all_threads
447
+ leave_subsession nil
431
448
 
432
449
  # * `kill`
433
450
  # * Stop the debuggee process with `Kernal#exit!`.
@@ -457,8 +474,7 @@ module DEBUGGER__
457
474
  cmd.call
458
475
  end
459
476
 
460
- @tc << :continue
461
- restart_all_threads
477
+ leave_subsession :continue
462
478
 
463
479
  rescue Exception => e
464
480
  @ui.puts "Exception: #{e}"
@@ -722,8 +738,8 @@ module DEBUGGER__
722
738
  if ask "clear all?", 'N'
723
739
  @displays.clear
724
740
  end
741
+ return :retry
725
742
  end
726
- return :retry
727
743
 
728
744
  ### Frame control
729
745
 
@@ -922,6 +938,36 @@ module DEBUGGER__
922
938
  end
923
939
  return :retry
924
940
 
941
+ # * `open`
942
+ # * open debuggee port on UNIX domain socket and wait for attaching.
943
+ # * Note that `open` command is EXPERIMENTAL.
944
+ # * `open [<host>:]<port>`
945
+ # * open debuggee port on TCP/IP with given `[<host>:]<port>` and wait for attaching.
946
+ # * `open vscode`
947
+ # * open debuggee port for VSCode and launch VSCode if available.
948
+ # * `open chrome`
949
+ # * open debuggee port for Chrome and wait for attaching.
950
+ when 'open'
951
+ case arg&.downcase
952
+ when '', nil
953
+ repl_open_unix
954
+ when 'vscode'
955
+ repl_open_vscode
956
+ when /\A(.+):(\d+)\z/
957
+ repl_open_tcp $1, $2.to_i
958
+ when /\A(\d+)z/
959
+ repl_open_tcp nil, $1.to_i
960
+ when 'tcp'
961
+ repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
962
+ when 'chrome', 'cdp'
963
+ CONFIG[:open_frontend] = 'chrome'
964
+ repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
965
+ else
966
+ raise "Unknown arg: #{arg}"
967
+ end
968
+
969
+ return :retry
970
+
925
971
  ### Help
926
972
 
927
973
  # * `h[elp]`
@@ -967,14 +1013,38 @@ module DEBUGGER__
967
1013
  return :retry
968
1014
  end
969
1015
 
1016
+ def repl_open_setup
1017
+ @tp_thread_begin.disable
1018
+ @ui.activate self
1019
+ if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
1020
+ thc.is_management
1021
+ end
1022
+ @tp_thread_begin.enable
1023
+ end
1024
+
1025
+ def repl_open_tcp host, port, **kw
1026
+ DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
1027
+ repl_open_setup
1028
+ end
1029
+
1030
+ def repl_open_unix
1031
+ DEBUGGER__.open_unix nonstop: true
1032
+ repl_open_setup
1033
+ end
1034
+
1035
+ def repl_open_vscode
1036
+ CONFIG[:open_frontend] = 'vscode'
1037
+ repl_open_unix
1038
+ end
1039
+
970
1040
  def step_command type, arg
971
1041
  case arg
972
- when nil
973
- @tc << [:step, type]
974
- restart_all_threads
975
- when /\A\d+\z/
976
- @tc << [:step, type, arg.to_i]
977
- restart_all_threads
1042
+ when nil, /\A\d+\z/
1043
+ if type == :in && @tc.recorder&.replaying?
1044
+ @tc << [:step, type, arg&.to_i]
1045
+ else
1046
+ leave_subsession [:step, type, arg&.to_i]
1047
+ end
978
1048
  when /\Aback\z/, /\Areset\z/
979
1049
  if type != :in
980
1050
  @ui.puts "only `step #{arg}` is supported."
@@ -1288,10 +1358,15 @@ module DEBUGGER__
1288
1358
  end
1289
1359
 
1290
1360
  def setup_threads
1361
+ prev_clients = @th_clients || {}
1291
1362
  @th_clients = {}
1292
1363
 
1293
1364
  Thread.list.each{|th|
1294
- thread_client_create(th)
1365
+ if tc = prev_clients[th]
1366
+ @th_clients[th] = tc
1367
+ else
1368
+ thread_client_create(th)
1369
+ end
1295
1370
  }
1296
1371
  end
1297
1372
 
@@ -1375,7 +1450,30 @@ module DEBUGGER__
1375
1450
  next if @tc == tc
1376
1451
  tc << :continue
1377
1452
  }
1453
+ end
1454
+
1455
+ private def enter_subsession
1456
+ raise "already in subsession" if @subsession
1457
+ @subsession = true
1458
+ stop_all_threads
1459
+ @process_group.lock
1460
+ DEBUGGER__.info "enter_subsession"
1461
+ end
1462
+
1463
+ private def leave_subsession type
1464
+ DEBUGGER__.info "leave_subsession"
1465
+ @process_group.unlock
1466
+ restart_all_threads
1467
+ @tc << type if type
1378
1468
  @tc = nil
1469
+ @subsession = false
1470
+ rescue Exception => e
1471
+ STDERR.puts [e, e.backtrace].inspect
1472
+ raise
1473
+ end
1474
+
1475
+ def in_subsession?
1476
+ @subsession
1379
1477
  end
1380
1478
 
1381
1479
  ## event
@@ -1501,8 +1599,6 @@ module DEBUGGER__
1501
1599
  prev
1502
1600
  end
1503
1601
 
1504
- attr_reader :intercepted_sigint_cmd
1505
-
1506
1602
  def intercept_trap_sigint?
1507
1603
  @intercept_trap_sigint
1508
1604
  end
@@ -1519,6 +1615,159 @@ module DEBUGGER__
1519
1615
  @intercept_trap_sigint = true
1520
1616
  @intercepted_sigint_cmd = prev
1521
1617
  end
1618
+
1619
+ def intercept_trap_sigint_end
1620
+ @intercept_trap_sigint = false
1621
+ prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, nil
1622
+ prev
1623
+ end
1624
+
1625
+ def process_info
1626
+ if @process_group.multi?
1627
+ "#{$0}\##{Process.pid}"
1628
+ end
1629
+ end
1630
+
1631
+ def before_fork need_lock = true
1632
+ if need_lock
1633
+ @process_group.multi_process!
1634
+ end
1635
+ end
1636
+
1637
+ def after_fork_parent
1638
+ @ui.after_fork_parent
1639
+ end
1640
+ end
1641
+
1642
+ class ProcessGroup
1643
+ def initialize
1644
+ @lock_file = nil
1645
+ end
1646
+
1647
+ def locked?
1648
+ true
1649
+ end
1650
+
1651
+ def trylock
1652
+ true
1653
+ end
1654
+
1655
+ def lock
1656
+ true
1657
+ end
1658
+
1659
+ def unlock
1660
+ true
1661
+ end
1662
+
1663
+ def sync
1664
+ yield
1665
+ end
1666
+
1667
+ def after_fork
1668
+ end
1669
+
1670
+ def multi?
1671
+ @lock_file
1672
+ end
1673
+
1674
+ def multi_process!
1675
+ require 'tempfile'
1676
+ @lock_tempfile = Tempfile.open("ruby-debug-lock-")
1677
+ @lock_tempfile.close
1678
+ extend MultiProcessGroup
1679
+ end
1680
+ end
1681
+
1682
+ module MultiProcessGroup
1683
+ def multi_process!
1684
+ end
1685
+
1686
+ def after_fork child: true
1687
+ if child || !@lock_file
1688
+ @m = Mutex.new
1689
+ @lock_level = 0
1690
+ @lock_file = open(@lock_tempfile.path, 'w')
1691
+ end
1692
+ end
1693
+
1694
+ def info msg
1695
+ DEBUGGER__.info "#{msg} (#{@lock_level})" # #{caller.first(1).map{|bt| bt.sub(__dir__, '')}}"
1696
+ end
1697
+
1698
+ def locked?
1699
+ # DEBUGGER__.info "locked? #{@lock_level}"
1700
+ @lock_level > 0
1701
+ end
1702
+
1703
+ private def lock_level_up
1704
+ raise unless @m.owned?
1705
+ @lock_level += 1
1706
+ end
1707
+
1708
+ private def lock_level_down
1709
+ raise unless @m.owned?
1710
+ raise "@lock_level underflow: #{@lock_level}" if @lock_level < 1
1711
+ @lock_level -= 1
1712
+ end
1713
+
1714
+ private def trylock
1715
+ @m.synchronize do
1716
+ if locked?
1717
+ lock_level_up
1718
+ info "Try lock, already locked"
1719
+ true
1720
+ else
1721
+ case r = @lock_file.flock(File::LOCK_EX | File::LOCK_NB)
1722
+ when 0
1723
+ lock_level_up
1724
+ info "Try lock with file: success"
1725
+ true
1726
+ when false
1727
+ info "Try lock with file: failed"
1728
+ false
1729
+ else
1730
+ raise "unknown flock result: #{r.inspect}"
1731
+ end
1732
+ end
1733
+ end
1734
+ end
1735
+
1736
+ def lock
1737
+ unless trylock
1738
+ @m.synchronize do
1739
+ if locked?
1740
+ lock_level_up
1741
+ else
1742
+ info "Lock: block"
1743
+ @lock_file.flock(File::LOCK_EX)
1744
+ lock_level_up
1745
+ end
1746
+ end
1747
+
1748
+ info "Lock: success"
1749
+ end
1750
+ end
1751
+
1752
+ def unlock
1753
+ @m.synchronize do
1754
+ raise "lock file is not opened (#{@lock_file.inspect})" if @lock_file.closed?
1755
+ lock_level_down
1756
+ @lock_file.flock(File::LOCK_UN) unless locked?
1757
+ info "Unlocked"
1758
+ end
1759
+ end
1760
+
1761
+ def sync &b
1762
+ info "sync"
1763
+
1764
+ lock
1765
+ begin
1766
+ b.call if b
1767
+ ensure
1768
+ unlock
1769
+ end
1770
+ end
1522
1771
  end
1523
1772
 
1524
1773
  class UI_Base
@@ -1576,8 +1825,8 @@ module DEBUGGER__
1576
1825
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
1577
1826
  CONFIG.set_config(**kw)
1578
1827
 
1579
- if port
1580
- open_tcp host: host, port: port, nonstop: nonstop
1828
+ if port || CONFIG[:open_frontend] == 'chrome'
1829
+ open_tcp host: host, port: (port || 0), nonstop: nonstop
1581
1830
  else
1582
1831
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
1583
1832
  end
@@ -1699,15 +1948,24 @@ module DEBUGGER__
1699
1948
  end
1700
1949
 
1701
1950
  def self.log level, msg
1951
+ @logfile = STDERR unless defined? @logfile
1952
+
1702
1953
  lv = LOG_LEVELS[level]
1703
1954
  config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
1704
1955
 
1956
+ if defined? SESSION
1957
+ pi = SESSION.process_info
1958
+ process_info = pi ? "[#{pi}]" : nil
1959
+ end
1960
+
1705
1961
  if lv <= config_lv
1706
1962
  if level == :WARN
1707
1963
  # :WARN on debugger is general information
1708
- STDERR.puts "DEBUGGER: #{msg}"
1964
+ @logfile.puts "DEBUGGER#{process_info}: #{msg}"
1965
+ @logfile.flush
1709
1966
  else
1710
- STDERR.puts "DEBUGGER (#{level}): #{msg}"
1967
+ @logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
1968
+ @logfile.flush
1711
1969
  end
1712
1970
  end
1713
1971
  end
@@ -1716,8 +1974,19 @@ module DEBUGGER__
1716
1974
  def fork(&given_block)
1717
1975
  return super unless defined?(SESSION) && SESSION.active?
1718
1976
 
1977
+ unless fork_mode = CONFIG[:fork_mode]
1978
+ if CONFIG[:parent_on_fork]
1979
+ fork_mode = :parent
1980
+ else
1981
+ fork_mode = :both
1982
+ end
1983
+ end
1984
+
1985
+ parent_pid = Process.pid
1986
+
1719
1987
  # before fork
1720
- if CONFIG[:parent_on_fork]
1988
+ case fork_mode
1989
+ when :parent
1721
1990
  parent_hook = -> child_pid {
1722
1991
  # Do nothing
1723
1992
  }
@@ -1725,31 +1994,28 @@ module DEBUGGER__
1725
1994
  DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
1726
1995
  SESSION.deactivate
1727
1996
  }
1728
- else
1729
- parent_pid = Process.pid
1997
+ when :child
1998
+ SESSION.before_fork false
1730
1999
 
1731
2000
  parent_hook = -> child_pid {
1732
2001
  DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
2002
+ SESSION.after_fork_parent
1733
2003
  SESSION.deactivate
2004
+ }
2005
+ child_hook = -> {
2006
+ DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2007
+ SESSION.activate on_fork: true
2008
+ }
2009
+ when :both
2010
+ SESSION.before_fork
1734
2011
 
1735
- at_exit{
1736
- trap(:SIGINT, :IGNORE)
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
1749
- }
2012
+ parent_hook = -> child_pid {
2013
+ SESSION.process_group.after_fork
2014
+ SESSION.after_fork_parent
1750
2015
  }
1751
2016
  child_hook = -> {
1752
2017
  DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2018
+ SESSION.process_group.after_fork child: true
1753
2019
  SESSION.activate on_fork: true
1754
2020
  }
1755
2021
  end
@@ -1823,20 +2089,21 @@ module DEBUGGER__
1823
2089
  end
1824
2090
 
1825
2091
  module Kernel
1826
- def debugger pre: nil, do: nil
2092
+ def debugger pre: nil, do: nil, up_level: 0
1827
2093
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
1828
2094
 
1829
2095
  if pre || (do_expr = binding.local_variable_get(:do))
1830
2096
  cmds = ['binding.break', pre, do_expr]
1831
2097
  end
1832
2098
 
1833
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
2099
+ loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
1834
2100
  self
1835
2101
  end
2102
+
2103
+ alias bb debugger if ENV['RUBY_DEBUG_BB']
1836
2104
  end
1837
2105
 
1838
2106
  class Binding
1839
2107
  alias break debugger
1840
2108
  alias b debugger
1841
2109
  end
1842
-