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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +9 -11
- data/README.md +116 -5
- data/ext/debug/debug.c +3 -2
- data/ext/debug/extconf.rb +2 -0
- data/lib/debug/client.rb +39 -17
- data/lib/debug/color.rb +1 -1
- data/lib/debug/config.rb +66 -30
- 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 +188 -25
- data/lib/debug/server_cdp.rb +426 -0
- data/lib/debug/server_dap.rb +54 -24
- data/lib/debug/session.rb +417 -121
- 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'
|
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
1994
|
+
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
|
1995
|
+
@logfile.flush
|
|
1710
1996
|
else
|
|
1711
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1730
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
-
|