debug 1.2.4 → 1.3.0

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'
@@ -77,6 +78,8 @@ module DEBUGGER__
77
78
  class PostmortemError < RuntimeError; end
78
79
 
79
80
  class Session
81
+ attr_reader :intercepted_sigint_cmd, :process_group
82
+
80
83
  def initialize ui
81
84
  @ui = ui
82
85
  @sr = SourceRepository.new
@@ -99,11 +102,15 @@ module DEBUGGER__
99
102
  @thread_stopper = nil
100
103
  @intercept_trap_sigint = false
101
104
  @intercepted_sigint_cmd = 'DEFAULT'
105
+ @process_group = ProcessGroup.new
106
+ @subsession = nil
102
107
 
103
108
  @frame_map = {} # {id => [threadId, frame_depth]} for DAP
104
109
  @var_map = {1 => [:globals], } # {id => ...} for DAP
105
110
  @src_map = {} # {id => src}
106
111
 
112
+ @script_paths = [File.absolute_path($0)] # for CDP
113
+
107
114
  @tp_thread_begin = nil
108
115
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
109
116
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
@@ -184,95 +191,105 @@ module DEBUGGER__
184
191
 
185
192
  def session_server_main
186
193
  while evt = pop_event
187
- # variable `@internal_info` is only used for test
188
- 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
189
231
  output.each{|str| @ui.puts str}
190
232
 
191
- 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
192
239
 
193
- when :thread_begin # special event, tc is nil
194
- th = ev_args.shift
195
- q = ev_args.shift
196
- on_thread_begin th
197
- 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
198
247
 
199
- when :init
248
+ if @displays.empty?
200
249
  wait_command_loop tc
250
+ else
251
+ tc << [:eval, :display, @displays]
252
+ end
201
253
 
202
- when :load
203
- iseq, src = ev_args
204
- on_load iseq, src
205
- @ui.event :load
206
- tc << :continue
254
+ when :result
255
+ raise "[BUG] not in subsession" unless @subsession
207
256
 
208
- when :trace
209
- trace_id, msg = ev_args
210
- if t = @tracers.values.find{|t| t.object_id == trace_id}
211
- t.puts msg
212
- end
213
- tc << :continue
214
-
215
- when :suspend
216
- case ev_args.first
217
- when :breakpoint
218
- bp, i = bp_index ev_args[1]
219
- @ui.event :suspend_bp, i, bp, tc.id
220
- when :trap
221
- @ui.event :suspend_trap, sig = ev_args[1], tc.id
222
-
223
- if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
224
- @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
225
- @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}"
226
264
  end
227
- else
228
- @ui.event :suspended, tc.id
229
- end
230
-
231
- if @displays.empty?
232
- stop_all_threads
233
- wait_command_loop tc
234
- else
235
- tc << [:eval, :display, @displays]
236
265
  end
237
266
 
238
- when :result
239
- case ev_args.first
240
- when :try_display
241
- failed_results = ev_args[1]
242
- if failed_results.size > 0
243
- i, _msg = failed_results.last
244
- if i+1 == @displays.size
245
- @ui.puts "canceled: #{@displays.pop}"
246
- end
247
- end
248
- stop_all_threads
249
-
250
- when :method_breakpoint, :watch_breakpoint
251
- bp = ev_args[1]
252
- if bp
253
- add_bp(bp)
254
- show_bps bp
255
- else
256
- # can't make a bp
257
- end
258
- when :trace_pass
259
- obj_id = ev_args[1]
260
- obj_inspect = ev_args[2]
261
- opt = ev_args[3]
262
- 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
263
272
  else
264
- # ignore
273
+ # can't make a bp
265
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
266
283
 
267
- wait_command_loop tc
284
+ wait_command_loop tc
268
285
 
269
- when :dap_result
270
- dap_event ev_args # server.rb
271
- wait_command_loop tc
272
- 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
273
292
  end
274
- ensure
275
- deactivate
276
293
  end
277
294
 
278
295
  def add_preset_commands name, cmds, kick: true, continue: true
@@ -323,6 +340,8 @@ module DEBUGGER__
323
340
  def prompt
324
341
  if @postmortem
325
342
  '(rdbg:postmortem) '
343
+ elsif @process_group.multi?
344
+ "(rdbg@#{process_info}) "
326
345
  else
327
346
  '(rdbg) '
328
347
  end
@@ -334,8 +353,7 @@ module DEBUGGER__
334
353
  if @preset_command.auto_continue
335
354
  @preset_command = nil
336
355
 
337
- @tc << :continue
338
- restart_all_threads
356
+ leave_subsession :continue
339
357
  return
340
358
  else
341
359
  @preset_command = nil
@@ -354,7 +372,7 @@ module DEBUGGER__
354
372
  when String
355
373
  process_command line
356
374
  when Hash
357
- process_dap_request line # defined in server.rb
375
+ process_protocol_request line # defined in server.rb
358
376
  else
359
377
  raise "unexpected input: #{line.inspect}"
360
378
  end
@@ -410,16 +428,14 @@ module DEBUGGER__
410
428
  # * Resume the program.
411
429
  when 'c', 'continue'
412
430
  cancel_auto_continue
413
- @tc << :continue
414
- restart_all_threads
431
+ leave_subsession :continue
415
432
 
416
433
  # * `q[uit]` or `Ctrl-D`
417
434
  # * Finish debugger (with the debuggee process on non-remote debugging).
418
435
  when 'q', 'quit'
419
436
  if ask 'Really quit?'
420
437
  @ui.quit arg.to_i
421
- @tc << :continue
422
- restart_all_threads
438
+ leave_subsession :continue
423
439
  else
424
440
  return :retry
425
441
  end
@@ -428,7 +444,7 @@ module DEBUGGER__
428
444
  # * Same as q[uit] but without the confirmation prompt.
429
445
  when 'q!', 'quit!'
430
446
  @ui.quit arg.to_i
431
- restart_all_threads
447
+ leave_subsession nil
432
448
 
433
449
  # * `kill`
434
450
  # * Stop the debuggee process with `Kernal#exit!`.
@@ -458,8 +474,7 @@ module DEBUGGER__
458
474
  cmd.call
459
475
  end
460
476
 
461
- @tc << :continue
462
- restart_all_threads
477
+ leave_subsession :continue
463
478
 
464
479
  rescue Exception => e
465
480
  @ui.puts "Exception: #{e}"
@@ -723,8 +738,8 @@ module DEBUGGER__
723
738
  if ask "clear all?", 'N'
724
739
  @displays.clear
725
740
  end
741
+ return :retry
726
742
  end
727
- return :retry
728
743
 
729
744
  ### Frame control
730
745
 
@@ -923,6 +938,36 @@ module DEBUGGER__
923
938
  end
924
939
  return :retry
925
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
+
926
971
  ### Help
927
972
 
928
973
  # * `h[elp]`
@@ -968,14 +1013,38 @@ module DEBUGGER__
968
1013
  return :retry
969
1014
  end
970
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
+
971
1040
  def step_command type, arg
972
1041
  case arg
973
- when nil
974
- @tc << [:step, type]
975
- restart_all_threads
976
- when /\A\d+\z/
977
- @tc << [:step, type, arg.to_i]
978
- 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
979
1048
  when /\Aback\z/, /\Areset\z/
980
1049
  if type != :in
981
1050
  @ui.puts "only `step #{arg}` is supported."
@@ -1289,10 +1358,15 @@ module DEBUGGER__
1289
1358
  end
1290
1359
 
1291
1360
  def setup_threads
1361
+ prev_clients = @th_clients || {}
1292
1362
  @th_clients = {}
1293
1363
 
1294
1364
  Thread.list.each{|th|
1295
- thread_client_create(th)
1365
+ if tc = prev_clients[th]
1366
+ @th_clients[th] = tc
1367
+ else
1368
+ thread_client_create(th)
1369
+ end
1296
1370
  }
1297
1371
  end
1298
1372
 
@@ -1376,7 +1450,30 @@ module DEBUGGER__
1376
1450
  next if @tc == tc
1377
1451
  tc << :continue
1378
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
1379
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
1380
1477
  end
1381
1478
 
1382
1479
  ## event
@@ -1502,8 +1599,6 @@ module DEBUGGER__
1502
1599
  prev
1503
1600
  end
1504
1601
 
1505
- attr_reader :intercepted_sigint_cmd
1506
-
1507
1602
  def intercept_trap_sigint?
1508
1603
  @intercept_trap_sigint
1509
1604
  end
@@ -1520,6 +1615,172 @@ module DEBUGGER__
1520
1615
  @intercept_trap_sigint = true
1521
1616
  @intercepted_sigint_cmd = prev
1522
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
+ parent_pid = Process.pid
1639
+ at_exit{
1640
+ @intercept_trap_sigint = false
1641
+ trap(:SIGINT, :IGNORE)
1642
+
1643
+ if Process.pid == parent_pid
1644
+ # only check child process from its parent
1645
+ begin
1646
+ # wait for all child processes to keep terminal
1647
+ loop{ Process.waitpid }
1648
+ rescue Errno::ESRCH, Errno::ECHILD
1649
+ end
1650
+ end
1651
+ }
1652
+ end
1653
+ end
1654
+
1655
+ class ProcessGroup
1656
+ def initialize
1657
+ @lock_file = nil
1658
+ end
1659
+
1660
+ def locked?
1661
+ true
1662
+ end
1663
+
1664
+ def trylock
1665
+ true
1666
+ end
1667
+
1668
+ def lock
1669
+ true
1670
+ end
1671
+
1672
+ def unlock
1673
+ true
1674
+ end
1675
+
1676
+ def sync
1677
+ yield
1678
+ end
1679
+
1680
+ def after_fork
1681
+ end
1682
+
1683
+ def multi?
1684
+ @lock_file
1685
+ end
1686
+
1687
+ def multi_process!
1688
+ require 'tempfile'
1689
+ @lock_tempfile = Tempfile.open("ruby-debug-lock-")
1690
+ @lock_tempfile.close
1691
+ extend MultiProcessGroup
1692
+ end
1693
+ end
1694
+
1695
+ module MultiProcessGroup
1696
+ def multi_process!
1697
+ end
1698
+
1699
+ def after_fork child: true
1700
+ if child || !@lock_file
1701
+ @m = Mutex.new
1702
+ @lock_level = 0
1703
+ @lock_file = open(@lock_tempfile.path, 'w')
1704
+ end
1705
+ end
1706
+
1707
+ def info msg
1708
+ DEBUGGER__.info "#{msg} (#{@lock_level})" # #{caller.first(1).map{|bt| bt.sub(__dir__, '')}}"
1709
+ end
1710
+
1711
+ def locked?
1712
+ # DEBUGGER__.info "locked? #{@lock_level}"
1713
+ @lock_level > 0
1714
+ end
1715
+
1716
+ private def lock_level_up
1717
+ raise unless @m.owned?
1718
+ @lock_level += 1
1719
+ end
1720
+
1721
+ private def lock_level_down
1722
+ raise unless @m.owned?
1723
+ raise "@lock_level underflow: #{@lock_level}" if @lock_level < 1
1724
+ @lock_level -= 1
1725
+ end
1726
+
1727
+ private def trylock
1728
+ @m.synchronize do
1729
+ if locked?
1730
+ lock_level_up
1731
+ info "Try lock, already locked"
1732
+ true
1733
+ else
1734
+ case r = @lock_file.flock(File::LOCK_EX | File::LOCK_NB)
1735
+ when 0
1736
+ lock_level_up
1737
+ info "Try lock with file: success"
1738
+ true
1739
+ when false
1740
+ info "Try lock with file: failed"
1741
+ false
1742
+ else
1743
+ raise "unknown flock result: #{r.inspect}"
1744
+ end
1745
+ end
1746
+ end
1747
+ end
1748
+
1749
+ def lock
1750
+ unless trylock
1751
+ @m.synchronize do
1752
+ if locked?
1753
+ lock_level_up
1754
+ else
1755
+ info "Lock: block"
1756
+ @lock_file.flock(File::LOCK_EX)
1757
+ lock_level_up
1758
+ end
1759
+ end
1760
+
1761
+ info "Lock: success"
1762
+ end
1763
+ end
1764
+
1765
+ def unlock
1766
+ @m.synchronize do
1767
+ raise "lock file is not opened (#{@lock_file.inspect})" if @lock_file.closed?
1768
+ lock_level_down
1769
+ @lock_file.flock(File::LOCK_UN) unless locked?
1770
+ info "Unlocked"
1771
+ end
1772
+ end
1773
+
1774
+ def sync &b
1775
+ info "sync"
1776
+
1777
+ begin
1778
+ lock
1779
+ b.call if b
1780
+ ensure
1781
+ unlock
1782
+ end
1783
+ end
1523
1784
  end
1524
1785
 
1525
1786
  class UI_Base
@@ -1577,8 +1838,8 @@ module DEBUGGER__
1577
1838
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
1578
1839
  CONFIG.set_config(**kw)
1579
1840
 
1580
- if port
1581
- open_tcp host: host, port: port, nonstop: nonstop
1841
+ if port || CONFIG[:open_frontend] == 'chrome'
1842
+ open_tcp host: host, port: (port || 0), nonstop: nonstop
1582
1843
  else
1583
1844
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
1584
1845
  end
@@ -1700,15 +1961,24 @@ module DEBUGGER__
1700
1961
  end
1701
1962
 
1702
1963
  def self.log level, msg
1964
+ @logfile = STDERR unless defined? @logfile
1965
+
1703
1966
  lv = LOG_LEVELS[level]
1704
1967
  config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
1705
1968
 
1969
+ if defined? SESSION
1970
+ pi = SESSION.process_info
1971
+ process_info = pi ? "[#{pi}]" : nil
1972
+ end
1973
+
1706
1974
  if lv <= config_lv
1707
1975
  if level == :WARN
1708
1976
  # :WARN on debugger is general information
1709
- STDERR.puts "DEBUGGER: #{msg}"
1977
+ @logfile.puts "DEBUGGER#{process_info}: #{msg}"
1978
+ @logfile.flush
1710
1979
  else
1711
- STDERR.puts "DEBUGGER (#{level}): #{msg}"
1980
+ @logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
1981
+ @logfile.flush
1712
1982
  end
1713
1983
  end
1714
1984
  end
@@ -1717,8 +1987,19 @@ module DEBUGGER__
1717
1987
  def fork(&given_block)
1718
1988
  return super unless defined?(SESSION) && SESSION.active?
1719
1989
 
1990
+ unless fork_mode = CONFIG[:fork_mode]
1991
+ if CONFIG[:parent_on_fork]
1992
+ fork_mode = :parent
1993
+ else
1994
+ fork_mode = :both
1995
+ end
1996
+ end
1997
+
1998
+ parent_pid = Process.pid
1999
+
1720
2000
  # before fork
1721
- if CONFIG[:parent_on_fork]
2001
+ case fork_mode
2002
+ when :parent
1722
2003
  parent_hook = -> child_pid {
1723
2004
  # Do nothing
1724
2005
  }
@@ -1726,31 +2007,28 @@ module DEBUGGER__
1726
2007
  DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
1727
2008
  SESSION.deactivate
1728
2009
  }
1729
- else
1730
- parent_pid = Process.pid
2010
+ when :child
2011
+ SESSION.before_fork false
1731
2012
 
1732
2013
  parent_hook = -> child_pid {
1733
2014
  DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
1734
2015
  SESSION.deactivate
2016
+ SESSION.after_fork_parent
2017
+ }
2018
+ child_hook = -> {
2019
+ DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2020
+ SESSION.activate on_fork: true
2021
+ }
2022
+ when :both
2023
+ SESSION.before_fork
1735
2024
 
1736
- at_exit{
1737
- trap(:SIGINT, :IGNORE)
1738
-
1739
- # only check child process from its parent
1740
- if Process.pid == parent_pid
1741
- begin
1742
- # sending a null signal to see if the child is still alive
1743
- Process.kill(0, child_pid)
1744
- # if the child is still alive, wait for it
1745
- Process.waitpid(child_pid)
1746
- rescue Errno::ESRCH
1747
- # if the child process has died, do nothing
1748
- end
1749
- end
1750
- }
2025
+ parent_hook = -> child_pid {
2026
+ SESSION.process_group.after_fork
2027
+ SESSION.after_fork_parent
1751
2028
  }
1752
2029
  child_hook = -> {
1753
2030
  DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2031
+ SESSION.process_group.after_fork child: true
1754
2032
  SESSION.activate on_fork: true
1755
2033
  }
1756
2034
  end
@@ -1824,14 +2102,14 @@ module DEBUGGER__
1824
2102
  end
1825
2103
 
1826
2104
  module Kernel
1827
- def debugger pre: nil, do: nil
2105
+ def debugger pre: nil, do: nil, up_level: 0
1828
2106
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
1829
2107
 
1830
2108
  if pre || (do_expr = binding.local_variable_get(:do))
1831
2109
  cmds = ['binding.break', pre, do_expr]
1832
2110
  end
1833
2111
 
1834
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
2112
+ loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
1835
2113
  self
1836
2114
  end
1837
2115
  end
@@ -1840,4 +2118,3 @@ class Binding
1840
2118
  alias break debugger
1841
2119
  alias b debugger
1842
2120
  end
1843
-