debug 1.2.4 → 1.3.3

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
@@ -88,7 +91,7 @@ module DEBUGGER__
88
91
  # [:check, expr] => CheckBreakpoint
89
92
  #
90
93
  @tracers = {}
91
- @th_clients = nil # {Thread => ThreadClient}
94
+ @th_clients = {} # {Thread => ThreadClient}
92
95
  @q_evt = Queue.new
93
96
  @displays = []
94
97
  @tc = nil
@@ -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
@@ -1458,6 +1555,36 @@ module DEBUGGER__
1458
1555
  @postmortem = false
1459
1556
  end
1460
1557
 
1558
+ def capture_exception_frames *exclude_path
1559
+ postmortem_hook = TracePoint.new(:raise){|tp|
1560
+ exc = tp.raised_exception
1561
+ frames = DEBUGGER__.capture_frames(__dir__)
1562
+
1563
+ exclude_path.each{|ex|
1564
+ if Regexp === ex
1565
+ frames.delete_if{|e| ex =~ e.path}
1566
+ else
1567
+ frames.delete_if{|e| e.path.start_with? ex.to_s}
1568
+ end
1569
+ }
1570
+ exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
1571
+ }
1572
+ postmortem_hook.enable
1573
+
1574
+ begin
1575
+ yield
1576
+ nil
1577
+ rescue Exception => e
1578
+ if e.instance_variable_defined? :@__debugger_postmortem_frames
1579
+ e
1580
+ else
1581
+ raise
1582
+ end
1583
+ ensure
1584
+ postmortem_hook.disable
1585
+ end
1586
+ end
1587
+
1461
1588
  def postmortem=(is_enable)
1462
1589
  if is_enable
1463
1590
  unless @postmortem_hook
@@ -1502,8 +1629,6 @@ module DEBUGGER__
1502
1629
  prev
1503
1630
  end
1504
1631
 
1505
- attr_reader :intercepted_sigint_cmd
1506
-
1507
1632
  def intercept_trap_sigint?
1508
1633
  @intercept_trap_sigint
1509
1634
  end
@@ -1520,6 +1645,159 @@ module DEBUGGER__
1520
1645
  @intercept_trap_sigint = true
1521
1646
  @intercepted_sigint_cmd = prev
1522
1647
  end
1648
+
1649
+ def intercept_trap_sigint_end
1650
+ @intercept_trap_sigint = false
1651
+ prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, nil
1652
+ prev
1653
+ end
1654
+
1655
+ def process_info
1656
+ if @process_group.multi?
1657
+ "#{$0}\##{Process.pid}"
1658
+ end
1659
+ end
1660
+
1661
+ def before_fork need_lock = true
1662
+ if need_lock
1663
+ @process_group.multi_process!
1664
+ end
1665
+ end
1666
+
1667
+ def after_fork_parent
1668
+ @ui.after_fork_parent
1669
+ end
1670
+ end
1671
+
1672
+ class ProcessGroup
1673
+ def initialize
1674
+ @lock_file = nil
1675
+ end
1676
+
1677
+ def locked?
1678
+ true
1679
+ end
1680
+
1681
+ def trylock
1682
+ true
1683
+ end
1684
+
1685
+ def lock
1686
+ true
1687
+ end
1688
+
1689
+ def unlock
1690
+ true
1691
+ end
1692
+
1693
+ def sync
1694
+ yield
1695
+ end
1696
+
1697
+ def after_fork
1698
+ end
1699
+
1700
+ def multi?
1701
+ @lock_file
1702
+ end
1703
+
1704
+ def multi_process!
1705
+ require 'tempfile'
1706
+ @lock_tempfile = Tempfile.open("ruby-debug-lock-")
1707
+ @lock_tempfile.close
1708
+ extend MultiProcessGroup
1709
+ end
1710
+ end
1711
+
1712
+ module MultiProcessGroup
1713
+ def multi_process!
1714
+ end
1715
+
1716
+ def after_fork child: true
1717
+ if child || !@lock_file
1718
+ @m = Mutex.new
1719
+ @lock_level = 0
1720
+ @lock_file = open(@lock_tempfile.path, 'w')
1721
+ end
1722
+ end
1723
+
1724
+ def info msg
1725
+ DEBUGGER__.info "#{msg} (#{@lock_level})" # #{caller.first(1).map{|bt| bt.sub(__dir__, '')}}"
1726
+ end
1727
+
1728
+ def locked?
1729
+ # DEBUGGER__.info "locked? #{@lock_level}"
1730
+ @lock_level > 0
1731
+ end
1732
+
1733
+ private def lock_level_up
1734
+ raise unless @m.owned?
1735
+ @lock_level += 1
1736
+ end
1737
+
1738
+ private def lock_level_down
1739
+ raise unless @m.owned?
1740
+ raise "@lock_level underflow: #{@lock_level}" if @lock_level < 1
1741
+ @lock_level -= 1
1742
+ end
1743
+
1744
+ private def trylock
1745
+ @m.synchronize do
1746
+ if locked?
1747
+ lock_level_up
1748
+ info "Try lock, already locked"
1749
+ true
1750
+ else
1751
+ case r = @lock_file.flock(File::LOCK_EX | File::LOCK_NB)
1752
+ when 0
1753
+ lock_level_up
1754
+ info "Try lock with file: success"
1755
+ true
1756
+ when false
1757
+ info "Try lock with file: failed"
1758
+ false
1759
+ else
1760
+ raise "unknown flock result: #{r.inspect}"
1761
+ end
1762
+ end
1763
+ end
1764
+ end
1765
+
1766
+ def lock
1767
+ unless trylock
1768
+ @m.synchronize do
1769
+ if locked?
1770
+ lock_level_up
1771
+ else
1772
+ info "Lock: block"
1773
+ @lock_file.flock(File::LOCK_EX)
1774
+ lock_level_up
1775
+ end
1776
+ end
1777
+
1778
+ info "Lock: success"
1779
+ end
1780
+ end
1781
+
1782
+ def unlock
1783
+ @m.synchronize do
1784
+ raise "lock file is not opened (#{@lock_file.inspect})" if @lock_file.closed?
1785
+ lock_level_down
1786
+ @lock_file.flock(File::LOCK_UN) unless locked?
1787
+ info "Unlocked"
1788
+ end
1789
+ end
1790
+
1791
+ def sync &b
1792
+ info "sync"
1793
+
1794
+ lock
1795
+ begin
1796
+ b.call if b
1797
+ ensure
1798
+ unlock
1799
+ end
1800
+ end
1523
1801
  end
1524
1802
 
1525
1803
  class UI_Base
@@ -1577,8 +1855,8 @@ module DEBUGGER__
1577
1855
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
1578
1856
  CONFIG.set_config(**kw)
1579
1857
 
1580
- if port
1581
- open_tcp host: host, port: port, nonstop: nonstop
1858
+ if port || CONFIG[:open_frontend] == 'chrome'
1859
+ open_tcp host: host, port: (port || 0), nonstop: nonstop
1582
1860
  else
1583
1861
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
1584
1862
  end
@@ -1700,15 +1978,24 @@ module DEBUGGER__
1700
1978
  end
1701
1979
 
1702
1980
  def self.log level, msg
1981
+ @logfile = STDERR unless defined? @logfile
1982
+
1703
1983
  lv = LOG_LEVELS[level]
1704
1984
  config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
1705
1985
 
1986
+ if defined? SESSION
1987
+ pi = SESSION.process_info
1988
+ process_info = pi ? "[#{pi}]" : nil
1989
+ end
1990
+
1706
1991
  if lv <= config_lv
1707
1992
  if level == :WARN
1708
1993
  # :WARN on debugger is general information
1709
- STDERR.puts "DEBUGGER: #{msg}"
1994
+ @logfile.puts "DEBUGGER#{process_info}: #{msg}"
1995
+ @logfile.flush
1710
1996
  else
1711
- STDERR.puts "DEBUGGER (#{level}): #{msg}"
1997
+ @logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
1998
+ @logfile.flush
1712
1999
  end
1713
2000
  end
1714
2001
  end
@@ -1717,8 +2004,19 @@ module DEBUGGER__
1717
2004
  def fork(&given_block)
1718
2005
  return super unless defined?(SESSION) && SESSION.active?
1719
2006
 
2007
+ unless fork_mode = CONFIG[:fork_mode]
2008
+ if CONFIG[:parent_on_fork]
2009
+ fork_mode = :parent
2010
+ else
2011
+ fork_mode = :both
2012
+ end
2013
+ end
2014
+
2015
+ parent_pid = Process.pid
2016
+
1720
2017
  # before fork
1721
- if CONFIG[:parent_on_fork]
2018
+ case fork_mode
2019
+ when :parent
1722
2020
  parent_hook = -> child_pid {
1723
2021
  # Do nothing
1724
2022
  }
@@ -1726,31 +2024,28 @@ module DEBUGGER__
1726
2024
  DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
1727
2025
  SESSION.deactivate
1728
2026
  }
1729
- else
1730
- parent_pid = Process.pid
2027
+ when :child
2028
+ SESSION.before_fork false
1731
2029
 
1732
2030
  parent_hook = -> child_pid {
1733
2031
  DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
2032
+ SESSION.after_fork_parent
1734
2033
  SESSION.deactivate
2034
+ }
2035
+ child_hook = -> {
2036
+ DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2037
+ SESSION.activate on_fork: true
2038
+ }
2039
+ when :both
2040
+ SESSION.before_fork
1735
2041
 
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
- }
2042
+ parent_hook = -> child_pid {
2043
+ SESSION.process_group.after_fork
2044
+ SESSION.after_fork_parent
1751
2045
  }
1752
2046
  child_hook = -> {
1753
2047
  DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2048
+ SESSION.process_group.after_fork child: true
1754
2049
  SESSION.activate on_fork: true
1755
2050
  }
1756
2051
  end
@@ -1824,20 +2119,21 @@ module DEBUGGER__
1824
2119
  end
1825
2120
 
1826
2121
  module Kernel
1827
- def debugger pre: nil, do: nil
2122
+ def debugger pre: nil, do: nil, up_level: 0
1828
2123
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
1829
2124
 
1830
2125
  if pre || (do_expr = binding.local_variable_get(:do))
1831
2126
  cmds = ['binding.break', pre, do_expr]
1832
2127
  end
1833
2128
 
1834
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
2129
+ loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
1835
2130
  self
1836
2131
  end
2132
+
2133
+ alias bb debugger if ENV['RUBY_DEBUG_BB']
1837
2134
  end
1838
2135
 
1839
2136
  class Binding
1840
2137
  alias break debugger
1841
2138
  alias b debugger
1842
2139
  end
1843
-