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.
- 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
|
-
|