debug 1.2.4 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/client.rb +41 -17
- data/lib/debug/config.rb +30 -10
- data/lib/debug/console.rb +92 -25
- data/lib/debug/local.rb +4 -1
- data/lib/debug/prelude.rb +49 -0
- data/lib/debug/server.rb +161 -17
- data/lib/debug/server_cdp.rb +412 -0
- data/lib/debug/server_dap.rb +53 -23
- data/lib/debug/session.rb +397 -120
- data/lib/debug/thread_client.rb +4 -2
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +95 -1
- metadata +4 -2
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'
|
@@ -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
|
-
|
188
|
-
|
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
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
248
|
+
if @displays.empty?
|
200
249
|
wait_command_loop tc
|
250
|
+
else
|
251
|
+
tc << [:eval, :display, @displays]
|
252
|
+
end
|
201
253
|
|
202
|
-
|
203
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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 :
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
-
#
|
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
|
-
|
284
|
+
wait_command_loop tc
|
268
285
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
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
|
-
|
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
|
-
|
1977
|
+
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
1978
|
+
@logfile.flush
|
1710
1979
|
else
|
1711
|
-
|
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
|
-
|
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
|
-
|
1730
|
-
|
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
|
-
|
1737
|
-
|
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
|
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
|
-
|