debug 1.2.1 → 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/Rakefile +9 -6
- 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 +72 -47
- data/lib/debug/config.rb +48 -11
- data/lib/debug/console.rb +92 -25
- data/lib/debug/frame_info.rb +3 -3
- 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 +398 -120
- data/lib/debug/thread_client.rb +4 -2
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +95 -1
- metadata +5 -3
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,172 @@ 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
|
+
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
|
|
1522
1784
|
end
|
|
1523
1785
|
|
|
1524
1786
|
class UI_Base
|
|
@@ -1576,8 +1838,8 @@ module DEBUGGER__
|
|
|
1576
1838
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
1577
1839
|
CONFIG.set_config(**kw)
|
|
1578
1840
|
|
|
1579
|
-
if port
|
|
1580
|
-
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
|
|
1581
1843
|
else
|
|
1582
1844
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
|
1583
1845
|
end
|
|
@@ -1699,15 +1961,24 @@ module DEBUGGER__
|
|
|
1699
1961
|
end
|
|
1700
1962
|
|
|
1701
1963
|
def self.log level, msg
|
|
1964
|
+
@logfile = STDERR unless defined? @logfile
|
|
1965
|
+
|
|
1702
1966
|
lv = LOG_LEVELS[level]
|
|
1703
1967
|
config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
|
|
1704
1968
|
|
|
1969
|
+
if defined? SESSION
|
|
1970
|
+
pi = SESSION.process_info
|
|
1971
|
+
process_info = pi ? "[#{pi}]" : nil
|
|
1972
|
+
end
|
|
1973
|
+
|
|
1705
1974
|
if lv <= config_lv
|
|
1706
1975
|
if level == :WARN
|
|
1707
1976
|
# :WARN on debugger is general information
|
|
1708
|
-
|
|
1977
|
+
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
|
1978
|
+
@logfile.flush
|
|
1709
1979
|
else
|
|
1710
|
-
|
|
1980
|
+
@logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
|
|
1981
|
+
@logfile.flush
|
|
1711
1982
|
end
|
|
1712
1983
|
end
|
|
1713
1984
|
end
|
|
@@ -1716,8 +1987,19 @@ module DEBUGGER__
|
|
|
1716
1987
|
def fork(&given_block)
|
|
1717
1988
|
return super unless defined?(SESSION) && SESSION.active?
|
|
1718
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
|
+
|
|
1719
2000
|
# before fork
|
|
1720
|
-
|
|
2001
|
+
case fork_mode
|
|
2002
|
+
when :parent
|
|
1721
2003
|
parent_hook = -> child_pid {
|
|
1722
2004
|
# Do nothing
|
|
1723
2005
|
}
|
|
@@ -1725,31 +2007,28 @@ module DEBUGGER__
|
|
|
1725
2007
|
DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
|
|
1726
2008
|
SESSION.deactivate
|
|
1727
2009
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
2010
|
+
when :child
|
|
2011
|
+
SESSION.before_fork false
|
|
1730
2012
|
|
|
1731
2013
|
parent_hook = -> child_pid {
|
|
1732
2014
|
DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
|
|
1733
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
|
|
1734
2024
|
|
|
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
|
-
}
|
|
2025
|
+
parent_hook = -> child_pid {
|
|
2026
|
+
SESSION.process_group.after_fork
|
|
2027
|
+
SESSION.after_fork_parent
|
|
1750
2028
|
}
|
|
1751
2029
|
child_hook = -> {
|
|
1752
2030
|
DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
|
2031
|
+
SESSION.process_group.after_fork child: true
|
|
1753
2032
|
SESSION.activate on_fork: true
|
|
1754
2033
|
}
|
|
1755
2034
|
end
|
|
@@ -1823,14 +2102,14 @@ module DEBUGGER__
|
|
|
1823
2102
|
end
|
|
1824
2103
|
|
|
1825
2104
|
module Kernel
|
|
1826
|
-
def debugger pre: nil, do: nil
|
|
2105
|
+
def debugger pre: nil, do: nil, up_level: 0
|
|
1827
2106
|
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
|
1828
2107
|
|
|
1829
2108
|
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1830
2109
|
cmds = ['binding.break', pre, do_expr]
|
|
1831
2110
|
end
|
|
1832
2111
|
|
|
1833
|
-
::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
|
|
1834
2113
|
self
|
|
1835
2114
|
end
|
|
1836
2115
|
end
|
|
@@ -1839,4 +2118,3 @@ class Binding
|
|
|
1839
2118
|
alias break debugger
|
|
1840
2119
|
alias b debugger
|
|
1841
2120
|
end
|
|
1842
|
-
|