debug 1.2.4 → 1.3.0

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