debug 1.0.0.beta5 → 1.0.0.rc1
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 +213 -20
- data/Gemfile +1 -0
- data/README.md +460 -226
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/bin/gentest +22 -0
- data/debug.gemspec +1 -0
- data/exe/rdbg +11 -18
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +106 -62
- data/lib/debug/client.rb +11 -17
- data/lib/debug/color.rb +28 -7
- data/lib/debug/config.rb +378 -144
- data/lib/debug/console.rb +79 -57
- data/lib/debug/frame_info.rb +42 -8
- data/lib/debug/local.rb +91 -0
- data/lib/debug/open.rb +2 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +96 -43
- data/lib/debug/server_dap.rb +34 -7
- data/lib/debug/session.rb +827 -341
- data/lib/debug/source_repository.rb +2 -0
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +691 -184
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +3 -1
- data/lib/debug.rb +3 -0
- data/misc/README.md.erb +341 -216
- metadata +21 -4
- data/lib/debug/run.rb +0 -4
- data/lib/debug/test_console.rb +0 -0
data/lib/debug/server_dap.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
|
3
5
|
module DEBUGGER__
|
@@ -5,7 +7,7 @@ module DEBUGGER__
|
|
5
7
|
SHOW_PROTOCOL = ENV['RUBY_DEBUG_DAP_SHOW_PROTOCOL'] == '1'
|
6
8
|
|
7
9
|
def dap_setup bytes
|
8
|
-
|
10
|
+
CONFIG.set_config no_color: true
|
9
11
|
@seq = 0
|
10
12
|
|
11
13
|
$stderr.puts '[>]' + bytes if SHOW_PROTOCOL
|
@@ -35,6 +37,7 @@ module DEBUGGER__
|
|
35
37
|
},
|
36
38
|
],
|
37
39
|
supportsExceptionFilterOptions: true,
|
40
|
+
supportsStepBack: true,
|
38
41
|
|
39
42
|
## Will be supported
|
40
43
|
# supportsExceptionOptions: true,
|
@@ -48,7 +51,6 @@ module DEBUGGER__
|
|
48
51
|
# supportsBreakpointLocationsRequest:
|
49
52
|
|
50
53
|
## Possible?
|
51
|
-
# supportsStepBack:
|
52
54
|
# supportsRestartFrame:
|
53
55
|
# supportsCompletionsRequest:
|
54
56
|
# completionTriggerCharacters:
|
@@ -195,6 +197,12 @@ module DEBUGGER__
|
|
195
197
|
when 'pause'
|
196
198
|
send_response req
|
197
199
|
Process.kill(:SIGINT, Process.pid)
|
200
|
+
when 'reverseContinue'
|
201
|
+
send_response req,
|
202
|
+
success: false, message: 'cancelled',
|
203
|
+
result: "Reverse Continue is not supported. Only \"Step back\" is supported."
|
204
|
+
when 'stepBack'
|
205
|
+
@q_msg << req
|
198
206
|
|
199
207
|
## query
|
200
208
|
when 'threads'
|
@@ -210,6 +218,7 @@ module DEBUGGER__
|
|
210
218
|
'evaluate',
|
211
219
|
'source'
|
212
220
|
@q_msg << req
|
221
|
+
|
213
222
|
else
|
214
223
|
raise "Unknown request: #{req.inspect}"
|
215
224
|
end
|
@@ -218,7 +227,7 @@ module DEBUGGER__
|
|
218
227
|
|
219
228
|
## called by the SESSION thread
|
220
229
|
|
221
|
-
def readline
|
230
|
+
def readline prompt
|
222
231
|
@q_msg.pop || 'kill!'
|
223
232
|
end
|
224
233
|
|
@@ -279,6 +288,13 @@ module DEBUGGER__
|
|
279
288
|
|
280
289
|
def process_dap_request req
|
281
290
|
case req['command']
|
291
|
+
when 'stepBack'
|
292
|
+
if @tc.recorder&.can_step_back?
|
293
|
+
@tc << [:step, :back]
|
294
|
+
else
|
295
|
+
fail_response req, message: 'cancelled'
|
296
|
+
end
|
297
|
+
|
282
298
|
when 'stackTrace'
|
283
299
|
tid = req.dig('arguments', 'threadId')
|
284
300
|
if tc = find_tc(tid)
|
@@ -363,7 +379,6 @@ module DEBUGGER__
|
|
363
379
|
else
|
364
380
|
fail_response req, message: 'not found...'
|
365
381
|
end
|
366
|
-
|
367
382
|
return :retry
|
368
383
|
else
|
369
384
|
raise "Unknown DAP request: #{req.inspect}"
|
@@ -436,8 +451,8 @@ module DEBUGGER__
|
|
436
451
|
when :backtrace
|
437
452
|
event! :dap_result, :backtrace, req, {
|
438
453
|
stackFrames: @target_frames.map.with_index{|frame, i|
|
439
|
-
path = frame.path
|
440
|
-
ref = frame.file_lines unless File.exist?(path)
|
454
|
+
path = frame.realpath || frame.path
|
455
|
+
ref = frame.file_lines unless path && File.exist?(path)
|
441
456
|
|
442
457
|
{
|
443
458
|
# id: ??? # filled by SESSION
|
@@ -455,7 +470,15 @@ module DEBUGGER__
|
|
455
470
|
when :scopes
|
456
471
|
fid = args.shift
|
457
472
|
frame = @target_frames[fid]
|
458
|
-
|
473
|
+
|
474
|
+
lnum =
|
475
|
+
if frame.binding
|
476
|
+
frame.binding.local_variables.size
|
477
|
+
elsif vars = frame.local_variables
|
478
|
+
vars.size
|
479
|
+
else
|
480
|
+
0
|
481
|
+
end
|
459
482
|
|
460
483
|
event! :dap_result, :scopes, req, scopes: [{
|
461
484
|
name: 'Local variables',
|
@@ -483,6 +506,10 @@ module DEBUGGER__
|
|
483
506
|
vars.unshift variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
484
507
|
vars.unshift variable('%return', frame.return_value) if frame.has_return_value
|
485
508
|
vars.unshift variable('%self', b.receiver)
|
509
|
+
elsif lvars = frame.local_variables
|
510
|
+
vars = lvars.map{|var, val|
|
511
|
+
variable(var, val)
|
512
|
+
}
|
486
513
|
else
|
487
514
|
vars = [variable('%self', frame.self)]
|
488
515
|
vars.push variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
data/lib/debug/session.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# skip to load debugger for bundle exec
|
3
4
|
return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
4
5
|
|
@@ -6,6 +7,7 @@ require_relative 'config'
|
|
6
7
|
require_relative 'thread_client'
|
7
8
|
require_relative 'source_repository'
|
8
9
|
require_relative 'breakpoint'
|
10
|
+
require_relative 'tracer'
|
9
11
|
|
10
12
|
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
11
13
|
|
@@ -52,6 +54,9 @@ class RubyVM::InstructionSequence
|
|
52
54
|
end
|
53
55
|
|
54
56
|
module DEBUGGER__
|
57
|
+
PresetCommand = Struct.new(:commands, :source, :auto_continue)
|
58
|
+
class PostmortemError < RuntimeError; end
|
59
|
+
|
55
60
|
class Session
|
56
61
|
def initialize ui
|
57
62
|
@ui = ui
|
@@ -62,59 +67,125 @@ module DEBUGGER__
|
|
62
67
|
# "Foo#bar" => MethodBreakpoint
|
63
68
|
# [:watch, ivar] => WatchIVarBreakpoint
|
64
69
|
# [:check, expr] => CheckBreakpoint
|
65
|
-
|
70
|
+
#
|
71
|
+
@tracers = []
|
72
|
+
@th_clients = nil # {Thread => ThreadClient}
|
66
73
|
@q_evt = Queue.new
|
67
74
|
@displays = []
|
68
75
|
@tc = nil
|
69
76
|
@tc_id = 0
|
70
|
-
@
|
77
|
+
@preset_command = nil
|
78
|
+
@postmortem_hook = nil
|
79
|
+
@postmortem = false
|
80
|
+
@thread_stopper = nil
|
71
81
|
|
72
82
|
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
|
73
83
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
74
84
|
@src_map = {} # {id => src}
|
75
85
|
|
76
86
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
77
|
-
|
78
|
-
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
79
|
-
end
|
87
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
80
88
|
}
|
81
89
|
@tp_load_script.enable
|
82
90
|
|
91
|
+
activate
|
92
|
+
end
|
93
|
+
|
94
|
+
def active?
|
95
|
+
!@q_evt.closed?
|
96
|
+
end
|
97
|
+
|
98
|
+
def break_at? file, line
|
99
|
+
@bps.has_key? [file, line]
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_forked
|
103
|
+
unless active?
|
104
|
+
# TODO: Support it
|
105
|
+
raise 'DEBUGGER: stop at forked process is not supported yet.'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def activate on_fork: false
|
83
110
|
@session_server = Thread.new do
|
111
|
+
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
84
112
|
Thread.current.abort_on_exception = true
|
85
113
|
session_server_main
|
86
114
|
end
|
87
115
|
|
88
|
-
@management_threads = [@session_server]
|
89
|
-
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
90
|
-
|
91
116
|
setup_threads
|
92
117
|
|
118
|
+
thc = thread_client @session_server
|
119
|
+
thc.is_management
|
120
|
+
|
121
|
+
if on_fork
|
122
|
+
@tp_thread_begin.disable
|
123
|
+
@tp_thread_begin = nil
|
124
|
+
@ui.activate on_fork: true
|
125
|
+
end
|
126
|
+
|
127
|
+
if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
|
128
|
+
thc.is_management
|
129
|
+
end
|
130
|
+
|
93
131
|
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
94
|
-
|
95
|
-
|
96
|
-
end
|
132
|
+
th = Thread.current
|
133
|
+
ThreadClient.current.on_thread_begin th
|
97
134
|
}
|
98
135
|
@tp_thread_begin.enable
|
99
136
|
end
|
100
137
|
|
138
|
+
def deactivate
|
139
|
+
thread_client.deactivate
|
140
|
+
@thread_stopper.disable if @thread_stopper
|
141
|
+
@tp_load_script.disable
|
142
|
+
@tp_thread_begin.disable
|
143
|
+
@bps.each{|k, bp| bp.disable}
|
144
|
+
@th_clients.each{|th, thc| thc.close}
|
145
|
+
@tracers.each{|t| t.disable}
|
146
|
+
@q_evt.close
|
147
|
+
@ui&.deactivate
|
148
|
+
@ui = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def reset_ui ui
|
152
|
+
@ui.close
|
153
|
+
@ui = ui
|
154
|
+
end
|
155
|
+
|
156
|
+
def pop_event
|
157
|
+
@q_evt.pop
|
158
|
+
end
|
159
|
+
|
101
160
|
def session_server_main
|
102
|
-
while evt =
|
161
|
+
while evt = pop_event
|
103
162
|
# varible `@internal_info` is only used for test
|
104
163
|
tc, output, ev, @internal_info, *ev_args = evt
|
105
164
|
output.each{|str| @ui.puts str}
|
106
165
|
|
107
166
|
case ev
|
167
|
+
when :init
|
168
|
+
wait_command_loop tc
|
169
|
+
|
108
170
|
when :load
|
109
171
|
iseq, src = ev_args
|
110
172
|
on_load iseq, src
|
111
173
|
@ui.event :load
|
112
174
|
tc << :continue
|
175
|
+
|
176
|
+
when :trace
|
177
|
+
trace_id, msg = ev_args
|
178
|
+
if t = @tracers.find{|t| t.object_id == trace_id}
|
179
|
+
t.puts msg
|
180
|
+
end
|
181
|
+
tc << :continue
|
182
|
+
|
113
183
|
when :thread_begin
|
114
184
|
th = ev_args.shift
|
115
185
|
on_thread_begin th
|
116
186
|
@ui.event :thread_begin, th
|
117
187
|
tc << :continue
|
188
|
+
|
118
189
|
when :suspend
|
119
190
|
case ev_args.first
|
120
191
|
when :breakpoint
|
@@ -127,16 +198,14 @@ module DEBUGGER__
|
|
127
198
|
end
|
128
199
|
|
129
200
|
if @displays.empty?
|
201
|
+
stop_all_threads
|
130
202
|
wait_command_loop tc
|
131
203
|
else
|
132
204
|
tc << [:eval, :display, @displays]
|
133
205
|
end
|
206
|
+
|
134
207
|
when :result
|
135
208
|
case ev_args.first
|
136
|
-
when :watch
|
137
|
-
bp = ev_args[1]
|
138
|
-
@bps[bp.key] = bp
|
139
|
-
show_bps bp
|
140
209
|
when :try_display
|
141
210
|
failed_results = ev_args[1]
|
142
211
|
if failed_results.size > 0
|
@@ -145,14 +214,22 @@ module DEBUGGER__
|
|
145
214
|
@ui.puts "canceled: #{@displays.pop}"
|
146
215
|
end
|
147
216
|
end
|
148
|
-
|
217
|
+
stop_all_threads
|
218
|
+
|
219
|
+
when :method_breakpoint, :watch_breakpoint
|
149
220
|
bp = ev_args[1]
|
150
221
|
if bp
|
151
|
-
|
222
|
+
add_bp(bp)
|
152
223
|
show_bps bp
|
153
224
|
else
|
154
225
|
# can't make a bp
|
155
226
|
end
|
227
|
+
when :trace_pass
|
228
|
+
obj_id = ev_args[1]
|
229
|
+
obj_inspect = ev_args[2]
|
230
|
+
opt = ev_args[3]
|
231
|
+
@tracers << t = ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
232
|
+
@ui.puts "Enable #{t.to_s}"
|
156
233
|
else
|
157
234
|
# ignore
|
158
235
|
end
|
@@ -165,19 +242,28 @@ module DEBUGGER__
|
|
165
242
|
end
|
166
243
|
end
|
167
244
|
ensure
|
168
|
-
|
169
|
-
@th_clients.each{|th, thc| thc.close}
|
245
|
+
deactivate
|
170
246
|
end
|
171
247
|
|
172
|
-
def
|
173
|
-
cmds.
|
174
|
-
c.
|
175
|
-
|
176
|
-
|
248
|
+
def add_preset_commands name, cmds, kick: true, continue: true
|
249
|
+
cs = cmds.map{|c|
|
250
|
+
c.each_line.map{|line|
|
251
|
+
line = line.strip.gsub(/\A\s*\#.*/, '').strip
|
252
|
+
line unless line.empty?
|
253
|
+
}.compact
|
254
|
+
}.flatten.compact
|
255
|
+
|
256
|
+
if @preset_command && !@preset_command.commands.empty?
|
257
|
+
@preset_command.commands += cs
|
258
|
+
else
|
259
|
+
@preset_command = PresetCommand.new(cs, name, continue)
|
260
|
+
end
|
261
|
+
|
262
|
+
ThreadClient.current.on_init name if kick
|
177
263
|
end
|
178
264
|
|
179
265
|
def source iseq
|
180
|
-
if CONFIG[:
|
266
|
+
if !CONFIG[:no_color]
|
181
267
|
@sr.get_colored(iseq)
|
182
268
|
else
|
183
269
|
@sr.get(iseq)
|
@@ -190,29 +276,46 @@ module DEBUGGER__
|
|
190
276
|
|
191
277
|
def wait_command_loop tc
|
192
278
|
@tc = tc
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
end
|
201
|
-
rescue Interrupt
|
202
|
-
retry
|
279
|
+
|
280
|
+
loop do
|
281
|
+
case wait_command
|
282
|
+
when :retry
|
283
|
+
# nothing
|
284
|
+
else
|
285
|
+
break
|
203
286
|
end
|
287
|
+
rescue Interrupt
|
288
|
+
@ui.puts "\n^C"
|
289
|
+
retry
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def prompt
|
294
|
+
if @postmortem
|
295
|
+
'(rdbg:postmortem) '
|
296
|
+
else
|
297
|
+
'(rdbg) '
|
204
298
|
end
|
205
|
-
ensure
|
206
|
-
@tc = nil
|
207
299
|
end
|
208
300
|
|
209
301
|
def wait_command
|
210
|
-
if @
|
211
|
-
@
|
212
|
-
|
302
|
+
if @preset_command
|
303
|
+
if @preset_command.commands.empty?
|
304
|
+
if @preset_command.auto_continue
|
305
|
+
@preset_command = nil
|
306
|
+
@tc << :continue
|
307
|
+
return
|
308
|
+
else
|
309
|
+
@preset_command = nil
|
310
|
+
return :retry
|
311
|
+
end
|
312
|
+
else
|
313
|
+
line = @preset_command.commands.shift
|
314
|
+
@ui.puts "(rdbg:#{@preset_command.source}) #{line}"
|
315
|
+
end
|
213
316
|
else
|
214
|
-
|
215
|
-
@ui.
|
317
|
+
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
|
318
|
+
line = @ui.readline prompt
|
216
319
|
end
|
217
320
|
|
218
321
|
case line
|
@@ -246,23 +349,37 @@ module DEBUGGER__
|
|
246
349
|
|
247
350
|
# * `s[tep]`
|
248
351
|
# * Step in. Resume the program until next breakable point.
|
352
|
+
# * `s[tep] <n>`
|
353
|
+
# * Step in, resume the program at `<n>`th breakable point.
|
249
354
|
when 's', 'step'
|
250
|
-
|
355
|
+
cancel_auto_continue
|
356
|
+
check_postmortem
|
357
|
+
step_command :in, arg
|
251
358
|
|
252
359
|
# * `n[ext]`
|
253
360
|
# * Step over. Resume the program until next line.
|
361
|
+
# * `n[ext] <n>`
|
362
|
+
# * Step over, same as `step <n>`.
|
254
363
|
when 'n', 'next'
|
255
|
-
|
364
|
+
cancel_auto_continue
|
365
|
+
check_postmortem
|
366
|
+
step_command :next, arg
|
256
367
|
|
257
368
|
# * `fin[ish]`
|
258
369
|
# * Finish this frame. Resume the program until the current frame is finished.
|
370
|
+
# * `fin[ish] <n>`
|
371
|
+
# * Finish frames, same as `step <n>`.
|
259
372
|
when 'fin', 'finish'
|
260
|
-
|
373
|
+
cancel_auto_continue
|
374
|
+
check_postmortem
|
375
|
+
step_command :finish, arg
|
261
376
|
|
262
377
|
# * `c[ontinue]`
|
263
378
|
# * Resume the program.
|
264
379
|
when 'c', 'continue'
|
380
|
+
cancel_auto_continue
|
265
381
|
@tc << :continue
|
382
|
+
restart_all_threads
|
266
383
|
|
267
384
|
# * `q[uit]` or `Ctrl-D`
|
268
385
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
@@ -270,6 +387,7 @@ module DEBUGGER__
|
|
270
387
|
if ask 'Really quit?'
|
271
388
|
@ui.quit arg.to_i
|
272
389
|
@tc << :continue
|
390
|
+
restart_all_threads
|
273
391
|
else
|
274
392
|
return :retry
|
275
393
|
end
|
@@ -278,7 +396,7 @@ module DEBUGGER__
|
|
278
396
|
# * Same as q[uit] but without the confirmation prompt.
|
279
397
|
when 'q!', 'quit!'
|
280
398
|
@ui.quit arg.to_i
|
281
|
-
|
399
|
+
restart_all_threads
|
282
400
|
|
283
401
|
# * `kill`
|
284
402
|
# * Stop the debuggee process with `Kernal#exit!`.
|
@@ -306,12 +424,18 @@ module DEBUGGER__
|
|
306
424
|
# * Set breakpoint on the method `<class>#<name>`.
|
307
425
|
# * `b[reak] <expr>.<name>`
|
308
426
|
# * Set breakpoint on the method `<expr>.<name>`.
|
309
|
-
# * `b[reak] ... if <expr>`
|
427
|
+
# * `b[reak] ... if: <expr>`
|
310
428
|
# * break if `<expr>` is true at specified location.
|
311
|
-
# * `b[reak]
|
312
|
-
# * break
|
429
|
+
# * `b[reak] ... pre: <command>`
|
430
|
+
# * break and run `<command>` before stopping.
|
431
|
+
# * `b[reak] ... do: <command>`
|
432
|
+
# * break and run `<command>`, and continue.
|
433
|
+
# * `b[reak] if: <expr>`
|
434
|
+
# * break if: `<expr>` is true at any lines.
|
313
435
|
# * Note that this feature is super slow.
|
314
436
|
when 'b', 'break'
|
437
|
+
check_postmortem
|
438
|
+
|
315
439
|
if arg == nil
|
316
440
|
show_bps
|
317
441
|
return :retry
|
@@ -328,6 +452,7 @@ module DEBUGGER__
|
|
328
452
|
|
329
453
|
# skip
|
330
454
|
when 'bv'
|
455
|
+
check_postmortem
|
331
456
|
require 'json'
|
332
457
|
|
333
458
|
h = Hash.new{|h, k| h[k] = []}
|
@@ -354,8 +479,10 @@ module DEBUGGER__
|
|
354
479
|
# * `catch <Error>`
|
355
480
|
# * Set breakpoint on raising `<Error>`.
|
356
481
|
when 'catch'
|
482
|
+
check_postmortem
|
483
|
+
|
357
484
|
if arg
|
358
|
-
bp =
|
485
|
+
bp = repl_add_catch_breakpoint arg
|
359
486
|
show_bps bp if bp
|
360
487
|
else
|
361
488
|
show_bps
|
@@ -366,8 +493,10 @@ module DEBUGGER__
|
|
366
493
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
367
494
|
# * Note that this feature is super slow.
|
368
495
|
when 'wat', 'watch'
|
369
|
-
|
370
|
-
|
496
|
+
check_postmortem
|
497
|
+
|
498
|
+
if arg && arg.match?(/\A@\w+/)
|
499
|
+
@tc << [:breakpoint, :watch, arg]
|
371
500
|
else
|
372
501
|
show_bps
|
373
502
|
return :retry
|
@@ -378,15 +507,17 @@ module DEBUGGER__
|
|
378
507
|
# * `del[ete] <bpnum>`
|
379
508
|
# * delete specified breakpoint.
|
380
509
|
when 'del', 'delete'
|
510
|
+
check_postmortem
|
511
|
+
|
381
512
|
bp =
|
382
513
|
case arg
|
383
514
|
when nil
|
384
515
|
show_bps
|
385
516
|
if ask "Remove all breakpoints?", 'N'
|
386
|
-
|
517
|
+
delete_bp
|
387
518
|
end
|
388
519
|
when /\d+/
|
389
|
-
|
520
|
+
delete_bp arg.to_i
|
390
521
|
else
|
391
522
|
nil
|
392
523
|
end
|
@@ -397,8 +528,25 @@ module DEBUGGER__
|
|
397
528
|
|
398
529
|
# * `bt` or `backtrace`
|
399
530
|
# * Show backtrace (frame) information.
|
531
|
+
# * `bt <num>` or `backtrace <num>`
|
532
|
+
# * Only shows first `<num>` frames.
|
533
|
+
# * `bt /regexp/` or `backtrace /regexp/`
|
534
|
+
# * Only shows frames with method name or location info that matches `/regexp/`.
|
535
|
+
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
|
536
|
+
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
|
400
537
|
when 'bt', 'backtrace'
|
401
|
-
|
538
|
+
case arg
|
539
|
+
when /\A(\d+)\z/
|
540
|
+
@tc << [:show, :backtrace, arg.to_i, nil]
|
541
|
+
when /\A\/(.*)\/\z/
|
542
|
+
pattern = $1
|
543
|
+
@tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
|
544
|
+
when /\A(\d+)\s+\/(.*)\/\z/
|
545
|
+
max, pattern = $1, $2
|
546
|
+
@tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
547
|
+
else
|
548
|
+
@tc << [:show, :backtrace, nil, nil]
|
549
|
+
end
|
402
550
|
|
403
551
|
# * `l[ist]`
|
404
552
|
# * Show current frame's source code.
|
@@ -442,25 +590,57 @@ module DEBUGGER__
|
|
442
590
|
|
443
591
|
@tc << [:show, :edit, arg]
|
444
592
|
|
445
|
-
# * `i[nfo]
|
593
|
+
# * `i[nfo]`
|
594
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
595
|
+
# * `i[nfo] l[ocal[s]]`
|
446
596
|
# * Show information about the current frame (local variables)
|
447
597
|
# * It includes `self` as `%self` and a return value as `%return`.
|
598
|
+
# * `i[nfo] i[var[s]]` or `i[nfo] instance`
|
599
|
+
# * Show information about insttance variables about `self`.
|
600
|
+
# * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
|
601
|
+
# * Show information about accessible constants except toplevel constants.
|
602
|
+
# * `i[nfo] g[lobal[s]]`
|
603
|
+
# * Show information about global variables
|
604
|
+
# * `i[nfo] ... </pattern/>`
|
605
|
+
# * Filter the output with `</pattern/>`.
|
448
606
|
# * `i[nfo] th[read[s]]`
|
449
607
|
# * Show all threads (same as `th[read]`).
|
450
608
|
when 'i', 'info'
|
451
|
-
|
609
|
+
if /\/(.+)\/\z/ =~ arg
|
610
|
+
pat = Regexp.compile($1)
|
611
|
+
sub = $~.pre_match.strip
|
612
|
+
else
|
613
|
+
sub = arg
|
614
|
+
end
|
615
|
+
|
616
|
+
case sub
|
452
617
|
when nil
|
453
|
-
@tc << [:show, :
|
454
|
-
when 'l',
|
455
|
-
@tc << [:show, :
|
618
|
+
@tc << [:show, :default, pat] # something useful
|
619
|
+
when 'l', /^locals?/
|
620
|
+
@tc << [:show, :locals, pat]
|
621
|
+
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
622
|
+
@tc << [:show, :ivars, pat]
|
623
|
+
when 'c', /^consts?/i, /^constants?/i
|
624
|
+
@tc << [:show, :consts, pat]
|
625
|
+
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
626
|
+
@tc << [:show, :globals, pat]
|
456
627
|
when 'th', /threads?/
|
457
628
|
thread_list
|
458
629
|
return :retry
|
459
630
|
else
|
631
|
+
@ui.puts "unrecognized argument for info command: #{arg}"
|
460
632
|
show_help 'info'
|
461
633
|
return :retry
|
462
634
|
end
|
463
635
|
|
636
|
+
# * `o[utline]` or `ls`
|
637
|
+
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
638
|
+
# * `o[utline] <expr>` or `ls <expr>`
|
639
|
+
# * Show you available methods and instance variables of the given object.
|
640
|
+
# * If the object is a class/module, it also lists its constants.
|
641
|
+
when 'outline', 'o', 'ls'
|
642
|
+
@tc << [:show, :outline, arg]
|
643
|
+
|
464
644
|
# * `display`
|
465
645
|
# * Show display setting.
|
466
646
|
# * `display <expr>`
|
@@ -481,9 +661,7 @@ module DEBUGGER__
|
|
481
661
|
case arg
|
482
662
|
when /(\d+)/
|
483
663
|
if @displays[n = $1.to_i]
|
484
|
-
|
485
|
-
@displays.delete_at n
|
486
|
-
end
|
664
|
+
@displays.delete_at n
|
487
665
|
end
|
488
666
|
@tc << [:eval, :display, @displays]
|
489
667
|
when nil
|
@@ -493,28 +671,6 @@ module DEBUGGER__
|
|
493
671
|
end
|
494
672
|
return :retry
|
495
673
|
|
496
|
-
# * `trace [on|off]`
|
497
|
-
# * enable or disable line tracer.
|
498
|
-
when 'trace'
|
499
|
-
case arg
|
500
|
-
when 'on'
|
501
|
-
dir = __dir__
|
502
|
-
@tracer ||= TracePoint.new(:call, :return, :b_call, :b_return, :line, :class, :end){|tp|
|
503
|
-
next if File.dirname(tp.path) == dir
|
504
|
-
next if tp.path == '<internal:trace_point>'
|
505
|
-
# Skip when `JSON.generate` is called during tests
|
506
|
-
next if tp.binding.eval('self').to_s == 'JSON' and ENV['RUBY_DEBUG_TEST_MODE']
|
507
|
-
# next if tp.event != :line
|
508
|
-
@ui.puts pretty_tp(tp)
|
509
|
-
}
|
510
|
-
@tracer.enable
|
511
|
-
when 'off'
|
512
|
-
@tracer && @tracer.disable
|
513
|
-
end
|
514
|
-
enabled = (@tracer && @tracer.enabled?) ? true : false
|
515
|
-
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
516
|
-
return :retry
|
517
|
-
|
518
674
|
### Frame control
|
519
675
|
|
520
676
|
# * `f[rame]`
|
@@ -560,6 +716,108 @@ module DEBUGGER__
|
|
560
716
|
end
|
561
717
|
@tc << [:eval, :call, 'binding.irb']
|
562
718
|
|
719
|
+
# don't repeat irb command
|
720
|
+
@repl_prev_line = nil
|
721
|
+
|
722
|
+
### Trace
|
723
|
+
# * `trace`
|
724
|
+
# * Show available tracers list.
|
725
|
+
# * `trace line`
|
726
|
+
# * Add a line tracer. It indicates line events.
|
727
|
+
# * `trace call`
|
728
|
+
# * Add a call tracer. It indicate call/return events.
|
729
|
+
# * `trace exception`
|
730
|
+
# * Add an exception tracer. It indicates raising exceptions.
|
731
|
+
# * `trace object <expr>`
|
732
|
+
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
733
|
+
# * `trace ... </pattern/>`
|
734
|
+
# * Indicates only matched events to `</pattern/>` (RegExp).
|
735
|
+
# * `trace ... into: <file>`
|
736
|
+
# * Save trace information into: `<file>`.
|
737
|
+
# * `trace off <num>`
|
738
|
+
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
739
|
+
# * `trace off [line|call|pass]`
|
740
|
+
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
741
|
+
when 'trace'
|
742
|
+
if (re = /\s+into:\s*(.+)/) =~ arg
|
743
|
+
into = $1
|
744
|
+
arg.sub!(re, '')
|
745
|
+
end
|
746
|
+
|
747
|
+
if (re = /\s\/(.+)\/\z/) =~ arg
|
748
|
+
pattern = $1
|
749
|
+
arg.sub!(re, '')
|
750
|
+
end
|
751
|
+
|
752
|
+
case arg
|
753
|
+
when nil
|
754
|
+
@ui.puts 'Tracers:'
|
755
|
+
@tracers.each_with_index{|t, i|
|
756
|
+
@ui.puts "* \##{i} #{t}"
|
757
|
+
}
|
758
|
+
@ui.puts
|
759
|
+
return :retry
|
760
|
+
|
761
|
+
when /\Aline\z/
|
762
|
+
@tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
|
763
|
+
@ui.puts "Enable #{t.to_s}"
|
764
|
+
return :retry
|
765
|
+
|
766
|
+
when /\Acall\z/
|
767
|
+
@tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
|
768
|
+
@ui.puts "Enable #{t.to_s}"
|
769
|
+
return :retry
|
770
|
+
|
771
|
+
when /\Aexception\z/
|
772
|
+
@tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
773
|
+
@ui.puts "Enable #{t.to_s}"
|
774
|
+
return :retry
|
775
|
+
|
776
|
+
when /\Aobject\s+(.+)/
|
777
|
+
@tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
778
|
+
|
779
|
+
when /\Aoff\s+(\d+)\z/
|
780
|
+
if t = @tracers[$1.to_i]
|
781
|
+
t.disable
|
782
|
+
@ui.puts "Disable #{t.to_s}"
|
783
|
+
else
|
784
|
+
@ui.puts "Unmatched: #{$1}"
|
785
|
+
end
|
786
|
+
return :retry
|
787
|
+
|
788
|
+
when /\Aoff(\s+(line|call|exception|object))?\z/
|
789
|
+
@tracers.each{|t|
|
790
|
+
if $2.nil? || t.type == $2
|
791
|
+
t.disable
|
792
|
+
@ui.puts "Disable #{t.to_s}"
|
793
|
+
end
|
794
|
+
}
|
795
|
+
return :retry
|
796
|
+
|
797
|
+
else
|
798
|
+
@ui.puts "Unknown trace option: #{arg.inspect}"
|
799
|
+
return :retry
|
800
|
+
end
|
801
|
+
|
802
|
+
# Record
|
803
|
+
# * `record`
|
804
|
+
# * Show recording status.
|
805
|
+
# * `record [on|off]`
|
806
|
+
# * Start/Stop recording.
|
807
|
+
# * `step back`
|
808
|
+
# * Start replay. Step back with the last execution log.
|
809
|
+
# * `s[tep]` does stepping forward with the last log.
|
810
|
+
# * `step reset`
|
811
|
+
# * Stop replay .
|
812
|
+
when 'record'
|
813
|
+
case arg
|
814
|
+
when nil, 'on', 'off'
|
815
|
+
@tc << [:record, arg&.to_sym]
|
816
|
+
else
|
817
|
+
@ui.puts "unknown command: #{arg}"
|
818
|
+
return :retry
|
819
|
+
end
|
820
|
+
|
563
821
|
### Thread control
|
564
822
|
|
565
823
|
# * `th[read]`
|
@@ -577,13 +835,43 @@ module DEBUGGER__
|
|
577
835
|
end
|
578
836
|
return :retry
|
579
837
|
|
838
|
+
### Configuration
|
839
|
+
# * `config`
|
840
|
+
# * Show all configuration with description.
|
841
|
+
# * `config <name>`
|
842
|
+
# * Show current configuration of <name>.
|
843
|
+
# * `config set <name> <val>` or `config <name> = <val>`
|
844
|
+
# * Set <name> to <val>.
|
845
|
+
# * `config append <name> <val>` or `config <name> << <val>`
|
846
|
+
# * Append `<val>` to `<name>` if it is an array.
|
847
|
+
# * `config unset <name>`
|
848
|
+
# * Set <name> to default.
|
849
|
+
when 'config'
|
850
|
+
config_command arg
|
851
|
+
return :retry
|
852
|
+
|
853
|
+
# * `source <file>`
|
854
|
+
# * Evaluate lines in `<file>` as debug commands.
|
855
|
+
when 'source'
|
856
|
+
if arg
|
857
|
+
begin
|
858
|
+
cmds = File.readlines(path = File.expand_path(arg))
|
859
|
+
add_preset_commands path, cmds, kick: true, continue: false
|
860
|
+
rescue Errno::ENOENT
|
861
|
+
@ui.puts "File not found: #{arg}"
|
862
|
+
end
|
863
|
+
else
|
864
|
+
show_help 'source'
|
865
|
+
end
|
866
|
+
return :retry
|
867
|
+
|
580
868
|
### Help
|
581
869
|
|
582
870
|
# * `h[elp]`
|
583
871
|
# * Show help for all commands.
|
584
872
|
# * `h[elp] <command>`
|
585
873
|
# * Show help for the given command.
|
586
|
-
when 'h', 'help'
|
874
|
+
when 'h', 'help', '?'
|
587
875
|
if arg
|
588
876
|
show_help arg
|
589
877
|
else
|
@@ -593,21 +881,129 @@ module DEBUGGER__
|
|
593
881
|
|
594
882
|
### END
|
595
883
|
else
|
596
|
-
@
|
884
|
+
@tc << [:eval, :pp, line]
|
885
|
+
=begin
|
597
886
|
@repl_prev_line = nil
|
887
|
+
@ui.puts "unknown command: #{line}"
|
888
|
+
begin
|
889
|
+
require 'did_you_mean'
|
890
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
891
|
+
correction = spell_checker.correct(line.split(/\s/).first || '')
|
892
|
+
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
893
|
+
rescue LoadError
|
894
|
+
# Don't use D
|
895
|
+
end
|
598
896
|
return :retry
|
897
|
+
=end
|
599
898
|
end
|
600
899
|
|
601
900
|
rescue Interrupt
|
602
901
|
return :retry
|
603
902
|
rescue SystemExit
|
604
903
|
raise
|
904
|
+
rescue PostmortemError => e
|
905
|
+
@ui.puts e.message
|
906
|
+
return :retry
|
605
907
|
rescue Exception => e
|
606
908
|
@ui.puts "[REPL ERROR] #{e.inspect}"
|
607
909
|
@ui.puts e.backtrace.map{|e| ' ' + e}
|
608
910
|
return :retry
|
609
911
|
end
|
610
912
|
|
913
|
+
def step_command type, arg
|
914
|
+
case arg
|
915
|
+
when nil
|
916
|
+
@tc << [:step, type]
|
917
|
+
restart_all_threads
|
918
|
+
when /\A\d+\z/
|
919
|
+
@tc << [:step, type, arg.to_i]
|
920
|
+
restart_all_threads
|
921
|
+
when /\Aback\z/, /\Areset\z/
|
922
|
+
if type != :in
|
923
|
+
@ui.puts "only `step #{arg}` is supported."
|
924
|
+
:retry
|
925
|
+
else
|
926
|
+
@tc << [:step, arg.to_sym]
|
927
|
+
end
|
928
|
+
else
|
929
|
+
@ui.puts "Unknown option: #{arg}"
|
930
|
+
:retry
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
def config_show key
|
935
|
+
key = key.to_sym
|
936
|
+
if CONFIG_SET[key]
|
937
|
+
v = CONFIG[key]
|
938
|
+
kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
|
939
|
+
desc = CONFIG_SET[key][1]
|
940
|
+
line = "%-30s \# %s" % [kv, desc]
|
941
|
+
if line.size > SESSION.width
|
942
|
+
@ui.puts "\# #{desc}\n#{kv}"
|
943
|
+
else
|
944
|
+
@ui.puts line
|
945
|
+
end
|
946
|
+
else
|
947
|
+
@ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
def config_set key, val, append: false
|
952
|
+
if CONFIG_SET[key = key.to_sym]
|
953
|
+
begin
|
954
|
+
if append
|
955
|
+
CONFIG.append_config(key, val)
|
956
|
+
else
|
957
|
+
CONFIG[key] = val
|
958
|
+
end
|
959
|
+
rescue => e
|
960
|
+
@ui.puts e.message
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
config_show key
|
965
|
+
end
|
966
|
+
|
967
|
+
def config_command arg
|
968
|
+
case arg
|
969
|
+
when nil
|
970
|
+
CONFIG_SET.each do |k, _|
|
971
|
+
config_show k
|
972
|
+
end
|
973
|
+
|
974
|
+
when /\Aunset\s+(.+)\z/
|
975
|
+
if CONFIG_SET[key = $1.to_sym]
|
976
|
+
CONFIG[key] = nil
|
977
|
+
end
|
978
|
+
config_show key
|
979
|
+
|
980
|
+
when /\A(\w+)\s*=\s*(.+)\z/
|
981
|
+
config_set $1, $2
|
982
|
+
|
983
|
+
when /\A\s*set\s+(\w+)\s+(.+)\z/
|
984
|
+
config_set $1, $2
|
985
|
+
|
986
|
+
when /\A(\w+)\s*<<\s*(.+)\z/
|
987
|
+
config_set $1, $2, append: true
|
988
|
+
|
989
|
+
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
990
|
+
config_set $1, $2
|
991
|
+
|
992
|
+
when /\A(\w+)\z/
|
993
|
+
config_show $1
|
994
|
+
|
995
|
+
else
|
996
|
+
@ui.puts "Can not parse parameters: #{arg}"
|
997
|
+
end
|
998
|
+
end
|
999
|
+
|
1000
|
+
|
1001
|
+
def cancel_auto_continue
|
1002
|
+
if @preset_command&.auto_continue
|
1003
|
+
@preset_command.auto_continue = false
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
611
1007
|
def show_help arg
|
612
1008
|
DEBUGGER__.helps.each{|cat, cs|
|
613
1009
|
cs.each{|ws, desc|
|
@@ -632,51 +1028,7 @@ module DEBUGGER__
|
|
632
1028
|
end
|
633
1029
|
end
|
634
1030
|
|
635
|
-
|
636
|
-
if klass.singleton_class?
|
637
|
-
"#{receiver}."
|
638
|
-
else
|
639
|
-
"#{klass}#"
|
640
|
-
end
|
641
|
-
end
|
642
|
-
|
643
|
-
def pretty_tp tp
|
644
|
-
loc = "#{tp.path}:#{tp.lineno}"
|
645
|
-
level = caller.size
|
646
|
-
|
647
|
-
info =
|
648
|
-
case tp.event
|
649
|
-
when :line
|
650
|
-
"line at #{loc}"
|
651
|
-
when :call, :c_call
|
652
|
-
klass = tp.defined_class
|
653
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} at #{loc}"
|
654
|
-
when :return, :c_return
|
655
|
-
klass = tp.defined_class
|
656
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} => #{tp.return_value.inspect} at #{loc}"
|
657
|
-
when :b_call
|
658
|
-
"b_call at #{loc}"
|
659
|
-
when :b_return
|
660
|
-
"b_return => #{tp.return_value} at #{loc}"
|
661
|
-
when :class
|
662
|
-
"class #{tp.self} at #{loc}"
|
663
|
-
when :end
|
664
|
-
"class #{tp.self} end at #{loc}"
|
665
|
-
else
|
666
|
-
"#{tp.event} at #{loc}"
|
667
|
-
end
|
668
|
-
|
669
|
-
case tp.event
|
670
|
-
when :call, :b_call, :return, :b_return, :class, :end
|
671
|
-
level -= 1
|
672
|
-
end
|
673
|
-
|
674
|
-
"Tracing:#{' ' * level} #{info}"
|
675
|
-
rescue => e
|
676
|
-
p e
|
677
|
-
pp e.backtrace
|
678
|
-
exit!
|
679
|
-
end
|
1031
|
+
# breakpoint management
|
680
1032
|
|
681
1033
|
def iterate_bps
|
682
1034
|
deleted_bps = []
|
@@ -708,7 +1060,29 @@ module DEBUGGER__
|
|
708
1060
|
nil
|
709
1061
|
end
|
710
1062
|
|
711
|
-
def
|
1063
|
+
def rehash_bps
|
1064
|
+
bps = @bps.values
|
1065
|
+
@bps.clear
|
1066
|
+
bps.each{|bp|
|
1067
|
+
add_bp bp
|
1068
|
+
}
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
def add_bp bp
|
1072
|
+
# don't repeat commands that add breakpoints
|
1073
|
+
@repl_prev_line = nil
|
1074
|
+
|
1075
|
+
if @bps.has_key? bp.key
|
1076
|
+
unless bp.duplicable?
|
1077
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
1078
|
+
bp.disable
|
1079
|
+
end
|
1080
|
+
else
|
1081
|
+
@bps[bp.key] = bp
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def delete_bp arg = nil
|
712
1086
|
case arg
|
713
1087
|
when nil
|
714
1088
|
@bps.each{|key, bp| bp.delete}
|
@@ -724,26 +1098,34 @@ module DEBUGGER__
|
|
724
1098
|
end
|
725
1099
|
end
|
726
1100
|
|
727
|
-
|
728
|
-
arg.strip!
|
1101
|
+
BREAK_KEYWORDS = %w(if: do: pre:).freeze
|
729
1102
|
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
1103
|
+
def parse_break arg
|
1104
|
+
mode = :sig
|
1105
|
+
expr = Hash.new{|h, k| h[k] = []}
|
1106
|
+
arg.split(' ').each{|w|
|
1107
|
+
if BREAK_KEYWORDS.any?{|pat| w == pat}
|
1108
|
+
mode = w[0..-2].to_sym
|
1109
|
+
else
|
1110
|
+
expr[mode] << w
|
1111
|
+
end
|
1112
|
+
}
|
1113
|
+
expr.default_proc = nil
|
1114
|
+
expr.transform_values{|v| v.join(' ')}
|
1115
|
+
end
|
739
1116
|
|
740
|
-
|
1117
|
+
def repl_add_breakpoint arg
|
1118
|
+
expr = parse_break arg.strip
|
1119
|
+
cond = expr[:if]
|
1120
|
+
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1121
|
+
|
1122
|
+
case expr[:sig]
|
741
1123
|
when /\A(\d+)\z/
|
742
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
|
1124
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
|
743
1125
|
when /\A(.+)[:\s+](\d+)\z/
|
744
|
-
add_line_breakpoint $1, $2.to_i, cond: cond
|
1126
|
+
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
745
1127
|
when /\A(.+)([\.\#])(.+)\z/
|
746
|
-
@tc << [:breakpoint, :method, $1, $2, $3, cond]
|
1128
|
+
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
|
747
1129
|
return :noretry
|
748
1130
|
when nil
|
749
1131
|
add_check_breakpoint cond
|
@@ -754,6 +1136,29 @@ module DEBUGGER__
|
|
754
1136
|
end
|
755
1137
|
end
|
756
1138
|
|
1139
|
+
def repl_add_catch_breakpoint arg
|
1140
|
+
expr = parse_break arg.strip
|
1141
|
+
cond = expr[:if]
|
1142
|
+
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1143
|
+
|
1144
|
+
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
|
1145
|
+
add_bp bp
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
def add_check_breakpoint expr
|
1149
|
+
bp = CheckBreakpoint.new(expr)
|
1150
|
+
add_bp bp
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
def add_line_breakpoint file, line, **kw
|
1154
|
+
file = resolve_path(file)
|
1155
|
+
bp = LineBreakpoint.new(file, line, **kw)
|
1156
|
+
|
1157
|
+
add_bp bp
|
1158
|
+
rescue Errno::ENOENT => e
|
1159
|
+
@ui.puts e.message
|
1160
|
+
end
|
1161
|
+
|
757
1162
|
# threads
|
758
1163
|
|
759
1164
|
def update_thread_list
|
@@ -762,17 +1167,15 @@ module DEBUGGER__
|
|
762
1167
|
unmanaged = []
|
763
1168
|
|
764
1169
|
list.each{|th|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
# ignore
|
770
|
-
when @th_clients.has_key?(th)
|
771
|
-
thcs << @th_clients[th]
|
1170
|
+
if thc = @th_clients[th]
|
1171
|
+
if !thc.management?
|
1172
|
+
thcs << thc
|
1173
|
+
end
|
772
1174
|
else
|
773
1175
|
unmanaged << th
|
774
1176
|
end
|
775
1177
|
}
|
1178
|
+
|
776
1179
|
return thcs.sort_by{|thc| thc.id}, unmanaged
|
777
1180
|
end
|
778
1181
|
|
@@ -799,7 +1202,7 @@ module DEBUGGER__
|
|
799
1202
|
thcs, _unmanaged_ths = update_thread_list
|
800
1203
|
|
801
1204
|
if tc = thcs[n]
|
802
|
-
if tc.
|
1205
|
+
if tc.waiting?
|
803
1206
|
@tc = tc
|
804
1207
|
else
|
805
1208
|
@ui.puts "#{tc.thread} is not controllable yet."
|
@@ -813,11 +1216,11 @@ module DEBUGGER__
|
|
813
1216
|
end
|
814
1217
|
|
815
1218
|
def setup_threads
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
1219
|
+
@th_clients = {}
|
1220
|
+
|
1221
|
+
Thread.list.each{|th|
|
1222
|
+
thread_client_create(th)
|
1223
|
+
}
|
821
1224
|
end
|
822
1225
|
|
823
1226
|
def on_thread_begin th
|
@@ -829,8 +1232,7 @@ module DEBUGGER__
|
|
829
1232
|
end
|
830
1233
|
end
|
831
1234
|
|
832
|
-
def thread_client
|
833
|
-
thr = Thread.current
|
1235
|
+
def thread_client thr = Thread.current
|
834
1236
|
if @th_clients.has_key? thr
|
835
1237
|
@th_clients[thr]
|
836
1238
|
else
|
@@ -838,77 +1240,67 @@ module DEBUGGER__
|
|
838
1240
|
end
|
839
1241
|
end
|
840
1242
|
|
841
|
-
def
|
842
|
-
|
1243
|
+
private def thread_stopper
|
1244
|
+
@thread_stopper ||= TracePoint.new(:line) do
|
1245
|
+
# run on each thread
|
1246
|
+
tc = ThreadClient.current
|
1247
|
+
next if tc.management?
|
1248
|
+
next unless tc.running?
|
1249
|
+
next if tc == @tc
|
843
1250
|
|
844
|
-
|
845
|
-
TracePoint.new(:line) do
|
846
|
-
th = Thread.current
|
847
|
-
if current == th || @management_threads.include?(th)
|
848
|
-
next
|
849
|
-
else
|
850
|
-
tc = ThreadClient.current
|
851
|
-
tc.on_pause
|
852
|
-
end
|
853
|
-
end.enable do
|
854
|
-
yield
|
855
|
-
ensure
|
856
|
-
@th_clients.each{|thr, tc|
|
857
|
-
case thr
|
858
|
-
when current, (@tc && @tc.thread)
|
859
|
-
next
|
860
|
-
else
|
861
|
-
tc << :continue if thr != Thread.current
|
862
|
-
end
|
863
|
-
}
|
864
|
-
end
|
865
|
-
else
|
866
|
-
yield
|
1251
|
+
tc.on_pause
|
867
1252
|
end
|
868
1253
|
end
|
869
1254
|
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
1255
|
+
private def running_thread_clients_count
|
1256
|
+
@th_clients.count{|th, tc|
|
1257
|
+
next if tc.management?
|
1258
|
+
next unless tc.running?
|
1259
|
+
true
|
1260
|
+
}
|
1261
|
+
end
|
874
1262
|
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
1263
|
+
private def waiting_thread_clients
|
1264
|
+
@th_clients.map{|th, tc|
|
1265
|
+
next if tc.management?
|
1266
|
+
next unless tc.waiting?
|
1267
|
+
tc
|
1268
|
+
}.compact
|
880
1269
|
end
|
881
1270
|
|
882
|
-
|
1271
|
+
private def stop_all_threads
|
1272
|
+
return if running_thread_clients_count == 0
|
883
1273
|
|
884
|
-
|
885
|
-
|
886
|
-
@ui.puts "duplicated breakpoint: #{bp}"
|
887
|
-
else
|
888
|
-
@bps[bp.key] = bp
|
889
|
-
end
|
1274
|
+
stopper = thread_stopper
|
1275
|
+
stopper.enable unless stopper.enabled?
|
890
1276
|
end
|
891
1277
|
|
892
|
-
def
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
1278
|
+
private def restart_all_threads
|
1279
|
+
stopper = thread_stopper
|
1280
|
+
stopper.disable if stopper.enabled?
|
1281
|
+
|
1282
|
+
waiting_thread_clients.each{|tc|
|
1283
|
+
next if @tc == tc
|
1284
|
+
tc << :continue
|
897
1285
|
}
|
1286
|
+
@tc = nil
|
898
1287
|
end
|
899
1288
|
|
900
|
-
|
901
|
-
@bps.has_key? [file, line]
|
902
|
-
end
|
1289
|
+
## event
|
903
1290
|
|
904
|
-
def
|
905
|
-
|
906
|
-
|
907
|
-
end
|
1291
|
+
def on_load iseq, src
|
1292
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
1293
|
+
@sr.add iseq, src
|
908
1294
|
|
909
|
-
|
910
|
-
|
911
|
-
|
1295
|
+
pending_line_breakpoints = @bps.find_all do |key, bp|
|
1296
|
+
LineBreakpoint === bp && !bp.iseq
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
pending_line_breakpoints.each do |_key, bp|
|
1300
|
+
if bp.path == (iseq.absolute_path || iseq.path)
|
1301
|
+
bp.try_activate
|
1302
|
+
end
|
1303
|
+
end
|
912
1304
|
end
|
913
1305
|
|
914
1306
|
def resolve_path file
|
@@ -929,22 +1321,6 @@ module DEBUGGER__
|
|
929
1321
|
raise
|
930
1322
|
end
|
931
1323
|
|
932
|
-
def add_line_breakpoint file, line, **kw
|
933
|
-
file = resolve_path(file)
|
934
|
-
bp = LineBreakpoint.new(file, line, **kw)
|
935
|
-
add_breakpoint bp
|
936
|
-
rescue Errno::ENOENT => e
|
937
|
-
@ui.puts e.message
|
938
|
-
end
|
939
|
-
|
940
|
-
def pending_line_breakpoints
|
941
|
-
@bps.find_all do |key, bp|
|
942
|
-
LineBreakpoint === bp && !bp.iseq
|
943
|
-
end.each do |key, bp|
|
944
|
-
yield bp
|
945
|
-
end
|
946
|
-
end
|
947
|
-
|
948
1324
|
def method_added tp
|
949
1325
|
b = tp.binding
|
950
1326
|
if var_name = b.local_variables.first
|
@@ -956,7 +1332,7 @@ module DEBUGGER__
|
|
956
1332
|
when MethodBreakpoint
|
957
1333
|
if bp.method.nil?
|
958
1334
|
if bp.sig_method_name == mid.to_s
|
959
|
-
bp.try_enable(
|
1335
|
+
bp.try_enable(added: true)
|
960
1336
|
end
|
961
1337
|
end
|
962
1338
|
|
@@ -973,10 +1349,55 @@ module DEBUGGER__
|
|
973
1349
|
@ui.width
|
974
1350
|
end
|
975
1351
|
|
976
|
-
def
|
977
|
-
|
978
|
-
|
979
|
-
|
1352
|
+
def check_postmortem
|
1353
|
+
if @postmortem
|
1354
|
+
raise PostmortemError, "Can not use this command on postmortem mode."
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
def enter_postmortem_session frames
|
1359
|
+
@postmortem = true
|
1360
|
+
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
1361
|
+
ensure
|
1362
|
+
@postmortem = false
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def postmortem=(is_enable)
|
1366
|
+
if is_enable
|
1367
|
+
unless @postmortem_hook
|
1368
|
+
@postmortem_hook = TracePoint.new(:raise){|tp|
|
1369
|
+
exc = tp.raised_exception
|
1370
|
+
frames = DEBUGGER__.capture_frames(__dir__)
|
1371
|
+
exc.instance_variable_set(:@postmortem_frames, frames)
|
1372
|
+
}
|
1373
|
+
at_exit{
|
1374
|
+
@postmortem_hook.disable
|
1375
|
+
if CONFIG[:postmortem] && (exc = $!) != nil
|
1376
|
+
exc = exc.cause while exc.cause
|
1377
|
+
|
1378
|
+
begin
|
1379
|
+
@ui.puts "Enter postmortem mode with #{exc.inspect}"
|
1380
|
+
@ui.puts exc.backtrace.map{|e| ' ' + e}
|
1381
|
+
@ui.puts "\n"
|
1382
|
+
|
1383
|
+
enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
|
1384
|
+
rescue SystemExit
|
1385
|
+
exit!
|
1386
|
+
rescue Exception => e
|
1387
|
+
@ui = STDERR unless @ui
|
1388
|
+
@ui.puts "Error while postmortem console: #{e.inspect}"
|
1389
|
+
end
|
1390
|
+
end
|
1391
|
+
}
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
if !@postmortem_hook.enabled?
|
1395
|
+
@postmortem_hook.enable
|
1396
|
+
end
|
1397
|
+
else
|
1398
|
+
if @postmortem_hook && @postmortem_hook.enabled?
|
1399
|
+
@postmortem_hook.disable
|
1400
|
+
end
|
980
1401
|
end
|
981
1402
|
end
|
982
1403
|
end
|
@@ -1022,44 +1443,71 @@ module DEBUGGER__
|
|
1022
1443
|
|
1023
1444
|
# start methods
|
1024
1445
|
|
1025
|
-
def self.
|
1026
|
-
set_config(kw)
|
1027
|
-
|
1028
|
-
require_relative 'console'
|
1446
|
+
def self.start nonstop: false, **kw
|
1447
|
+
CONFIG.set_config(**kw)
|
1029
1448
|
|
1030
|
-
|
1449
|
+
unless defined? SESSION
|
1450
|
+
require_relative 'local'
|
1451
|
+
initialize_session UI_LocalConsole.new
|
1452
|
+
end
|
1031
1453
|
|
1032
|
-
|
1033
|
-
ThreadClient.current.on_trap :SIGINT
|
1034
|
-
}
|
1454
|
+
setup_initial_suspend unless nonstop
|
1035
1455
|
end
|
1036
1456
|
|
1037
|
-
def self.open host: nil, port:
|
1038
|
-
set_config(kw)
|
1457
|
+
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1458
|
+
CONFIG.set_config(**kw)
|
1039
1459
|
|
1040
1460
|
if port
|
1041
|
-
open_tcp host: host, port: port
|
1461
|
+
open_tcp host: host, port: port, nonstop: nonstop
|
1042
1462
|
else
|
1043
|
-
open_unix sock_path: sock_path, sock_dir: sock_dir
|
1463
|
+
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
1044
1464
|
end
|
1045
1465
|
end
|
1046
1466
|
|
1047
|
-
def self.open_tcp host: nil, port:, **kw
|
1048
|
-
set_config(kw)
|
1467
|
+
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
1468
|
+
CONFIG.set_config(**kw)
|
1049
1469
|
require_relative 'server'
|
1050
|
-
|
1470
|
+
|
1471
|
+
if defined? SESSION
|
1472
|
+
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
1473
|
+
else
|
1474
|
+
initialize_session UI_TcpServer.new(host: host, port: port)
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
setup_initial_suspend unless nonstop
|
1051
1478
|
end
|
1052
1479
|
|
1053
|
-
def self.open_unix sock_path: nil, sock_dir: nil, **kw
|
1054
|
-
set_config(kw)
|
1480
|
+
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1481
|
+
CONFIG.set_config(**kw)
|
1055
1482
|
require_relative 'server'
|
1056
|
-
|
1483
|
+
|
1484
|
+
if defined? SESSION
|
1485
|
+
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1486
|
+
else
|
1487
|
+
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
setup_initial_suspend unless nonstop
|
1057
1491
|
end
|
1058
1492
|
|
1059
1493
|
# boot utilities
|
1060
1494
|
|
1495
|
+
def self.setup_initial_suspend
|
1496
|
+
if !CONFIG[:nonstop]
|
1497
|
+
if loc = ::DEBUGGER__.require_location
|
1498
|
+
# require 'debug/start' or 'debug'
|
1499
|
+
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
1500
|
+
else
|
1501
|
+
# -r
|
1502
|
+
add_line_breakpoint $0, 0, oneshot: true, hook_call: false
|
1503
|
+
end
|
1504
|
+
end
|
1505
|
+
end
|
1506
|
+
|
1061
1507
|
class << self
|
1062
1508
|
define_method :initialize_session do |ui|
|
1509
|
+
DEBUGGER__.warn "Session start (pid: #{Process.pid})"
|
1510
|
+
|
1063
1511
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
1064
1512
|
|
1065
1513
|
# default breakpoints
|
@@ -1067,25 +1515,18 @@ module DEBUGGER__
|
|
1067
1515
|
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
1068
1516
|
|
1069
1517
|
Binding.module_eval do
|
1070
|
-
def
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1518
|
+
def break pre: nil, do: nil
|
1519
|
+
return unless SESSION.active?
|
1520
|
+
|
1521
|
+
if pre || (do_expr = binding.local_variable_get(:do))
|
1522
|
+
cmds = ['binding.break', pre, do_expr]
|
1074
1523
|
end
|
1075
1524
|
|
1076
|
-
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true
|
1525
|
+
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
1077
1526
|
true
|
1078
1527
|
end
|
1079
|
-
|
1080
|
-
|
1081
|
-
if !::DEBUGGER__::CONFIG[:nonstop]
|
1082
|
-
if loc = ::DEBUGGER__.require_location
|
1083
|
-
# require 'debug/console' or 'debug'
|
1084
|
-
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
1085
|
-
else
|
1086
|
-
# -r
|
1087
|
-
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
1088
|
-
end
|
1528
|
+
alias b break
|
1529
|
+
# alias bp break
|
1089
1530
|
end
|
1090
1531
|
|
1091
1532
|
load_rc
|
@@ -1093,80 +1534,30 @@ module DEBUGGER__
|
|
1093
1534
|
end
|
1094
1535
|
|
1095
1536
|
def self.load_rc
|
1096
|
-
[
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
[init_script = ::DEBUGGER__::CONFIG[:init_script],
|
1104
|
-
'./.rdbgrc',
|
1105
|
-
File.expand_path('~/.rdbgrc')].each{|path|
|
1106
|
-
next unless path
|
1537
|
+
[[File.expand_path('~/.rdbgrc'), true],
|
1538
|
+
[File.expand_path('~/.rdbgrc.rb'), true],
|
1539
|
+
# ['./.rdbgrc', true], # disable because of security concern
|
1540
|
+
[CONFIG[:init_script], false],
|
1541
|
+
].each{|(path, rc)|
|
1542
|
+
next unless path
|
1543
|
+
next if rc && CONFIG[:no_rc] # ignore rc
|
1107
1544
|
|
1108
1545
|
if File.file? path
|
1109
|
-
|
1110
|
-
|
1546
|
+
if path.end_with?('.rb')
|
1547
|
+
load path
|
1548
|
+
else
|
1549
|
+
::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
|
1550
|
+
end
|
1551
|
+
elsif !rc
|
1111
1552
|
warn "Not found: #{path}"
|
1112
1553
|
end
|
1113
1554
|
}
|
1114
1555
|
|
1115
1556
|
# given debug commands
|
1116
|
-
if
|
1117
|
-
cmds =
|
1118
|
-
::DEBUGGER__::SESSION.
|
1119
|
-
end
|
1120
|
-
end
|
1121
|
-
|
1122
|
-
def self.parse_help
|
1123
|
-
helps = Hash.new{|h, k| h[k] = []}
|
1124
|
-
desc = cat = nil
|
1125
|
-
cmds = []
|
1126
|
-
|
1127
|
-
File.read(__FILE__).each_line do |line|
|
1128
|
-
case line
|
1129
|
-
when /\A\s*### (.+)/
|
1130
|
-
cat = $1
|
1131
|
-
break if $1 == 'END'
|
1132
|
-
when /\A when (.+)/
|
1133
|
-
next unless cat
|
1134
|
-
next unless desc
|
1135
|
-
ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
|
1136
|
-
helps[cat] << [ws, desc]
|
1137
|
-
desc = nil
|
1138
|
-
cmds.concat ws
|
1139
|
-
when /\A\s+# (\s*\*.+)/
|
1140
|
-
if desc
|
1141
|
-
desc << "\n" + $1
|
1142
|
-
else
|
1143
|
-
desc = $1
|
1144
|
-
end
|
1145
|
-
end
|
1557
|
+
if CONFIG[:commands]
|
1558
|
+
cmds = CONFIG[:commands].split(';;')
|
1559
|
+
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
1146
1560
|
end
|
1147
|
-
@commands = cmds
|
1148
|
-
@helps = helps
|
1149
|
-
end
|
1150
|
-
|
1151
|
-
def self.helps
|
1152
|
-
(defined?(@helps) && @helps) || parse_help
|
1153
|
-
end
|
1154
|
-
|
1155
|
-
def self.commands
|
1156
|
-
(defined?(@commands) && @commands) || (parse_help; @commands)
|
1157
|
-
end
|
1158
|
-
|
1159
|
-
def self.help
|
1160
|
-
r = []
|
1161
|
-
self.helps.each{|cat, cmds|
|
1162
|
-
r << "### #{cat}"
|
1163
|
-
r << ''
|
1164
|
-
cmds.each{|ws, desc|
|
1165
|
-
r << desc
|
1166
|
-
}
|
1167
|
-
r << ''
|
1168
|
-
}
|
1169
|
-
r.join("\n")
|
1170
1561
|
end
|
1171
1562
|
|
1172
1563
|
class ::Module
|
@@ -1194,4 +1585,99 @@ module DEBUGGER__
|
|
1194
1585
|
str
|
1195
1586
|
end
|
1196
1587
|
end
|
1588
|
+
|
1589
|
+
LOG_LEVELS = {
|
1590
|
+
UNKNOWN: 0,
|
1591
|
+
FATAL: 1,
|
1592
|
+
ERROR: 2,
|
1593
|
+
WARN: 3,
|
1594
|
+
INFO: 4,
|
1595
|
+
}.freeze
|
1596
|
+
|
1597
|
+
def self.warn msg
|
1598
|
+
log :WARN, msg
|
1599
|
+
end
|
1600
|
+
|
1601
|
+
def self.info msg
|
1602
|
+
log :INFO, msg
|
1603
|
+
end
|
1604
|
+
|
1605
|
+
def self.log level, msg
|
1606
|
+
lv = LOG_LEVELS[level]
|
1607
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
|
1608
|
+
|
1609
|
+
if lv <= config_lv
|
1610
|
+
if level == :WARN
|
1611
|
+
# :WARN on debugger is general information
|
1612
|
+
STDERR.puts "DEBUGGER: #{msg}"
|
1613
|
+
else
|
1614
|
+
STDERR.puts "DEBUGGER (#{level}): #{msg}"
|
1615
|
+
end
|
1616
|
+
end
|
1617
|
+
end
|
1618
|
+
|
1619
|
+
module ForkInterceptor
|
1620
|
+
def fork(&given_block)
|
1621
|
+
return super unless defined?(SESSION) && SESSION.active?
|
1622
|
+
|
1623
|
+
# before fork
|
1624
|
+
if CONFIG[:parent_on_fork]
|
1625
|
+
parent_hook = -> child_pid {
|
1626
|
+
# Do nothing
|
1627
|
+
}
|
1628
|
+
child_hook = -> {
|
1629
|
+
DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
|
1630
|
+
SESSION.deactivate
|
1631
|
+
}
|
1632
|
+
else
|
1633
|
+
parent_pid = Process.pid
|
1634
|
+
|
1635
|
+
parent_hook = -> child_pid {
|
1636
|
+
DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
|
1637
|
+
SESSION.deactivate
|
1638
|
+
|
1639
|
+
at_exit{
|
1640
|
+
trap(:SIGINT, :IGNORE)
|
1641
|
+
Process.waitpid(child_pid)
|
1642
|
+
}
|
1643
|
+
}
|
1644
|
+
child_hook = -> {
|
1645
|
+
DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
1646
|
+
SESSION.activate on_fork: true
|
1647
|
+
}
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
if given_block
|
1651
|
+
new_block = proc {
|
1652
|
+
# after fork: child
|
1653
|
+
child_hook.call
|
1654
|
+
given_block.call
|
1655
|
+
}
|
1656
|
+
pid = super(&new_block)
|
1657
|
+
parent_hook.call(pid)
|
1658
|
+
pid
|
1659
|
+
else
|
1660
|
+
if pid = super
|
1661
|
+
# after fork: parent
|
1662
|
+
parent_hook.call pid
|
1663
|
+
else
|
1664
|
+
# after fork: child
|
1665
|
+
child_hook.call
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
pid
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
class ::Object
|
1674
|
+
include ForkInterceptor
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
module ::Process
|
1678
|
+
class << self
|
1679
|
+
prepend ForkInterceptor
|
1680
|
+
end
|
1681
|
+
end
|
1197
1682
|
end
|
1683
|
+
|