debug 1.0.0.beta8 → 1.1.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/CONTRIBUTING.md +108 -106
- data/README.md +107 -40
- data/Rakefile +1 -1
- data/TODO.md +4 -8
- data/debug.gemspec +1 -0
- data/exe/rdbg +4 -7
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +55 -22
- data/lib/debug/client.rb +7 -12
- data/lib/debug/color.rb +19 -4
- data/lib/debug/config.rb +362 -177
- data/lib/debug/console.rb +76 -68
- data/lib/debug/frame_info.rb +40 -7
- data/lib/debug/local.rb +91 -0
- data/lib/debug/server.rb +74 -26
- data/lib/debug/server_dap.rb +46 -12
- data/lib/debug/session.rb +637 -310
- data/lib/debug/thread_client.rb +620 -162
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +41 -33
- metadata +20 -4
data/lib/debug/server_dap.rb
CHANGED
|
@@ -7,7 +7,7 @@ module DEBUGGER__
|
|
|
7
7
|
SHOW_PROTOCOL = ENV['RUBY_DEBUG_DAP_SHOW_PROTOCOL'] == '1'
|
|
8
8
|
|
|
9
9
|
def dap_setup bytes
|
|
10
|
-
|
|
10
|
+
CONFIG.set_config no_color: true
|
|
11
11
|
@seq = 0
|
|
12
12
|
|
|
13
13
|
$stderr.puts '[>]' + bytes if SHOW_PROTOCOL
|
|
@@ -37,6 +37,7 @@ module DEBUGGER__
|
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
supportsExceptionFilterOptions: true,
|
|
40
|
+
supportsStepBack: true,
|
|
40
41
|
|
|
41
42
|
## Will be supported
|
|
42
43
|
# supportsExceptionOptions: true,
|
|
@@ -50,7 +51,6 @@ module DEBUGGER__
|
|
|
50
51
|
# supportsBreakpointLocationsRequest:
|
|
51
52
|
|
|
52
53
|
## Possible?
|
|
53
|
-
# supportsStepBack:
|
|
54
54
|
# supportsRestartFrame:
|
|
55
55
|
# supportsCompletionsRequest:
|
|
56
56
|
# completionTriggerCharacters:
|
|
@@ -153,8 +153,8 @@ module DEBUGGER__
|
|
|
153
153
|
when 'setFunctionBreakpoints'
|
|
154
154
|
send_response req
|
|
155
155
|
when 'setExceptionBreakpoints'
|
|
156
|
-
|
|
157
|
-
case
|
|
156
|
+
process_filter = ->(filter_id) {
|
|
157
|
+
case filter_id
|
|
158
158
|
when 'any'
|
|
159
159
|
bp = SESSION.add_catch_breakpoint 'Exception'
|
|
160
160
|
when 'RuntimeError'
|
|
@@ -163,10 +163,19 @@ module DEBUGGER__
|
|
|
163
163
|
bp = nil
|
|
164
164
|
end
|
|
165
165
|
{
|
|
166
|
-
|
|
166
|
+
verified: bp ? true : false,
|
|
167
167
|
message: bp.inspect,
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
filters = args.fetch('filters').map {|filter_id|
|
|
172
|
+
process_filter.call(filter_id)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
filters += args.fetch('filterOptions', {}).map{|bp_info|
|
|
176
|
+
process_filter.call(bp_info.dig('filterId'))
|
|
177
|
+
}
|
|
178
|
+
|
|
170
179
|
send_response req, breakpoints: filters
|
|
171
180
|
when 'configurationDone'
|
|
172
181
|
send_response req
|
|
@@ -197,6 +206,12 @@ module DEBUGGER__
|
|
|
197
206
|
when 'pause'
|
|
198
207
|
send_response req
|
|
199
208
|
Process.kill(:SIGINT, Process.pid)
|
|
209
|
+
when 'reverseContinue'
|
|
210
|
+
send_response req,
|
|
211
|
+
success: false, message: 'cancelled',
|
|
212
|
+
result: "Reverse Continue is not supported. Only \"Step back\" is supported."
|
|
213
|
+
when 'stepBack'
|
|
214
|
+
@q_msg << req
|
|
200
215
|
|
|
201
216
|
## query
|
|
202
217
|
when 'threads'
|
|
@@ -212,6 +227,7 @@ module DEBUGGER__
|
|
|
212
227
|
'evaluate',
|
|
213
228
|
'source'
|
|
214
229
|
@q_msg << req
|
|
230
|
+
|
|
215
231
|
else
|
|
216
232
|
raise "Unknown request: #{req.inspect}"
|
|
217
233
|
end
|
|
@@ -220,7 +236,7 @@ module DEBUGGER__
|
|
|
220
236
|
|
|
221
237
|
## called by the SESSION thread
|
|
222
238
|
|
|
223
|
-
def readline
|
|
239
|
+
def readline prompt
|
|
224
240
|
@q_msg.pop || 'kill!'
|
|
225
241
|
end
|
|
226
242
|
|
|
@@ -281,6 +297,13 @@ module DEBUGGER__
|
|
|
281
297
|
|
|
282
298
|
def process_dap_request req
|
|
283
299
|
case req['command']
|
|
300
|
+
when 'stepBack'
|
|
301
|
+
if @tc.recorder&.can_step_back?
|
|
302
|
+
@tc << [:step, :back]
|
|
303
|
+
else
|
|
304
|
+
fail_response req, message: 'cancelled'
|
|
305
|
+
end
|
|
306
|
+
|
|
284
307
|
when 'stackTrace'
|
|
285
308
|
tid = req.dig('arguments', 'threadId')
|
|
286
309
|
if tc = find_tc(tid)
|
|
@@ -340,7 +363,7 @@ module DEBUGGER__
|
|
|
340
363
|
fail_response req
|
|
341
364
|
end
|
|
342
365
|
else
|
|
343
|
-
raise "
|
|
366
|
+
raise "Unknown type: #{ref.inspect}"
|
|
344
367
|
end
|
|
345
368
|
else
|
|
346
369
|
fail_response req
|
|
@@ -365,7 +388,6 @@ module DEBUGGER__
|
|
|
365
388
|
else
|
|
366
389
|
fail_response req, message: 'not found...'
|
|
367
390
|
end
|
|
368
|
-
|
|
369
391
|
return :retry
|
|
370
392
|
else
|
|
371
393
|
raise "Unknown DAP request: #{req.inspect}"
|
|
@@ -438,8 +460,8 @@ module DEBUGGER__
|
|
|
438
460
|
when :backtrace
|
|
439
461
|
event! :dap_result, :backtrace, req, {
|
|
440
462
|
stackFrames: @target_frames.map.with_index{|frame, i|
|
|
441
|
-
path = frame.realpath
|
|
442
|
-
ref = frame.file_lines unless File.exist?(path)
|
|
463
|
+
path = frame.realpath || frame.path
|
|
464
|
+
ref = frame.file_lines unless path && File.exist?(path)
|
|
443
465
|
|
|
444
466
|
{
|
|
445
467
|
# id: ??? # filled by SESSION
|
|
@@ -457,7 +479,15 @@ module DEBUGGER__
|
|
|
457
479
|
when :scopes
|
|
458
480
|
fid = args.shift
|
|
459
481
|
frame = @target_frames[fid]
|
|
460
|
-
|
|
482
|
+
|
|
483
|
+
lnum =
|
|
484
|
+
if frame.binding
|
|
485
|
+
frame.binding.local_variables.size
|
|
486
|
+
elsif vars = frame.local_variables
|
|
487
|
+
vars.size
|
|
488
|
+
else
|
|
489
|
+
0
|
|
490
|
+
end
|
|
461
491
|
|
|
462
492
|
event! :dap_result, :scopes, req, scopes: [{
|
|
463
493
|
name: 'Local variables',
|
|
@@ -485,6 +515,10 @@ module DEBUGGER__
|
|
|
485
515
|
vars.unshift variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
|
486
516
|
vars.unshift variable('%return', frame.return_value) if frame.has_return_value
|
|
487
517
|
vars.unshift variable('%self', b.receiver)
|
|
518
|
+
elsif lvars = frame.local_variables
|
|
519
|
+
vars = lvars.map{|var, val|
|
|
520
|
+
variable(var, val)
|
|
521
|
+
}
|
|
488
522
|
else
|
|
489
523
|
vars = [variable('%self', frame.self)]
|
|
490
524
|
vars.push variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
|
@@ -555,7 +589,7 @@ module DEBUGGER__
|
|
|
555
589
|
end
|
|
556
590
|
event! :dap_result, :evaluate, req, tid: self.id, **evaluate_result(result)
|
|
557
591
|
else
|
|
558
|
-
raise "
|
|
592
|
+
raise "Unknown req: #{args.inspect}"
|
|
559
593
|
end
|
|
560
594
|
end
|
|
561
595
|
|
data/lib/debug/session.rb
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# skip to load debugger for bundle exec
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
6
|
+
trace_var(:$0) do |file|
|
|
7
|
+
trace_var(:$0, nil)
|
|
8
|
+
if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
|
|
9
|
+
lib = $1
|
|
10
|
+
$LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
|
|
11
|
+
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
|
|
12
|
+
require lib
|
|
13
|
+
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
end
|
|
5
19
|
|
|
6
20
|
require_relative 'config'
|
|
7
21
|
require_relative 'thread_client'
|
|
8
22
|
require_relative 'source_repository'
|
|
9
23
|
require_relative 'breakpoint'
|
|
24
|
+
require_relative 'tracer'
|
|
25
|
+
|
|
26
|
+
# To prevent loading old lib/debug.rb in Ruby 2.6 to 3.0
|
|
27
|
+
$LOADED_FEATURES << 'debug.rb'
|
|
28
|
+
require 'debug' # invalidate the $LOADED_FEATURE cache
|
|
10
29
|
|
|
11
30
|
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
|
12
31
|
|
|
@@ -54,6 +73,7 @@ end
|
|
|
54
73
|
|
|
55
74
|
module DEBUGGER__
|
|
56
75
|
PresetCommand = Struct.new(:commands, :source, :auto_continue)
|
|
76
|
+
class PostmortemError < RuntimeError; end
|
|
57
77
|
|
|
58
78
|
class Session
|
|
59
79
|
def initialize ui
|
|
@@ -65,71 +85,125 @@ module DEBUGGER__
|
|
|
65
85
|
# "Foo#bar" => MethodBreakpoint
|
|
66
86
|
# [:watch, ivar] => WatchIVarBreakpoint
|
|
67
87
|
# [:check, expr] => CheckBreakpoint
|
|
68
|
-
|
|
88
|
+
#
|
|
89
|
+
@tracers = []
|
|
90
|
+
@th_clients = nil # {Thread => ThreadClient}
|
|
69
91
|
@q_evt = Queue.new
|
|
70
92
|
@displays = []
|
|
71
93
|
@tc = nil
|
|
72
94
|
@tc_id = 0
|
|
73
95
|
@preset_command = nil
|
|
96
|
+
@postmortem_hook = nil
|
|
97
|
+
@postmortem = false
|
|
98
|
+
@thread_stopper = nil
|
|
74
99
|
|
|
75
100
|
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
|
|
76
101
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
|
77
102
|
@src_map = {} # {id => src}
|
|
78
103
|
|
|
79
104
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
|
80
|
-
|
|
81
|
-
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
82
|
-
end
|
|
105
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
83
106
|
}
|
|
84
107
|
@tp_load_script.enable
|
|
85
108
|
|
|
109
|
+
activate
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def active?
|
|
113
|
+
!@q_evt.closed?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def break_at? file, line
|
|
117
|
+
@bps.has_key? [file, line]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def check_forked
|
|
121
|
+
unless active?
|
|
122
|
+
# TODO: Support it
|
|
123
|
+
raise 'DEBUGGER: stop at forked process is not supported yet.'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def activate on_fork: false
|
|
86
128
|
@session_server = Thread.new do
|
|
129
|
+
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
|
87
130
|
Thread.current.abort_on_exception = true
|
|
88
131
|
session_server_main
|
|
89
132
|
end
|
|
90
133
|
|
|
91
|
-
@management_threads = [@session_server]
|
|
92
|
-
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
|
93
|
-
|
|
94
134
|
setup_threads
|
|
95
135
|
|
|
136
|
+
thc = thread_client @session_server
|
|
137
|
+
thc.is_management
|
|
138
|
+
|
|
139
|
+
if on_fork
|
|
140
|
+
@tp_thread_begin.disable
|
|
141
|
+
@tp_thread_begin = nil
|
|
142
|
+
@ui.activate on_fork: true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
|
|
146
|
+
thc.is_management
|
|
147
|
+
end
|
|
148
|
+
|
|
96
149
|
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
end
|
|
150
|
+
th = Thread.current
|
|
151
|
+
ThreadClient.current.on_thread_begin th
|
|
100
152
|
}
|
|
101
153
|
@tp_thread_begin.enable
|
|
102
154
|
end
|
|
103
155
|
|
|
104
|
-
def
|
|
105
|
-
|
|
156
|
+
def deactivate
|
|
157
|
+
thread_client.deactivate
|
|
158
|
+
@thread_stopper.disable if @thread_stopper
|
|
159
|
+
@tp_load_script.disable
|
|
160
|
+
@tp_thread_begin.disable
|
|
161
|
+
@bps.each{|k, bp| bp.disable}
|
|
162
|
+
@th_clients.each{|th, thc| thc.close}
|
|
163
|
+
@tracers.each{|t| t.disable}
|
|
164
|
+
@q_evt.close
|
|
165
|
+
@ui&.deactivate
|
|
166
|
+
@ui = nil
|
|
106
167
|
end
|
|
107
168
|
|
|
108
169
|
def reset_ui ui
|
|
109
170
|
@ui.close
|
|
110
171
|
@ui = ui
|
|
111
|
-
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def pop_event
|
|
175
|
+
@q_evt.pop
|
|
112
176
|
end
|
|
113
177
|
|
|
114
178
|
def session_server_main
|
|
115
|
-
while evt =
|
|
116
|
-
#
|
|
179
|
+
while evt = pop_event
|
|
180
|
+
# variable `@internal_info` is only used for test
|
|
117
181
|
tc, output, ev, @internal_info, *ev_args = evt
|
|
118
182
|
output.each{|str| @ui.puts str}
|
|
119
183
|
|
|
120
184
|
case ev
|
|
121
185
|
when :init
|
|
122
186
|
wait_command_loop tc
|
|
187
|
+
|
|
123
188
|
when :load
|
|
124
189
|
iseq, src = ev_args
|
|
125
190
|
on_load iseq, src
|
|
126
191
|
@ui.event :load
|
|
127
192
|
tc << :continue
|
|
193
|
+
|
|
194
|
+
when :trace
|
|
195
|
+
trace_id, msg = ev_args
|
|
196
|
+
if t = @tracers.find{|t| t.object_id == trace_id}
|
|
197
|
+
t.puts msg
|
|
198
|
+
end
|
|
199
|
+
tc << :continue
|
|
200
|
+
|
|
128
201
|
when :thread_begin
|
|
129
202
|
th = ev_args.shift
|
|
130
203
|
on_thread_begin th
|
|
131
204
|
@ui.event :thread_begin, th
|
|
132
205
|
tc << :continue
|
|
206
|
+
|
|
133
207
|
when :suspend
|
|
134
208
|
case ev_args.first
|
|
135
209
|
when :breakpoint
|
|
@@ -142,10 +216,12 @@ module DEBUGGER__
|
|
|
142
216
|
end
|
|
143
217
|
|
|
144
218
|
if @displays.empty?
|
|
219
|
+
stop_all_threads
|
|
145
220
|
wait_command_loop tc
|
|
146
221
|
else
|
|
147
222
|
tc << [:eval, :display, @displays]
|
|
148
223
|
end
|
|
224
|
+
|
|
149
225
|
when :result
|
|
150
226
|
case ev_args.first
|
|
151
227
|
when :try_display
|
|
@@ -156,14 +232,22 @@ module DEBUGGER__
|
|
|
156
232
|
@ui.puts "canceled: #{@displays.pop}"
|
|
157
233
|
end
|
|
158
234
|
end
|
|
235
|
+
stop_all_threads
|
|
236
|
+
|
|
159
237
|
when :method_breakpoint, :watch_breakpoint
|
|
160
238
|
bp = ev_args[1]
|
|
161
239
|
if bp
|
|
162
|
-
|
|
240
|
+
add_bp(bp)
|
|
163
241
|
show_bps bp
|
|
164
242
|
else
|
|
165
243
|
# can't make a bp
|
|
166
244
|
end
|
|
245
|
+
when :trace_pass
|
|
246
|
+
obj_id = ev_args[1]
|
|
247
|
+
obj_inspect = ev_args[2]
|
|
248
|
+
opt = ev_args[3]
|
|
249
|
+
@tracers << t = ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
|
250
|
+
@ui.puts "Enable #{t.to_s}"
|
|
167
251
|
else
|
|
168
252
|
# ignore
|
|
169
253
|
end
|
|
@@ -176,27 +260,24 @@ module DEBUGGER__
|
|
|
176
260
|
end
|
|
177
261
|
end
|
|
178
262
|
ensure
|
|
179
|
-
|
|
180
|
-
@tp_thread_begin.disable
|
|
181
|
-
@bps.each{|k, bp| bp.disable}
|
|
182
|
-
@th_clients.each{|th, thc| thc.close}
|
|
183
|
-
@ui = nil
|
|
263
|
+
deactivate
|
|
184
264
|
end
|
|
185
265
|
|
|
186
266
|
def add_preset_commands name, cmds, kick: true, continue: true
|
|
187
267
|
cs = cmds.map{|c|
|
|
188
|
-
c
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
ThreadClient.current.on_init name if kick
|
|
268
|
+
c.each_line.map{|line|
|
|
269
|
+
line = line.strip.gsub(/\A\s*\#.*/, '').strip
|
|
270
|
+
line unless line.empty?
|
|
271
|
+
}.compact
|
|
272
|
+
}.flatten.compact
|
|
273
|
+
|
|
274
|
+
if @preset_command && !@preset_command.commands.empty?
|
|
275
|
+
@preset_command.commands += cs
|
|
276
|
+
else
|
|
277
|
+
@preset_command = PresetCommand.new(cs, name, continue)
|
|
199
278
|
end
|
|
279
|
+
|
|
280
|
+
ThreadClient.current.on_init name if kick
|
|
200
281
|
end
|
|
201
282
|
|
|
202
283
|
def source iseq
|
|
@@ -213,20 +294,26 @@ module DEBUGGER__
|
|
|
213
294
|
|
|
214
295
|
def wait_command_loop tc
|
|
215
296
|
@tc = tc
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
end
|
|
224
|
-
rescue Interrupt
|
|
225
|
-
retry
|
|
297
|
+
|
|
298
|
+
loop do
|
|
299
|
+
case wait_command
|
|
300
|
+
when :retry
|
|
301
|
+
# nothing
|
|
302
|
+
else
|
|
303
|
+
break
|
|
226
304
|
end
|
|
305
|
+
rescue Interrupt
|
|
306
|
+
@ui.puts "\n^C"
|
|
307
|
+
retry
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def prompt
|
|
312
|
+
if @postmortem
|
|
313
|
+
'(rdbg:postmortem) '
|
|
314
|
+
else
|
|
315
|
+
'(rdbg) '
|
|
227
316
|
end
|
|
228
|
-
ensure
|
|
229
|
-
@tc = nil
|
|
230
317
|
end
|
|
231
318
|
|
|
232
319
|
def wait_command
|
|
@@ -234,7 +321,9 @@ module DEBUGGER__
|
|
|
234
321
|
if @preset_command.commands.empty?
|
|
235
322
|
if @preset_command.auto_continue
|
|
236
323
|
@preset_command = nil
|
|
324
|
+
|
|
237
325
|
@tc << :continue
|
|
326
|
+
restart_all_threads
|
|
238
327
|
return
|
|
239
328
|
else
|
|
240
329
|
@preset_command = nil
|
|
@@ -246,7 +335,7 @@ module DEBUGGER__
|
|
|
246
335
|
end
|
|
247
336
|
else
|
|
248
337
|
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
|
|
249
|
-
line = @ui.readline
|
|
338
|
+
line = @ui.readline prompt
|
|
250
339
|
end
|
|
251
340
|
|
|
252
341
|
case line
|
|
@@ -280,27 +369,37 @@ module DEBUGGER__
|
|
|
280
369
|
|
|
281
370
|
# * `s[tep]`
|
|
282
371
|
# * Step in. Resume the program until next breakable point.
|
|
372
|
+
# * `s[tep] <n>`
|
|
373
|
+
# * Step in, resume the program at `<n>`th breakable point.
|
|
283
374
|
when 's', 'step'
|
|
284
375
|
cancel_auto_continue
|
|
285
|
-
|
|
376
|
+
check_postmortem
|
|
377
|
+
step_command :in, arg
|
|
286
378
|
|
|
287
379
|
# * `n[ext]`
|
|
288
380
|
# * Step over. Resume the program until next line.
|
|
381
|
+
# * `n[ext] <n>`
|
|
382
|
+
# * Step over, same as `step <n>`.
|
|
289
383
|
when 'n', 'next'
|
|
290
384
|
cancel_auto_continue
|
|
291
|
-
|
|
385
|
+
check_postmortem
|
|
386
|
+
step_command :next, arg
|
|
292
387
|
|
|
293
388
|
# * `fin[ish]`
|
|
294
389
|
# * Finish this frame. Resume the program until the current frame is finished.
|
|
390
|
+
# * `fin[ish] <n>`
|
|
391
|
+
# * Finish frames, same as `step <n>`.
|
|
295
392
|
when 'fin', 'finish'
|
|
296
393
|
cancel_auto_continue
|
|
297
|
-
|
|
394
|
+
check_postmortem
|
|
395
|
+
step_command :finish, arg
|
|
298
396
|
|
|
299
397
|
# * `c[ontinue]`
|
|
300
398
|
# * Resume the program.
|
|
301
399
|
when 'c', 'continue'
|
|
302
400
|
cancel_auto_continue
|
|
303
401
|
@tc << :continue
|
|
402
|
+
restart_all_threads
|
|
304
403
|
|
|
305
404
|
# * `q[uit]` or `Ctrl-D`
|
|
306
405
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
|
@@ -308,6 +407,7 @@ module DEBUGGER__
|
|
|
308
407
|
if ask 'Really quit?'
|
|
309
408
|
@ui.quit arg.to_i
|
|
310
409
|
@tc << :continue
|
|
410
|
+
restart_all_threads
|
|
311
411
|
else
|
|
312
412
|
return :retry
|
|
313
413
|
end
|
|
@@ -316,7 +416,7 @@ module DEBUGGER__
|
|
|
316
416
|
# * Same as q[uit] but without the confirmation prompt.
|
|
317
417
|
when 'q!', 'quit!'
|
|
318
418
|
@ui.quit arg.to_i
|
|
319
|
-
|
|
419
|
+
restart_all_threads
|
|
320
420
|
|
|
321
421
|
# * `kill`
|
|
322
422
|
# * Stop the debuggee process with `Kernal#exit!`.
|
|
@@ -354,6 +454,8 @@ module DEBUGGER__
|
|
|
354
454
|
# * break if: `<expr>` is true at any lines.
|
|
355
455
|
# * Note that this feature is super slow.
|
|
356
456
|
when 'b', 'break'
|
|
457
|
+
check_postmortem
|
|
458
|
+
|
|
357
459
|
if arg == nil
|
|
358
460
|
show_bps
|
|
359
461
|
return :retry
|
|
@@ -370,6 +472,7 @@ module DEBUGGER__
|
|
|
370
472
|
|
|
371
473
|
# skip
|
|
372
474
|
when 'bv'
|
|
475
|
+
check_postmortem
|
|
373
476
|
require 'json'
|
|
374
477
|
|
|
375
478
|
h = Hash.new{|h, k| h[k] = []}
|
|
@@ -396,8 +499,10 @@ module DEBUGGER__
|
|
|
396
499
|
# * `catch <Error>`
|
|
397
500
|
# * Set breakpoint on raising `<Error>`.
|
|
398
501
|
when 'catch'
|
|
502
|
+
check_postmortem
|
|
503
|
+
|
|
399
504
|
if arg
|
|
400
|
-
bp =
|
|
505
|
+
bp = repl_add_catch_breakpoint arg
|
|
401
506
|
show_bps bp if bp
|
|
402
507
|
else
|
|
403
508
|
show_bps
|
|
@@ -408,6 +513,8 @@ module DEBUGGER__
|
|
|
408
513
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
|
409
514
|
# * Note that this feature is super slow.
|
|
410
515
|
when 'wat', 'watch'
|
|
516
|
+
check_postmortem
|
|
517
|
+
|
|
411
518
|
if arg && arg.match?(/\A@\w+/)
|
|
412
519
|
@tc << [:breakpoint, :watch, arg]
|
|
413
520
|
else
|
|
@@ -420,15 +527,17 @@ module DEBUGGER__
|
|
|
420
527
|
# * `del[ete] <bpnum>`
|
|
421
528
|
# * delete specified breakpoint.
|
|
422
529
|
when 'del', 'delete'
|
|
530
|
+
check_postmortem
|
|
531
|
+
|
|
423
532
|
bp =
|
|
424
533
|
case arg
|
|
425
534
|
when nil
|
|
426
535
|
show_bps
|
|
427
536
|
if ask "Remove all breakpoints?", 'N'
|
|
428
|
-
|
|
537
|
+
delete_bp
|
|
429
538
|
end
|
|
430
539
|
when /\d+/
|
|
431
|
-
|
|
540
|
+
delete_bp arg.to_i
|
|
432
541
|
else
|
|
433
542
|
nil
|
|
434
543
|
end
|
|
@@ -501,25 +610,57 @@ module DEBUGGER__
|
|
|
501
610
|
|
|
502
611
|
@tc << [:show, :edit, arg]
|
|
503
612
|
|
|
504
|
-
# * `i[nfo]
|
|
613
|
+
# * `i[nfo]`
|
|
614
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
|
615
|
+
# * `i[nfo] l[ocal[s]]`
|
|
505
616
|
# * Show information about the current frame (local variables)
|
|
506
617
|
# * It includes `self` as `%self` and a return value as `%return`.
|
|
618
|
+
# * `i[nfo] i[var[s]]` or `i[nfo] instance`
|
|
619
|
+
# * Show information about instance variables about `self`.
|
|
620
|
+
# * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
|
|
621
|
+
# * Show information about accessible constants except toplevel constants.
|
|
622
|
+
# * `i[nfo] g[lobal[s]]`
|
|
623
|
+
# * Show information about global variables
|
|
624
|
+
# * `i[nfo] ... </pattern/>`
|
|
625
|
+
# * Filter the output with `</pattern/>`.
|
|
507
626
|
# * `i[nfo] th[read[s]]`
|
|
508
627
|
# * Show all threads (same as `th[read]`).
|
|
509
628
|
when 'i', 'info'
|
|
510
|
-
|
|
629
|
+
if /\/(.+)\/\z/ =~ arg
|
|
630
|
+
pat = Regexp.compile($1)
|
|
631
|
+
sub = $~.pre_match.strip
|
|
632
|
+
else
|
|
633
|
+
sub = arg
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
case sub
|
|
511
637
|
when nil
|
|
512
|
-
@tc << [:show, :
|
|
513
|
-
when 'l',
|
|
514
|
-
@tc << [:show, :
|
|
638
|
+
@tc << [:show, :default, pat] # something useful
|
|
639
|
+
when 'l', /^locals?/
|
|
640
|
+
@tc << [:show, :locals, pat]
|
|
641
|
+
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
|
642
|
+
@tc << [:show, :ivars, pat]
|
|
643
|
+
when 'c', /^consts?/i, /^constants?/i
|
|
644
|
+
@tc << [:show, :consts, pat]
|
|
645
|
+
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
|
646
|
+
@tc << [:show, :globals, pat]
|
|
515
647
|
when 'th', /threads?/
|
|
516
648
|
thread_list
|
|
517
649
|
return :retry
|
|
518
650
|
else
|
|
651
|
+
@ui.puts "unrecognized argument for info command: #{arg}"
|
|
519
652
|
show_help 'info'
|
|
520
653
|
return :retry
|
|
521
654
|
end
|
|
522
655
|
|
|
656
|
+
# * `o[utline]` or `ls`
|
|
657
|
+
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
|
658
|
+
# * `o[utline] <expr>` or `ls <expr>`
|
|
659
|
+
# * Show you available methods and instance variables of the given object.
|
|
660
|
+
# * If the object is a class/module, it also lists its constants.
|
|
661
|
+
when 'outline', 'o', 'ls'
|
|
662
|
+
@tc << [:show, :outline, arg]
|
|
663
|
+
|
|
523
664
|
# * `display`
|
|
524
665
|
# * Show display setting.
|
|
525
666
|
# * `display <expr>`
|
|
@@ -581,10 +722,16 @@ module DEBUGGER__
|
|
|
581
722
|
when 'pp'
|
|
582
723
|
@tc << [:eval, :pp, arg.to_s]
|
|
583
724
|
|
|
584
|
-
# * `
|
|
725
|
+
# * `eval <expr>`
|
|
585
726
|
# * Evaluate `<expr>` on the current frame.
|
|
586
|
-
when '
|
|
587
|
-
|
|
727
|
+
when 'eval', 'call'
|
|
728
|
+
if arg == nil || arg.empty?
|
|
729
|
+
show_help 'eval'
|
|
730
|
+
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
|
731
|
+
return :retry
|
|
732
|
+
else
|
|
733
|
+
@tc << [:eval, :call, arg]
|
|
734
|
+
end
|
|
588
735
|
|
|
589
736
|
# * `irb`
|
|
590
737
|
# * Invoke `irb` on the current frame.
|
|
@@ -598,6 +745,105 @@ module DEBUGGER__
|
|
|
598
745
|
# don't repeat irb command
|
|
599
746
|
@repl_prev_line = nil
|
|
600
747
|
|
|
748
|
+
### Trace
|
|
749
|
+
# * `trace`
|
|
750
|
+
# * Show available tracers list.
|
|
751
|
+
# * `trace line`
|
|
752
|
+
# * Add a line tracer. It indicates line events.
|
|
753
|
+
# * `trace call`
|
|
754
|
+
# * Add a call tracer. It indicate call/return events.
|
|
755
|
+
# * `trace exception`
|
|
756
|
+
# * Add an exception tracer. It indicates raising exceptions.
|
|
757
|
+
# * `trace object <expr>`
|
|
758
|
+
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
|
759
|
+
# * `trace ... </pattern/>`
|
|
760
|
+
# * Indicates only matched events to `</pattern/>` (RegExp).
|
|
761
|
+
# * `trace ... into: <file>`
|
|
762
|
+
# * Save trace information into: `<file>`.
|
|
763
|
+
# * `trace off <num>`
|
|
764
|
+
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
|
765
|
+
# * `trace off [line|call|pass]`
|
|
766
|
+
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
|
767
|
+
when 'trace'
|
|
768
|
+
if (re = /\s+into:\s*(.+)/) =~ arg
|
|
769
|
+
into = $1
|
|
770
|
+
arg.sub!(re, '')
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
if (re = /\s\/(.+)\/\z/) =~ arg
|
|
774
|
+
pattern = $1
|
|
775
|
+
arg.sub!(re, '')
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
case arg
|
|
779
|
+
when nil
|
|
780
|
+
@ui.puts 'Tracers:'
|
|
781
|
+
@tracers.each_with_index{|t, i|
|
|
782
|
+
@ui.puts "* \##{i} #{t}"
|
|
783
|
+
}
|
|
784
|
+
@ui.puts
|
|
785
|
+
return :retry
|
|
786
|
+
|
|
787
|
+
when /\Aline\z/
|
|
788
|
+
@tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
|
|
789
|
+
@ui.puts "Enable #{t.to_s}"
|
|
790
|
+
return :retry
|
|
791
|
+
|
|
792
|
+
when /\Acall\z/
|
|
793
|
+
@tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
|
|
794
|
+
@ui.puts "Enable #{t.to_s}"
|
|
795
|
+
return :retry
|
|
796
|
+
|
|
797
|
+
when /\Aexception\z/
|
|
798
|
+
@tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
|
799
|
+
@ui.puts "Enable #{t.to_s}"
|
|
800
|
+
return :retry
|
|
801
|
+
|
|
802
|
+
when /\Aobject\s+(.+)/
|
|
803
|
+
@tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
|
804
|
+
|
|
805
|
+
when /\Aoff\s+(\d+)\z/
|
|
806
|
+
if t = @tracers[$1.to_i]
|
|
807
|
+
t.disable
|
|
808
|
+
@ui.puts "Disable #{t.to_s}"
|
|
809
|
+
else
|
|
810
|
+
@ui.puts "Unmatched: #{$1}"
|
|
811
|
+
end
|
|
812
|
+
return :retry
|
|
813
|
+
|
|
814
|
+
when /\Aoff(\s+(line|call|exception|object))?\z/
|
|
815
|
+
@tracers.each{|t|
|
|
816
|
+
if $2.nil? || t.type == $2
|
|
817
|
+
t.disable
|
|
818
|
+
@ui.puts "Disable #{t.to_s}"
|
|
819
|
+
end
|
|
820
|
+
}
|
|
821
|
+
return :retry
|
|
822
|
+
|
|
823
|
+
else
|
|
824
|
+
@ui.puts "Unknown trace option: #{arg.inspect}"
|
|
825
|
+
return :retry
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# Record
|
|
829
|
+
# * `record`
|
|
830
|
+
# * Show recording status.
|
|
831
|
+
# * `record [on|off]`
|
|
832
|
+
# * Start/Stop recording.
|
|
833
|
+
# * `step back`
|
|
834
|
+
# * Start replay. Step back with the last execution log.
|
|
835
|
+
# * `s[tep]` does stepping forward with the last log.
|
|
836
|
+
# * `step reset`
|
|
837
|
+
# * Stop replay .
|
|
838
|
+
when 'record'
|
|
839
|
+
case arg
|
|
840
|
+
when nil, 'on', 'off'
|
|
841
|
+
@tc << [:record, arg&.to_sym]
|
|
842
|
+
else
|
|
843
|
+
@ui.puts "unknown command: #{arg}"
|
|
844
|
+
return :retry
|
|
845
|
+
end
|
|
846
|
+
|
|
601
847
|
### Thread control
|
|
602
848
|
|
|
603
849
|
# * `th[read]`
|
|
@@ -630,13 +876,28 @@ module DEBUGGER__
|
|
|
630
876
|
config_command arg
|
|
631
877
|
return :retry
|
|
632
878
|
|
|
879
|
+
# * `source <file>`
|
|
880
|
+
# * Evaluate lines in `<file>` as debug commands.
|
|
881
|
+
when 'source'
|
|
882
|
+
if arg
|
|
883
|
+
begin
|
|
884
|
+
cmds = File.readlines(path = File.expand_path(arg))
|
|
885
|
+
add_preset_commands path, cmds, kick: true, continue: false
|
|
886
|
+
rescue Errno::ENOENT
|
|
887
|
+
@ui.puts "File not found: #{arg}"
|
|
888
|
+
end
|
|
889
|
+
else
|
|
890
|
+
show_help 'source'
|
|
891
|
+
end
|
|
892
|
+
return :retry
|
|
893
|
+
|
|
633
894
|
### Help
|
|
634
895
|
|
|
635
896
|
# * `h[elp]`
|
|
636
897
|
# * Show help for all commands.
|
|
637
898
|
# * `h[elp] <command>`
|
|
638
899
|
# * Show help for the given command.
|
|
639
|
-
when 'h', 'help'
|
|
900
|
+
when 'h', 'help', '?'
|
|
640
901
|
if arg
|
|
641
902
|
show_help arg
|
|
642
903
|
else
|
|
@@ -646,21 +907,56 @@ module DEBUGGER__
|
|
|
646
907
|
|
|
647
908
|
### END
|
|
648
909
|
else
|
|
649
|
-
@
|
|
910
|
+
@tc << [:eval, :pp, line]
|
|
911
|
+
=begin
|
|
650
912
|
@repl_prev_line = nil
|
|
913
|
+
@ui.puts "unknown command: #{line}"
|
|
914
|
+
begin
|
|
915
|
+
require 'did_you_mean'
|
|
916
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
|
917
|
+
correction = spell_checker.correct(line.split(/\s/).first || '')
|
|
918
|
+
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
|
919
|
+
rescue LoadError
|
|
920
|
+
# Don't use D
|
|
921
|
+
end
|
|
651
922
|
return :retry
|
|
923
|
+
=end
|
|
652
924
|
end
|
|
653
925
|
|
|
654
926
|
rescue Interrupt
|
|
655
927
|
return :retry
|
|
656
928
|
rescue SystemExit
|
|
657
929
|
raise
|
|
930
|
+
rescue PostmortemError => e
|
|
931
|
+
@ui.puts e.message
|
|
932
|
+
return :retry
|
|
658
933
|
rescue Exception => e
|
|
659
934
|
@ui.puts "[REPL ERROR] #{e.inspect}"
|
|
660
935
|
@ui.puts e.backtrace.map{|e| ' ' + e}
|
|
661
936
|
return :retry
|
|
662
937
|
end
|
|
663
938
|
|
|
939
|
+
def step_command type, arg
|
|
940
|
+
case arg
|
|
941
|
+
when nil
|
|
942
|
+
@tc << [:step, type]
|
|
943
|
+
restart_all_threads
|
|
944
|
+
when /\A\d+\z/
|
|
945
|
+
@tc << [:step, type, arg.to_i]
|
|
946
|
+
restart_all_threads
|
|
947
|
+
when /\Aback\z/, /\Areset\z/
|
|
948
|
+
if type != :in
|
|
949
|
+
@ui.puts "only `step #{arg}` is supported."
|
|
950
|
+
:retry
|
|
951
|
+
else
|
|
952
|
+
@tc << [:step, arg.to_sym]
|
|
953
|
+
end
|
|
954
|
+
else
|
|
955
|
+
@ui.puts "Unknown option: #{arg}"
|
|
956
|
+
:retry
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
|
|
664
960
|
def config_show key
|
|
665
961
|
key = key.to_sym
|
|
666
962
|
if CONFIG_SET[key]
|
|
@@ -682,9 +978,9 @@ module DEBUGGER__
|
|
|
682
978
|
if CONFIG_SET[key = key.to_sym]
|
|
683
979
|
begin
|
|
684
980
|
if append
|
|
685
|
-
|
|
981
|
+
CONFIG.append_config(key, val)
|
|
686
982
|
else
|
|
687
|
-
|
|
983
|
+
CONFIG[key] = val
|
|
688
984
|
end
|
|
689
985
|
rescue => e
|
|
690
986
|
@ui.puts e.message
|
|
@@ -703,7 +999,7 @@ module DEBUGGER__
|
|
|
703
999
|
|
|
704
1000
|
when /\Aunset\s+(.+)\z/
|
|
705
1001
|
if CONFIG_SET[key = $1.to_sym]
|
|
706
|
-
|
|
1002
|
+
CONFIG[key] = nil
|
|
707
1003
|
end
|
|
708
1004
|
config_show key
|
|
709
1005
|
|
|
@@ -758,51 +1054,7 @@ module DEBUGGER__
|
|
|
758
1054
|
end
|
|
759
1055
|
end
|
|
760
1056
|
|
|
761
|
-
|
|
762
|
-
if klass.singleton_class?
|
|
763
|
-
"#{receiver}."
|
|
764
|
-
else
|
|
765
|
-
"#{klass}#"
|
|
766
|
-
end
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
def pretty_tp tp
|
|
770
|
-
loc = "#{tp.path}:#{tp.lineno}"
|
|
771
|
-
level = caller.size
|
|
772
|
-
|
|
773
|
-
info =
|
|
774
|
-
case tp.event
|
|
775
|
-
when :line
|
|
776
|
-
"line at #{loc}"
|
|
777
|
-
when :call, :c_call
|
|
778
|
-
klass = tp.defined_class
|
|
779
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} at #{loc}"
|
|
780
|
-
when :return, :c_return
|
|
781
|
-
klass = tp.defined_class
|
|
782
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} => #{tp.return_value.inspect} at #{loc}"
|
|
783
|
-
when :b_call
|
|
784
|
-
"b_call at #{loc}"
|
|
785
|
-
when :b_return
|
|
786
|
-
"b_return => #{tp.return_value} at #{loc}"
|
|
787
|
-
when :class
|
|
788
|
-
"class #{tp.self} at #{loc}"
|
|
789
|
-
when :end
|
|
790
|
-
"class #{tp.self} end at #{loc}"
|
|
791
|
-
else
|
|
792
|
-
"#{tp.event} at #{loc}"
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
case tp.event
|
|
796
|
-
when :call, :b_call, :return, :b_return, :class, :end
|
|
797
|
-
level -= 1
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
"Tracing:#{' ' * level} #{info}"
|
|
801
|
-
rescue => e
|
|
802
|
-
p e
|
|
803
|
-
pp e.backtrace
|
|
804
|
-
exit!
|
|
805
|
-
end
|
|
1057
|
+
# breakpoint management
|
|
806
1058
|
|
|
807
1059
|
def iterate_bps
|
|
808
1060
|
deleted_bps = []
|
|
@@ -834,7 +1086,29 @@ module DEBUGGER__
|
|
|
834
1086
|
nil
|
|
835
1087
|
end
|
|
836
1088
|
|
|
837
|
-
def
|
|
1089
|
+
def rehash_bps
|
|
1090
|
+
bps = @bps.values
|
|
1091
|
+
@bps.clear
|
|
1092
|
+
bps.each{|bp|
|
|
1093
|
+
add_bp bp
|
|
1094
|
+
}
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def add_bp bp
|
|
1098
|
+
# don't repeat commands that add breakpoints
|
|
1099
|
+
@repl_prev_line = nil
|
|
1100
|
+
|
|
1101
|
+
if @bps.has_key? bp.key
|
|
1102
|
+
unless bp.duplicable?
|
|
1103
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
|
1104
|
+
bp.disable
|
|
1105
|
+
end
|
|
1106
|
+
else
|
|
1107
|
+
@bps[bp.key] = bp
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
def delete_bp arg = nil
|
|
838
1112
|
case arg
|
|
839
1113
|
when nil
|
|
840
1114
|
@bps.each{|key, bp| bp.delete}
|
|
@@ -873,14 +1147,14 @@ module DEBUGGER__
|
|
|
873
1147
|
|
|
874
1148
|
case expr[:sig]
|
|
875
1149
|
when /\A(\d+)\z/
|
|
876
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond:
|
|
1150
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
|
|
877
1151
|
when /\A(.+)[:\s+](\d+)\z/
|
|
878
|
-
add_line_breakpoint $1, $2.to_i, cond:
|
|
1152
|
+
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
|
879
1153
|
when /\A(.+)([\.\#])(.+)\z/
|
|
880
|
-
@tc << [:breakpoint, :method, $1, $2, $3,
|
|
1154
|
+
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
|
|
881
1155
|
return :noretry
|
|
882
1156
|
when nil
|
|
883
|
-
add_check_breakpoint
|
|
1157
|
+
add_check_breakpoint cond
|
|
884
1158
|
else
|
|
885
1159
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
|
886
1160
|
@ui.puts
|
|
@@ -888,6 +1162,34 @@ module DEBUGGER__
|
|
|
888
1162
|
end
|
|
889
1163
|
end
|
|
890
1164
|
|
|
1165
|
+
def repl_add_catch_breakpoint arg
|
|
1166
|
+
expr = parse_break arg.strip
|
|
1167
|
+
cond = expr[:if]
|
|
1168
|
+
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
|
1169
|
+
|
|
1170
|
+
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
|
|
1171
|
+
add_bp bp
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
def add_catch_breakpoint pat
|
|
1175
|
+
bp = CatchBreakpoint.new(pat)
|
|
1176
|
+
add_bp bp
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
def add_check_breakpoint expr
|
|
1180
|
+
bp = CheckBreakpoint.new(expr)
|
|
1181
|
+
add_bp bp
|
|
1182
|
+
end
|
|
1183
|
+
|
|
1184
|
+
def add_line_breakpoint file, line, **kw
|
|
1185
|
+
file = resolve_path(file)
|
|
1186
|
+
bp = LineBreakpoint.new(file, line, **kw)
|
|
1187
|
+
|
|
1188
|
+
add_bp bp
|
|
1189
|
+
rescue Errno::ENOENT => e
|
|
1190
|
+
@ui.puts e.message
|
|
1191
|
+
end
|
|
1192
|
+
|
|
891
1193
|
# threads
|
|
892
1194
|
|
|
893
1195
|
def update_thread_list
|
|
@@ -896,17 +1198,15 @@ module DEBUGGER__
|
|
|
896
1198
|
unmanaged = []
|
|
897
1199
|
|
|
898
1200
|
list.each{|th|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
# ignore
|
|
904
|
-
when @th_clients.has_key?(th)
|
|
905
|
-
thcs << @th_clients[th]
|
|
1201
|
+
if thc = @th_clients[th]
|
|
1202
|
+
if !thc.management?
|
|
1203
|
+
thcs << thc
|
|
1204
|
+
end
|
|
906
1205
|
else
|
|
907
1206
|
unmanaged << th
|
|
908
1207
|
end
|
|
909
1208
|
}
|
|
1209
|
+
|
|
910
1210
|
return thcs.sort_by{|thc| thc.id}, unmanaged
|
|
911
1211
|
end
|
|
912
1212
|
|
|
@@ -933,7 +1233,7 @@ module DEBUGGER__
|
|
|
933
1233
|
thcs, _unmanaged_ths = update_thread_list
|
|
934
1234
|
|
|
935
1235
|
if tc = thcs[n]
|
|
936
|
-
if tc.
|
|
1236
|
+
if tc.waiting?
|
|
937
1237
|
@tc = tc
|
|
938
1238
|
else
|
|
939
1239
|
@ui.puts "#{tc.thread} is not controllable yet."
|
|
@@ -947,11 +1247,11 @@ module DEBUGGER__
|
|
|
947
1247
|
end
|
|
948
1248
|
|
|
949
1249
|
def setup_threads
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1250
|
+
@th_clients = {}
|
|
1251
|
+
|
|
1252
|
+
Thread.list.each{|th|
|
|
1253
|
+
thread_client_create(th)
|
|
1254
|
+
}
|
|
955
1255
|
end
|
|
956
1256
|
|
|
957
1257
|
def on_thread_begin th
|
|
@@ -963,8 +1263,7 @@ module DEBUGGER__
|
|
|
963
1263
|
end
|
|
964
1264
|
end
|
|
965
1265
|
|
|
966
|
-
def thread_client
|
|
967
|
-
thr = Thread.current
|
|
1266
|
+
def thread_client thr = Thread.current
|
|
968
1267
|
if @th_clients.has_key? thr
|
|
969
1268
|
@th_clients[thr]
|
|
970
1269
|
else
|
|
@@ -972,84 +1271,67 @@ module DEBUGGER__
|
|
|
972
1271
|
end
|
|
973
1272
|
end
|
|
974
1273
|
|
|
975
|
-
def
|
|
976
|
-
|
|
1274
|
+
private def thread_stopper
|
|
1275
|
+
@thread_stopper ||= TracePoint.new(:line) do
|
|
1276
|
+
# run on each thread
|
|
1277
|
+
tc = ThreadClient.current
|
|
1278
|
+
next if tc.management?
|
|
1279
|
+
next unless tc.running?
|
|
1280
|
+
next if tc == @tc
|
|
977
1281
|
|
|
978
|
-
|
|
979
|
-
TracePoint.new(:line) do
|
|
980
|
-
th = Thread.current
|
|
981
|
-
if current == th || @management_threads.include?(th)
|
|
982
|
-
next
|
|
983
|
-
else
|
|
984
|
-
tc = ThreadClient.current
|
|
985
|
-
tc.on_pause
|
|
986
|
-
end
|
|
987
|
-
end.enable do
|
|
988
|
-
yield
|
|
989
|
-
ensure
|
|
990
|
-
@th_clients.each{|thr, tc|
|
|
991
|
-
case thr
|
|
992
|
-
when current, (@tc && @tc.thread)
|
|
993
|
-
next
|
|
994
|
-
else
|
|
995
|
-
tc << :continue if thr != Thread.current
|
|
996
|
-
end
|
|
997
|
-
}
|
|
998
|
-
end
|
|
999
|
-
else
|
|
1000
|
-
yield
|
|
1282
|
+
tc.on_pause
|
|
1001
1283
|
end
|
|
1002
1284
|
end
|
|
1003
1285
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
pending_line_breakpoints do |bp|
|
|
1011
|
-
if bp.path == (iseq.absolute_path || iseq.path)
|
|
1012
|
-
bp.try_activate
|
|
1013
|
-
end
|
|
1014
|
-
end
|
|
1286
|
+
private def running_thread_clients_count
|
|
1287
|
+
@th_clients.count{|th, tc|
|
|
1288
|
+
next if tc.management?
|
|
1289
|
+
next unless tc.running?
|
|
1290
|
+
true
|
|
1291
|
+
}
|
|
1015
1292
|
end
|
|
1016
1293
|
|
|
1017
|
-
|
|
1294
|
+
private def waiting_thread_clients
|
|
1295
|
+
@th_clients.map{|th, tc|
|
|
1296
|
+
next if tc.management?
|
|
1297
|
+
next unless tc.waiting?
|
|
1298
|
+
tc
|
|
1299
|
+
}.compact
|
|
1300
|
+
end
|
|
1018
1301
|
|
|
1019
|
-
def
|
|
1020
|
-
|
|
1021
|
-
@repl_prev_line = nil
|
|
1302
|
+
private def stop_all_threads
|
|
1303
|
+
return if running_thread_clients_count == 0
|
|
1022
1304
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
@ui.puts "duplicated breakpoint: #{bp}"
|
|
1026
|
-
bp.disable
|
|
1027
|
-
end
|
|
1028
|
-
else
|
|
1029
|
-
@bps[bp.key] = bp
|
|
1030
|
-
end
|
|
1305
|
+
stopper = thread_stopper
|
|
1306
|
+
stopper.enable unless stopper.enabled?
|
|
1031
1307
|
end
|
|
1032
1308
|
|
|
1033
|
-
def
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1309
|
+
private def restart_all_threads
|
|
1310
|
+
stopper = thread_stopper
|
|
1311
|
+
stopper.disable if stopper.enabled?
|
|
1312
|
+
|
|
1313
|
+
waiting_thread_clients.each{|tc|
|
|
1314
|
+
next if @tc == tc
|
|
1315
|
+
tc << :continue
|
|
1038
1316
|
}
|
|
1317
|
+
@tc = nil
|
|
1039
1318
|
end
|
|
1040
1319
|
|
|
1041
|
-
|
|
1042
|
-
@bps.has_key? [file, line]
|
|
1043
|
-
end
|
|
1320
|
+
## event
|
|
1044
1321
|
|
|
1045
|
-
def
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
end
|
|
1322
|
+
def on_load iseq, src
|
|
1323
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
|
1324
|
+
@sr.add iseq, src
|
|
1049
1325
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1326
|
+
pending_line_breakpoints = @bps.find_all do |key, bp|
|
|
1327
|
+
LineBreakpoint === bp && !bp.iseq
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
pending_line_breakpoints.each do |_key, bp|
|
|
1331
|
+
if bp.path == (iseq.absolute_path || iseq.path)
|
|
1332
|
+
bp.try_activate
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1053
1335
|
end
|
|
1054
1336
|
|
|
1055
1337
|
def resolve_path file
|
|
@@ -1070,23 +1352,6 @@ module DEBUGGER__
|
|
|
1070
1352
|
raise
|
|
1071
1353
|
end
|
|
1072
1354
|
|
|
1073
|
-
def add_line_breakpoint file, line, **kw
|
|
1074
|
-
file = resolve_path(file)
|
|
1075
|
-
bp = LineBreakpoint.new(file, line, **kw)
|
|
1076
|
-
|
|
1077
|
-
add_breakpoint bp
|
|
1078
|
-
rescue Errno::ENOENT => e
|
|
1079
|
-
@ui.puts e.message
|
|
1080
|
-
end
|
|
1081
|
-
|
|
1082
|
-
def pending_line_breakpoints
|
|
1083
|
-
@bps.find_all do |key, bp|
|
|
1084
|
-
LineBreakpoint === bp && !bp.iseq
|
|
1085
|
-
end.each do |key, bp|
|
|
1086
|
-
yield bp
|
|
1087
|
-
end
|
|
1088
|
-
end
|
|
1089
|
-
|
|
1090
1355
|
def method_added tp
|
|
1091
1356
|
b = tp.binding
|
|
1092
1357
|
if var_name = b.local_variables.first
|
|
@@ -1115,10 +1380,55 @@ module DEBUGGER__
|
|
|
1115
1380
|
@ui.width
|
|
1116
1381
|
end
|
|
1117
1382
|
|
|
1118
|
-
def
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1383
|
+
def check_postmortem
|
|
1384
|
+
if @postmortem
|
|
1385
|
+
raise PostmortemError, "Can not use this command on postmortem mode."
|
|
1386
|
+
end
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
def enter_postmortem_session frames
|
|
1390
|
+
@postmortem = true
|
|
1391
|
+
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
|
1392
|
+
ensure
|
|
1393
|
+
@postmortem = false
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
def postmortem=(is_enable)
|
|
1397
|
+
if is_enable
|
|
1398
|
+
unless @postmortem_hook
|
|
1399
|
+
@postmortem_hook = TracePoint.new(:raise){|tp|
|
|
1400
|
+
exc = tp.raised_exception
|
|
1401
|
+
frames = DEBUGGER__.capture_frames(__dir__)
|
|
1402
|
+
exc.instance_variable_set(:@postmortem_frames, frames)
|
|
1403
|
+
}
|
|
1404
|
+
at_exit{
|
|
1405
|
+
@postmortem_hook.disable
|
|
1406
|
+
if CONFIG[:postmortem] && (exc = $!) != nil
|
|
1407
|
+
exc = exc.cause while exc.cause
|
|
1408
|
+
|
|
1409
|
+
begin
|
|
1410
|
+
@ui.puts "Enter postmortem mode with #{exc.inspect}"
|
|
1411
|
+
@ui.puts exc.backtrace.map{|e| ' ' + e}
|
|
1412
|
+
@ui.puts "\n"
|
|
1413
|
+
|
|
1414
|
+
enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
|
|
1415
|
+
rescue SystemExit
|
|
1416
|
+
exit!
|
|
1417
|
+
rescue Exception => e
|
|
1418
|
+
@ui = STDERR unless @ui
|
|
1419
|
+
@ui.puts "Error while postmortem console: #{e.inspect}"
|
|
1420
|
+
end
|
|
1421
|
+
end
|
|
1422
|
+
}
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1425
|
+
if !@postmortem_hook.enabled?
|
|
1426
|
+
@postmortem_hook.enable
|
|
1427
|
+
end
|
|
1428
|
+
else
|
|
1429
|
+
if @postmortem_hook && @postmortem_hook.enabled?
|
|
1430
|
+
@postmortem_hook.disable
|
|
1431
|
+
end
|
|
1122
1432
|
end
|
|
1123
1433
|
end
|
|
1124
1434
|
end
|
|
@@ -1165,18 +1475,18 @@ module DEBUGGER__
|
|
|
1165
1475
|
# start methods
|
|
1166
1476
|
|
|
1167
1477
|
def self.start nonstop: false, **kw
|
|
1168
|
-
set_config(kw)
|
|
1478
|
+
CONFIG.set_config(**kw)
|
|
1169
1479
|
|
|
1170
1480
|
unless defined? SESSION
|
|
1171
|
-
require_relative '
|
|
1172
|
-
initialize_session
|
|
1481
|
+
require_relative 'local'
|
|
1482
|
+
initialize_session UI_LocalConsole.new
|
|
1173
1483
|
end
|
|
1174
1484
|
|
|
1175
1485
|
setup_initial_suspend unless nonstop
|
|
1176
1486
|
end
|
|
1177
1487
|
|
|
1178
|
-
def self.open host: nil, port:
|
|
1179
|
-
set_config(kw)
|
|
1488
|
+
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
1489
|
+
CONFIG.set_config(**kw)
|
|
1180
1490
|
|
|
1181
1491
|
if port
|
|
1182
1492
|
open_tcp host: host, port: port, nonstop: nonstop
|
|
@@ -1186,7 +1496,7 @@ module DEBUGGER__
|
|
|
1186
1496
|
end
|
|
1187
1497
|
|
|
1188
1498
|
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
|
1189
|
-
set_config(kw)
|
|
1499
|
+
CONFIG.set_config(**kw)
|
|
1190
1500
|
require_relative 'server'
|
|
1191
1501
|
|
|
1192
1502
|
if defined? SESSION
|
|
@@ -1199,7 +1509,7 @@ module DEBUGGER__
|
|
|
1199
1509
|
end
|
|
1200
1510
|
|
|
1201
1511
|
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
1202
|
-
set_config(kw)
|
|
1512
|
+
CONFIG.set_config(**kw)
|
|
1203
1513
|
require_relative 'server'
|
|
1204
1514
|
|
|
1205
1515
|
if defined? SESSION
|
|
@@ -1214,42 +1524,24 @@ module DEBUGGER__
|
|
|
1214
1524
|
# boot utilities
|
|
1215
1525
|
|
|
1216
1526
|
def self.setup_initial_suspend
|
|
1217
|
-
if
|
|
1218
|
-
|
|
1219
|
-
|
|
1527
|
+
if !CONFIG[:nonstop]
|
|
1528
|
+
case
|
|
1529
|
+
when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
|
|
1530
|
+
add_line_breakpoint path, 0, oneshot: true, hook_call: false
|
|
1531
|
+
when loc = ::DEBUGGER__.require_location
|
|
1532
|
+
# require 'debug/start' or 'debug'
|
|
1220
1533
|
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
|
1221
1534
|
else
|
|
1222
1535
|
# -r
|
|
1223
|
-
add_line_breakpoint $0,
|
|
1536
|
+
add_line_breakpoint $0, 0, oneshot: true, hook_call: false
|
|
1224
1537
|
end
|
|
1225
1538
|
end
|
|
1226
1539
|
end
|
|
1227
1540
|
|
|
1228
1541
|
class << self
|
|
1229
1542
|
define_method :initialize_session do |ui|
|
|
1230
|
-
DEBUGGER__.
|
|
1231
|
-
|
|
1543
|
+
DEBUGGER__.info "Session start (pid: #{Process.pid})"
|
|
1232
1544
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
|
1233
|
-
|
|
1234
|
-
# default breakpoints
|
|
1235
|
-
|
|
1236
|
-
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
|
1237
|
-
|
|
1238
|
-
Binding.module_eval do
|
|
1239
|
-
def break pre: nil, do: nil
|
|
1240
|
-
return unless SESSION.active?
|
|
1241
|
-
|
|
1242
|
-
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1243
|
-
cmds = ['binding.break', pre, do_expr]
|
|
1244
|
-
end
|
|
1245
|
-
|
|
1246
|
-
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
|
1247
|
-
true
|
|
1248
|
-
end
|
|
1249
|
-
alias b break
|
|
1250
|
-
# alias bp break
|
|
1251
|
-
end
|
|
1252
|
-
|
|
1253
1545
|
load_rc
|
|
1254
1546
|
end
|
|
1255
1547
|
end
|
|
@@ -1258,10 +1550,10 @@ module DEBUGGER__
|
|
|
1258
1550
|
[[File.expand_path('~/.rdbgrc'), true],
|
|
1259
1551
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
|
1260
1552
|
# ['./.rdbgrc', true], # disable because of security concern
|
|
1261
|
-
[
|
|
1553
|
+
[CONFIG[:init_script], false],
|
|
1262
1554
|
].each{|(path, rc)|
|
|
1263
1555
|
next unless path
|
|
1264
|
-
next if rc &&
|
|
1556
|
+
next if rc && CONFIG[:no_rc] # ignore rc
|
|
1265
1557
|
|
|
1266
1558
|
if File.file? path
|
|
1267
1559
|
if path.end_with?('.rb')
|
|
@@ -1275,62 +1567,12 @@ module DEBUGGER__
|
|
|
1275
1567
|
}
|
|
1276
1568
|
|
|
1277
1569
|
# given debug commands
|
|
1278
|
-
if
|
|
1279
|
-
cmds =
|
|
1570
|
+
if CONFIG[:commands]
|
|
1571
|
+
cmds = CONFIG[:commands].split(';;')
|
|
1280
1572
|
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
|
1281
1573
|
end
|
|
1282
1574
|
end
|
|
1283
1575
|
|
|
1284
|
-
def self.parse_help
|
|
1285
|
-
helps = Hash.new{|h, k| h[k] = []}
|
|
1286
|
-
desc = cat = nil
|
|
1287
|
-
cmds = []
|
|
1288
|
-
|
|
1289
|
-
File.read(__FILE__).each_line do |line|
|
|
1290
|
-
case line
|
|
1291
|
-
when /\A\s*### (.+)/
|
|
1292
|
-
cat = $1
|
|
1293
|
-
break if $1 == 'END'
|
|
1294
|
-
when /\A when (.+)/
|
|
1295
|
-
next unless cat
|
|
1296
|
-
next unless desc
|
|
1297
|
-
ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
|
|
1298
|
-
helps[cat] << [ws, desc]
|
|
1299
|
-
desc = nil
|
|
1300
|
-
cmds.concat ws
|
|
1301
|
-
when /\A\s+# (\s*\*.+)/
|
|
1302
|
-
if desc
|
|
1303
|
-
desc << "\n" + $1
|
|
1304
|
-
else
|
|
1305
|
-
desc = $1
|
|
1306
|
-
end
|
|
1307
|
-
end
|
|
1308
|
-
end
|
|
1309
|
-
@commands = cmds
|
|
1310
|
-
@helps = helps
|
|
1311
|
-
end
|
|
1312
|
-
|
|
1313
|
-
def self.helps
|
|
1314
|
-
(defined?(@helps) && @helps) || parse_help
|
|
1315
|
-
end
|
|
1316
|
-
|
|
1317
|
-
def self.commands
|
|
1318
|
-
(defined?(@commands) && @commands) || (parse_help; @commands)
|
|
1319
|
-
end
|
|
1320
|
-
|
|
1321
|
-
def self.help
|
|
1322
|
-
r = []
|
|
1323
|
-
self.helps.each{|cat, cmds|
|
|
1324
|
-
r << "### #{cat}"
|
|
1325
|
-
r << ''
|
|
1326
|
-
cmds.each{|ws, desc|
|
|
1327
|
-
r << desc
|
|
1328
|
-
}
|
|
1329
|
-
r << ''
|
|
1330
|
-
}
|
|
1331
|
-
r.join("\n")
|
|
1332
|
-
end
|
|
1333
|
-
|
|
1334
1576
|
class ::Module
|
|
1335
1577
|
undef method_added
|
|
1336
1578
|
def method_added mid; end
|
|
@@ -1357,14 +1599,6 @@ module DEBUGGER__
|
|
|
1357
1599
|
end
|
|
1358
1600
|
end
|
|
1359
1601
|
|
|
1360
|
-
LOG_LEVELS = {
|
|
1361
|
-
UNKNOWN: 0,
|
|
1362
|
-
FATAL: 1,
|
|
1363
|
-
ERROR: 2,
|
|
1364
|
-
WARN: 3,
|
|
1365
|
-
INFO: 4,
|
|
1366
|
-
}.freeze
|
|
1367
|
-
|
|
1368
1602
|
def self.warn msg
|
|
1369
1603
|
log :WARN, msg
|
|
1370
1604
|
end
|
|
@@ -1386,4 +1620,97 @@ module DEBUGGER__
|
|
|
1386
1620
|
end
|
|
1387
1621
|
end
|
|
1388
1622
|
end
|
|
1623
|
+
|
|
1624
|
+
module ForkInterceptor
|
|
1625
|
+
def fork(&given_block)
|
|
1626
|
+
return super unless defined?(SESSION) && SESSION.active?
|
|
1627
|
+
|
|
1628
|
+
# before fork
|
|
1629
|
+
if CONFIG[:parent_on_fork]
|
|
1630
|
+
parent_hook = -> child_pid {
|
|
1631
|
+
# Do nothing
|
|
1632
|
+
}
|
|
1633
|
+
child_hook = -> {
|
|
1634
|
+
DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
|
|
1635
|
+
SESSION.deactivate
|
|
1636
|
+
}
|
|
1637
|
+
else
|
|
1638
|
+
parent_pid = Process.pid
|
|
1639
|
+
|
|
1640
|
+
parent_hook = -> child_pid {
|
|
1641
|
+
DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
|
|
1642
|
+
SESSION.deactivate
|
|
1643
|
+
|
|
1644
|
+
at_exit{
|
|
1645
|
+
trap(:SIGINT, :IGNORE)
|
|
1646
|
+
Process.waitpid(child_pid)
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
child_hook = -> {
|
|
1650
|
+
DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
|
1651
|
+
SESSION.activate on_fork: true
|
|
1652
|
+
}
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
if given_block
|
|
1656
|
+
new_block = proc {
|
|
1657
|
+
# after fork: child
|
|
1658
|
+
child_hook.call
|
|
1659
|
+
given_block.call
|
|
1660
|
+
}
|
|
1661
|
+
pid = super(&new_block)
|
|
1662
|
+
parent_hook.call(pid)
|
|
1663
|
+
pid
|
|
1664
|
+
else
|
|
1665
|
+
if pid = super
|
|
1666
|
+
# after fork: parent
|
|
1667
|
+
parent_hook.call pid
|
|
1668
|
+
else
|
|
1669
|
+
# after fork: child
|
|
1670
|
+
child_hook.call
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1673
|
+
pid
|
|
1674
|
+
end
|
|
1675
|
+
end
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
class ::Object
|
|
1679
|
+
include ForkInterceptor
|
|
1680
|
+
end
|
|
1681
|
+
|
|
1682
|
+
module ::Process
|
|
1683
|
+
class << self
|
|
1684
|
+
prepend ForkInterceptor
|
|
1685
|
+
end
|
|
1686
|
+
end
|
|
1687
|
+
end
|
|
1688
|
+
|
|
1689
|
+
class Binding
|
|
1690
|
+
def break pre: nil, do: nil
|
|
1691
|
+
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
|
1692
|
+
|
|
1693
|
+
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1694
|
+
cmds = ['binding.break', pre, do_expr]
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
|
1698
|
+
self
|
|
1699
|
+
end
|
|
1700
|
+
alias b break
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
module Kernel
|
|
1704
|
+
if RUBY_VERSION >= '2.7.0'
|
|
1705
|
+
eval <<~RUBY, binding, __FILE__, __LINE__
|
|
1706
|
+
def debugger(...)
|
|
1707
|
+
binding.break(...)
|
|
1708
|
+
end
|
|
1709
|
+
RUBY
|
|
1710
|
+
else
|
|
1711
|
+
def debugger pre: nil, do: nil
|
|
1712
|
+
b = binding
|
|
1713
|
+
b.break pre: pre, do: b.local_variable_get(:do)
|
|
1714
|
+
end
|
|
1715
|
+
end
|
|
1389
1716
|
end
|