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.
- checksums.yaml +4 -4
- data/README.md +116 -5
- data/ext/debug/debug.c +2 -1
- data/ext/debug/extconf.rb +2 -0
- data/lib/debug/breakpoint.rb +2 -2
- data/lib/debug/client.rb +69 -46
- data/lib/debug/config.rb +74 -21
- data/lib/debug/console.rb +92 -25
- data/lib/debug/local.rb +22 -1
- data/lib/debug/prelude.rb +49 -0
- data/lib/debug/server.rb +186 -25
- data/lib/debug/server_cdp.rb +412 -0
- data/lib/debug/server_dap.rb +53 -23
- data/lib/debug/session.rb +387 -120
- data/lib/debug/thread_client.rb +4 -2
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +95 -1
- metadata +8 -6
data/lib/debug/session.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
-
|
187
|
-
|
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
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
248
|
+
if @displays.empty?
|
199
249
|
wait_command_loop tc
|
250
|
+
else
|
251
|
+
tc << [:eval, :display, @displays]
|
252
|
+
end
|
200
253
|
|
201
|
-
|
202
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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 :
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
-
#
|
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
|
-
|
284
|
+
wait_command_loop tc
|
267
285
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
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
|
-
|
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
|
-
|
1964
|
+
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
1965
|
+
@logfile.flush
|
1709
1966
|
else
|
1710
|
-
|
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
|
-
|
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
|
-
|
1729
|
-
|
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
|
-
|
1736
|
-
|
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
|
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
|
-
|