debug 1.0.0.beta7 → 1.0.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/Gemfile +1 -0
- data/README.md +415 -250
- data/Rakefile +2 -1
- data/TODO.md +3 -8
- data/debug.gemspec +1 -0
- data/exe/rdbg +5 -8
- 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 +354 -175
- data/lib/debug/console.rb +75 -70
- data/lib/debug/frame_info.rb +40 -7
- data/lib/debug/local.rb +91 -0
- data/lib/debug/open.rb +1 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +74 -30
- data/lib/debug/server_dap.rb +32 -7
- data/lib/debug/session.rb +584 -299
- data/lib/debug/{run.rb → start.rb} +1 -1
- 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 +335 -227
- metadata +22 -5
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:
|
|
@@ -197,6 +197,12 @@ module DEBUGGER__
|
|
|
197
197
|
when 'pause'
|
|
198
198
|
send_response req
|
|
199
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
|
|
200
206
|
|
|
201
207
|
## query
|
|
202
208
|
when 'threads'
|
|
@@ -212,6 +218,7 @@ module DEBUGGER__
|
|
|
212
218
|
'evaluate',
|
|
213
219
|
'source'
|
|
214
220
|
@q_msg << req
|
|
221
|
+
|
|
215
222
|
else
|
|
216
223
|
raise "Unknown request: #{req.inspect}"
|
|
217
224
|
end
|
|
@@ -220,7 +227,7 @@ module DEBUGGER__
|
|
|
220
227
|
|
|
221
228
|
## called by the SESSION thread
|
|
222
229
|
|
|
223
|
-
def readline
|
|
230
|
+
def readline prompt
|
|
224
231
|
@q_msg.pop || 'kill!'
|
|
225
232
|
end
|
|
226
233
|
|
|
@@ -281,6 +288,13 @@ module DEBUGGER__
|
|
|
281
288
|
|
|
282
289
|
def process_dap_request req
|
|
283
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
|
+
|
|
284
298
|
when 'stackTrace'
|
|
285
299
|
tid = req.dig('arguments', 'threadId')
|
|
286
300
|
if tc = find_tc(tid)
|
|
@@ -365,7 +379,6 @@ module DEBUGGER__
|
|
|
365
379
|
else
|
|
366
380
|
fail_response req, message: 'not found...'
|
|
367
381
|
end
|
|
368
|
-
|
|
369
382
|
return :retry
|
|
370
383
|
else
|
|
371
384
|
raise "Unknown DAP request: #{req.inspect}"
|
|
@@ -438,8 +451,8 @@ module DEBUGGER__
|
|
|
438
451
|
when :backtrace
|
|
439
452
|
event! :dap_result, :backtrace, req, {
|
|
440
453
|
stackFrames: @target_frames.map.with_index{|frame, i|
|
|
441
|
-
path = frame.realpath
|
|
442
|
-
ref = frame.file_lines unless File.exist?(path)
|
|
454
|
+
path = frame.realpath || frame.path
|
|
455
|
+
ref = frame.file_lines unless path && File.exist?(path)
|
|
443
456
|
|
|
444
457
|
{
|
|
445
458
|
# id: ??? # filled by SESSION
|
|
@@ -457,7 +470,15 @@ module DEBUGGER__
|
|
|
457
470
|
when :scopes
|
|
458
471
|
fid = args.shift
|
|
459
472
|
frame = @target_frames[fid]
|
|
460
|
-
|
|
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
|
|
461
482
|
|
|
462
483
|
event! :dap_result, :scopes, req, scopes: [{
|
|
463
484
|
name: 'Local variables',
|
|
@@ -485,6 +506,10 @@ module DEBUGGER__
|
|
|
485
506
|
vars.unshift variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
|
486
507
|
vars.unshift variable('%return', frame.return_value) if frame.has_return_value
|
|
487
508
|
vars.unshift variable('%self', b.receiver)
|
|
509
|
+
elsif lvars = frame.local_variables
|
|
510
|
+
vars = lvars.map{|var, val|
|
|
511
|
+
variable(var, val)
|
|
512
|
+
}
|
|
488
513
|
else
|
|
489
514
|
vars = [variable('%self', frame.self)]
|
|
490
515
|
vars.push variable('%raised', frame.raised_exception) if frame.has_raised_exception
|
data/lib/debug/session.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative 'config'
|
|
|
7
7
|
require_relative 'thread_client'
|
|
8
8
|
require_relative 'source_repository'
|
|
9
9
|
require_relative 'breakpoint'
|
|
10
|
+
require_relative 'tracer'
|
|
10
11
|
|
|
11
12
|
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
|
12
13
|
|
|
@@ -54,6 +55,7 @@ end
|
|
|
54
55
|
|
|
55
56
|
module DEBUGGER__
|
|
56
57
|
PresetCommand = Struct.new(:commands, :source, :auto_continue)
|
|
58
|
+
class PostmortemError < RuntimeError; end
|
|
57
59
|
|
|
58
60
|
class Session
|
|
59
61
|
def initialize ui
|
|
@@ -65,54 +67,98 @@ module DEBUGGER__
|
|
|
65
67
|
# "Foo#bar" => MethodBreakpoint
|
|
66
68
|
# [:watch, ivar] => WatchIVarBreakpoint
|
|
67
69
|
# [:check, expr] => CheckBreakpoint
|
|
68
|
-
|
|
70
|
+
#
|
|
71
|
+
@tracers = []
|
|
72
|
+
@th_clients = nil # {Thread => ThreadClient}
|
|
69
73
|
@q_evt = Queue.new
|
|
70
74
|
@displays = []
|
|
71
75
|
@tc = nil
|
|
72
76
|
@tc_id = 0
|
|
73
77
|
@preset_command = nil
|
|
78
|
+
@postmortem_hook = nil
|
|
79
|
+
@postmortem = false
|
|
80
|
+
@thread_stopper = nil
|
|
74
81
|
|
|
75
82
|
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
|
|
76
83
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
|
77
84
|
@src_map = {} # {id => src}
|
|
78
85
|
|
|
79
86
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
|
80
|
-
|
|
81
|
-
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
82
|
-
end
|
|
87
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
83
88
|
}
|
|
84
89
|
@tp_load_script.enable
|
|
85
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
|
|
86
110
|
@session_server = Thread.new do
|
|
111
|
+
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
|
87
112
|
Thread.current.abort_on_exception = true
|
|
88
113
|
session_server_main
|
|
89
114
|
end
|
|
90
115
|
|
|
91
|
-
@management_threads = [@session_server]
|
|
92
|
-
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
|
93
|
-
|
|
94
116
|
setup_threads
|
|
95
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
|
+
|
|
96
131
|
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
end
|
|
132
|
+
th = Thread.current
|
|
133
|
+
ThreadClient.current.on_thread_begin th
|
|
100
134
|
}
|
|
101
135
|
@tp_thread_begin.enable
|
|
102
136
|
end
|
|
103
137
|
|
|
104
|
-
def
|
|
105
|
-
|
|
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
|
|
106
149
|
end
|
|
107
150
|
|
|
108
151
|
def reset_ui ui
|
|
109
152
|
@ui.close
|
|
110
153
|
@ui = ui
|
|
111
|
-
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def pop_event
|
|
157
|
+
@q_evt.pop
|
|
112
158
|
end
|
|
113
159
|
|
|
114
160
|
def session_server_main
|
|
115
|
-
while evt =
|
|
161
|
+
while evt = pop_event
|
|
116
162
|
# varible `@internal_info` is only used for test
|
|
117
163
|
tc, output, ev, @internal_info, *ev_args = evt
|
|
118
164
|
output.each{|str| @ui.puts str}
|
|
@@ -120,16 +166,26 @@ module DEBUGGER__
|
|
|
120
166
|
case ev
|
|
121
167
|
when :init
|
|
122
168
|
wait_command_loop tc
|
|
169
|
+
|
|
123
170
|
when :load
|
|
124
171
|
iseq, src = ev_args
|
|
125
172
|
on_load iseq, src
|
|
126
173
|
@ui.event :load
|
|
127
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
|
+
|
|
128
183
|
when :thread_begin
|
|
129
184
|
th = ev_args.shift
|
|
130
185
|
on_thread_begin th
|
|
131
186
|
@ui.event :thread_begin, th
|
|
132
187
|
tc << :continue
|
|
188
|
+
|
|
133
189
|
when :suspend
|
|
134
190
|
case ev_args.first
|
|
135
191
|
when :breakpoint
|
|
@@ -142,10 +198,12 @@ module DEBUGGER__
|
|
|
142
198
|
end
|
|
143
199
|
|
|
144
200
|
if @displays.empty?
|
|
201
|
+
stop_all_threads
|
|
145
202
|
wait_command_loop tc
|
|
146
203
|
else
|
|
147
204
|
tc << [:eval, :display, @displays]
|
|
148
205
|
end
|
|
206
|
+
|
|
149
207
|
when :result
|
|
150
208
|
case ev_args.first
|
|
151
209
|
when :try_display
|
|
@@ -156,14 +214,22 @@ module DEBUGGER__
|
|
|
156
214
|
@ui.puts "canceled: #{@displays.pop}"
|
|
157
215
|
end
|
|
158
216
|
end
|
|
217
|
+
stop_all_threads
|
|
218
|
+
|
|
159
219
|
when :method_breakpoint, :watch_breakpoint
|
|
160
220
|
bp = ev_args[1]
|
|
161
221
|
if bp
|
|
162
|
-
|
|
222
|
+
add_bp(bp)
|
|
163
223
|
show_bps bp
|
|
164
224
|
else
|
|
165
225
|
# can't make a bp
|
|
166
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}"
|
|
167
233
|
else
|
|
168
234
|
# ignore
|
|
169
235
|
end
|
|
@@ -176,27 +242,24 @@ module DEBUGGER__
|
|
|
176
242
|
end
|
|
177
243
|
end
|
|
178
244
|
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
|
|
245
|
+
deactivate
|
|
184
246
|
end
|
|
185
247
|
|
|
186
248
|
def add_preset_commands name, cmds, kick: true, continue: true
|
|
187
249
|
cs = cmds.map{|c|
|
|
188
|
-
c
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
ThreadClient.current.on_init name if kick
|
|
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)
|
|
199
260
|
end
|
|
261
|
+
|
|
262
|
+
ThreadClient.current.on_init name if kick
|
|
200
263
|
end
|
|
201
264
|
|
|
202
265
|
def source iseq
|
|
@@ -213,20 +276,26 @@ module DEBUGGER__
|
|
|
213
276
|
|
|
214
277
|
def wait_command_loop tc
|
|
215
278
|
@tc = tc
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
end
|
|
224
|
-
rescue Interrupt
|
|
225
|
-
retry
|
|
279
|
+
|
|
280
|
+
loop do
|
|
281
|
+
case wait_command
|
|
282
|
+
when :retry
|
|
283
|
+
# nothing
|
|
284
|
+
else
|
|
285
|
+
break
|
|
226
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) '
|
|
227
298
|
end
|
|
228
|
-
ensure
|
|
229
|
-
@tc = nil
|
|
230
299
|
end
|
|
231
300
|
|
|
232
301
|
def wait_command
|
|
@@ -234,7 +303,9 @@ module DEBUGGER__
|
|
|
234
303
|
if @preset_command.commands.empty?
|
|
235
304
|
if @preset_command.auto_continue
|
|
236
305
|
@preset_command = nil
|
|
306
|
+
|
|
237
307
|
@tc << :continue
|
|
308
|
+
restart_all_threads
|
|
238
309
|
return
|
|
239
310
|
else
|
|
240
311
|
@preset_command = nil
|
|
@@ -246,7 +317,7 @@ module DEBUGGER__
|
|
|
246
317
|
end
|
|
247
318
|
else
|
|
248
319
|
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
|
|
249
|
-
line = @ui.readline
|
|
320
|
+
line = @ui.readline prompt
|
|
250
321
|
end
|
|
251
322
|
|
|
252
323
|
case line
|
|
@@ -280,27 +351,37 @@ module DEBUGGER__
|
|
|
280
351
|
|
|
281
352
|
# * `s[tep]`
|
|
282
353
|
# * Step in. Resume the program until next breakable point.
|
|
354
|
+
# * `s[tep] <n>`
|
|
355
|
+
# * Step in, resume the program at `<n>`th breakable point.
|
|
283
356
|
when 's', 'step'
|
|
284
357
|
cancel_auto_continue
|
|
285
|
-
|
|
358
|
+
check_postmortem
|
|
359
|
+
step_command :in, arg
|
|
286
360
|
|
|
287
361
|
# * `n[ext]`
|
|
288
362
|
# * Step over. Resume the program until next line.
|
|
363
|
+
# * `n[ext] <n>`
|
|
364
|
+
# * Step over, same as `step <n>`.
|
|
289
365
|
when 'n', 'next'
|
|
290
366
|
cancel_auto_continue
|
|
291
|
-
|
|
367
|
+
check_postmortem
|
|
368
|
+
step_command :next, arg
|
|
292
369
|
|
|
293
370
|
# * `fin[ish]`
|
|
294
371
|
# * Finish this frame. Resume the program until the current frame is finished.
|
|
372
|
+
# * `fin[ish] <n>`
|
|
373
|
+
# * Finish frames, same as `step <n>`.
|
|
295
374
|
when 'fin', 'finish'
|
|
296
375
|
cancel_auto_continue
|
|
297
|
-
|
|
376
|
+
check_postmortem
|
|
377
|
+
step_command :finish, arg
|
|
298
378
|
|
|
299
379
|
# * `c[ontinue]`
|
|
300
380
|
# * Resume the program.
|
|
301
381
|
when 'c', 'continue'
|
|
302
382
|
cancel_auto_continue
|
|
303
383
|
@tc << :continue
|
|
384
|
+
restart_all_threads
|
|
304
385
|
|
|
305
386
|
# * `q[uit]` or `Ctrl-D`
|
|
306
387
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
|
@@ -308,6 +389,7 @@ module DEBUGGER__
|
|
|
308
389
|
if ask 'Really quit?'
|
|
309
390
|
@ui.quit arg.to_i
|
|
310
391
|
@tc << :continue
|
|
392
|
+
restart_all_threads
|
|
311
393
|
else
|
|
312
394
|
return :retry
|
|
313
395
|
end
|
|
@@ -316,7 +398,7 @@ module DEBUGGER__
|
|
|
316
398
|
# * Same as q[uit] but without the confirmation prompt.
|
|
317
399
|
when 'q!', 'quit!'
|
|
318
400
|
@ui.quit arg.to_i
|
|
319
|
-
|
|
401
|
+
restart_all_threads
|
|
320
402
|
|
|
321
403
|
# * `kill`
|
|
322
404
|
# * Stop the debuggee process with `Kernal#exit!`.
|
|
@@ -354,6 +436,8 @@ module DEBUGGER__
|
|
|
354
436
|
# * break if: `<expr>` is true at any lines.
|
|
355
437
|
# * Note that this feature is super slow.
|
|
356
438
|
when 'b', 'break'
|
|
439
|
+
check_postmortem
|
|
440
|
+
|
|
357
441
|
if arg == nil
|
|
358
442
|
show_bps
|
|
359
443
|
return :retry
|
|
@@ -370,6 +454,7 @@ module DEBUGGER__
|
|
|
370
454
|
|
|
371
455
|
# skip
|
|
372
456
|
when 'bv'
|
|
457
|
+
check_postmortem
|
|
373
458
|
require 'json'
|
|
374
459
|
|
|
375
460
|
h = Hash.new{|h, k| h[k] = []}
|
|
@@ -396,8 +481,10 @@ module DEBUGGER__
|
|
|
396
481
|
# * `catch <Error>`
|
|
397
482
|
# * Set breakpoint on raising `<Error>`.
|
|
398
483
|
when 'catch'
|
|
484
|
+
check_postmortem
|
|
485
|
+
|
|
399
486
|
if arg
|
|
400
|
-
bp =
|
|
487
|
+
bp = repl_add_catch_breakpoint arg
|
|
401
488
|
show_bps bp if bp
|
|
402
489
|
else
|
|
403
490
|
show_bps
|
|
@@ -408,6 +495,8 @@ module DEBUGGER__
|
|
|
408
495
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
|
409
496
|
# * Note that this feature is super slow.
|
|
410
497
|
when 'wat', 'watch'
|
|
498
|
+
check_postmortem
|
|
499
|
+
|
|
411
500
|
if arg && arg.match?(/\A@\w+/)
|
|
412
501
|
@tc << [:breakpoint, :watch, arg]
|
|
413
502
|
else
|
|
@@ -420,15 +509,17 @@ module DEBUGGER__
|
|
|
420
509
|
# * `del[ete] <bpnum>`
|
|
421
510
|
# * delete specified breakpoint.
|
|
422
511
|
when 'del', 'delete'
|
|
512
|
+
check_postmortem
|
|
513
|
+
|
|
423
514
|
bp =
|
|
424
515
|
case arg
|
|
425
516
|
when nil
|
|
426
517
|
show_bps
|
|
427
518
|
if ask "Remove all breakpoints?", 'N'
|
|
428
|
-
|
|
519
|
+
delete_bp
|
|
429
520
|
end
|
|
430
521
|
when /\d+/
|
|
431
|
-
|
|
522
|
+
delete_bp arg.to_i
|
|
432
523
|
else
|
|
433
524
|
nil
|
|
434
525
|
end
|
|
@@ -501,25 +592,57 @@ module DEBUGGER__
|
|
|
501
592
|
|
|
502
593
|
@tc << [:show, :edit, arg]
|
|
503
594
|
|
|
504
|
-
# * `i[nfo]
|
|
595
|
+
# * `i[nfo]`
|
|
596
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
|
597
|
+
# * `i[nfo] l[ocal[s]]`
|
|
505
598
|
# * Show information about the current frame (local variables)
|
|
506
599
|
# * It includes `self` as `%self` and a return value as `%return`.
|
|
600
|
+
# * `i[nfo] i[var[s]]` or `i[nfo] instance`
|
|
601
|
+
# * Show information about insttance variables about `self`.
|
|
602
|
+
# * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
|
|
603
|
+
# * Show information about accessible constants except toplevel constants.
|
|
604
|
+
# * `i[nfo] g[lobal[s]]`
|
|
605
|
+
# * Show information about global variables
|
|
606
|
+
# * `i[nfo] ... </pattern/>`
|
|
607
|
+
# * Filter the output with `</pattern/>`.
|
|
507
608
|
# * `i[nfo] th[read[s]]`
|
|
508
609
|
# * Show all threads (same as `th[read]`).
|
|
509
610
|
when 'i', 'info'
|
|
510
|
-
|
|
611
|
+
if /\/(.+)\/\z/ =~ arg
|
|
612
|
+
pat = Regexp.compile($1)
|
|
613
|
+
sub = $~.pre_match.strip
|
|
614
|
+
else
|
|
615
|
+
sub = arg
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
case sub
|
|
511
619
|
when nil
|
|
512
|
-
@tc << [:show, :
|
|
513
|
-
when 'l',
|
|
514
|
-
@tc << [:show, :
|
|
620
|
+
@tc << [:show, :default, pat] # something useful
|
|
621
|
+
when 'l', /^locals?/
|
|
622
|
+
@tc << [:show, :locals, pat]
|
|
623
|
+
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
|
624
|
+
@tc << [:show, :ivars, pat]
|
|
625
|
+
when 'c', /^consts?/i, /^constants?/i
|
|
626
|
+
@tc << [:show, :consts, pat]
|
|
627
|
+
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
|
628
|
+
@tc << [:show, :globals, pat]
|
|
515
629
|
when 'th', /threads?/
|
|
516
630
|
thread_list
|
|
517
631
|
return :retry
|
|
518
632
|
else
|
|
633
|
+
@ui.puts "unrecognized argument for info command: #{arg}"
|
|
519
634
|
show_help 'info'
|
|
520
635
|
return :retry
|
|
521
636
|
end
|
|
522
637
|
|
|
638
|
+
# * `o[utline]` or `ls`
|
|
639
|
+
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
|
640
|
+
# * `o[utline] <expr>` or `ls <expr>`
|
|
641
|
+
# * Show you available methods and instance variables of the given object.
|
|
642
|
+
# * If the object is a class/module, it also lists its constants.
|
|
643
|
+
when 'outline', 'o', 'ls'
|
|
644
|
+
@tc << [:show, :outline, arg]
|
|
645
|
+
|
|
523
646
|
# * `display`
|
|
524
647
|
# * Show display setting.
|
|
525
648
|
# * `display <expr>`
|
|
@@ -550,28 +673,6 @@ module DEBUGGER__
|
|
|
550
673
|
end
|
|
551
674
|
return :retry
|
|
552
675
|
|
|
553
|
-
# * `trace [on|off]`
|
|
554
|
-
# * enable or disable line tracer.
|
|
555
|
-
when 'trace'
|
|
556
|
-
case arg
|
|
557
|
-
when 'on'
|
|
558
|
-
dir = __dir__
|
|
559
|
-
@tracer ||= TracePoint.new(:call, :return, :b_call, :b_return, :line, :class, :end){|tp|
|
|
560
|
-
next if File.dirname(tp.path) == dir
|
|
561
|
-
next if tp.path == '<internal:trace_point>'
|
|
562
|
-
# Skip when `JSON.generate` is called during tests
|
|
563
|
-
next if tp.binding.eval('self').to_s == 'JSON' and ENV['RUBY_DEBUG_TEST_MODE']
|
|
564
|
-
# next if tp.event != :line
|
|
565
|
-
@ui.puts pretty_tp(tp)
|
|
566
|
-
}
|
|
567
|
-
@tracer.enable
|
|
568
|
-
when 'off'
|
|
569
|
-
@tracer && @tracer.disable
|
|
570
|
-
end
|
|
571
|
-
enabled = (@tracer && @tracer.enabled?) ? true : false
|
|
572
|
-
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
|
573
|
-
return :retry
|
|
574
|
-
|
|
575
676
|
### Frame control
|
|
576
677
|
|
|
577
678
|
# * `f[rame]`
|
|
@@ -603,10 +704,16 @@ module DEBUGGER__
|
|
|
603
704
|
when 'pp'
|
|
604
705
|
@tc << [:eval, :pp, arg.to_s]
|
|
605
706
|
|
|
606
|
-
# * `
|
|
707
|
+
# * `eval <expr>`
|
|
607
708
|
# * Evaluate `<expr>` on the current frame.
|
|
608
|
-
when '
|
|
609
|
-
|
|
709
|
+
when 'eval', 'call'
|
|
710
|
+
if arg == nil || arg.empty?
|
|
711
|
+
show_help 'eval'
|
|
712
|
+
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
|
713
|
+
return :retry
|
|
714
|
+
else
|
|
715
|
+
@tc << [:eval, :call, arg]
|
|
716
|
+
end
|
|
610
717
|
|
|
611
718
|
# * `irb`
|
|
612
719
|
# * Invoke `irb` on the current frame.
|
|
@@ -620,6 +727,105 @@ module DEBUGGER__
|
|
|
620
727
|
# don't repeat irb command
|
|
621
728
|
@repl_prev_line = nil
|
|
622
729
|
|
|
730
|
+
### Trace
|
|
731
|
+
# * `trace`
|
|
732
|
+
# * Show available tracers list.
|
|
733
|
+
# * `trace line`
|
|
734
|
+
# * Add a line tracer. It indicates line events.
|
|
735
|
+
# * `trace call`
|
|
736
|
+
# * Add a call tracer. It indicate call/return events.
|
|
737
|
+
# * `trace exception`
|
|
738
|
+
# * Add an exception tracer. It indicates raising exceptions.
|
|
739
|
+
# * `trace object <expr>`
|
|
740
|
+
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
|
741
|
+
# * `trace ... </pattern/>`
|
|
742
|
+
# * Indicates only matched events to `</pattern/>` (RegExp).
|
|
743
|
+
# * `trace ... into: <file>`
|
|
744
|
+
# * Save trace information into: `<file>`.
|
|
745
|
+
# * `trace off <num>`
|
|
746
|
+
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
|
747
|
+
# * `trace off [line|call|pass]`
|
|
748
|
+
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
|
749
|
+
when 'trace'
|
|
750
|
+
if (re = /\s+into:\s*(.+)/) =~ arg
|
|
751
|
+
into = $1
|
|
752
|
+
arg.sub!(re, '')
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
if (re = /\s\/(.+)\/\z/) =~ arg
|
|
756
|
+
pattern = $1
|
|
757
|
+
arg.sub!(re, '')
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
case arg
|
|
761
|
+
when nil
|
|
762
|
+
@ui.puts 'Tracers:'
|
|
763
|
+
@tracers.each_with_index{|t, i|
|
|
764
|
+
@ui.puts "* \##{i} #{t}"
|
|
765
|
+
}
|
|
766
|
+
@ui.puts
|
|
767
|
+
return :retry
|
|
768
|
+
|
|
769
|
+
when /\Aline\z/
|
|
770
|
+
@tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
|
|
771
|
+
@ui.puts "Enable #{t.to_s}"
|
|
772
|
+
return :retry
|
|
773
|
+
|
|
774
|
+
when /\Acall\z/
|
|
775
|
+
@tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
|
|
776
|
+
@ui.puts "Enable #{t.to_s}"
|
|
777
|
+
return :retry
|
|
778
|
+
|
|
779
|
+
when /\Aexception\z/
|
|
780
|
+
@tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
|
781
|
+
@ui.puts "Enable #{t.to_s}"
|
|
782
|
+
return :retry
|
|
783
|
+
|
|
784
|
+
when /\Aobject\s+(.+)/
|
|
785
|
+
@tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
|
786
|
+
|
|
787
|
+
when /\Aoff\s+(\d+)\z/
|
|
788
|
+
if t = @tracers[$1.to_i]
|
|
789
|
+
t.disable
|
|
790
|
+
@ui.puts "Disable #{t.to_s}"
|
|
791
|
+
else
|
|
792
|
+
@ui.puts "Unmatched: #{$1}"
|
|
793
|
+
end
|
|
794
|
+
return :retry
|
|
795
|
+
|
|
796
|
+
when /\Aoff(\s+(line|call|exception|object))?\z/
|
|
797
|
+
@tracers.each{|t|
|
|
798
|
+
if $2.nil? || t.type == $2
|
|
799
|
+
t.disable
|
|
800
|
+
@ui.puts "Disable #{t.to_s}"
|
|
801
|
+
end
|
|
802
|
+
}
|
|
803
|
+
return :retry
|
|
804
|
+
|
|
805
|
+
else
|
|
806
|
+
@ui.puts "Unknown trace option: #{arg.inspect}"
|
|
807
|
+
return :retry
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
# Record
|
|
811
|
+
# * `record`
|
|
812
|
+
# * Show recording status.
|
|
813
|
+
# * `record [on|off]`
|
|
814
|
+
# * Start/Stop recording.
|
|
815
|
+
# * `step back`
|
|
816
|
+
# * Start replay. Step back with the last execution log.
|
|
817
|
+
# * `s[tep]` does stepping forward with the last log.
|
|
818
|
+
# * `step reset`
|
|
819
|
+
# * Stop replay .
|
|
820
|
+
when 'record'
|
|
821
|
+
case arg
|
|
822
|
+
when nil, 'on', 'off'
|
|
823
|
+
@tc << [:record, arg&.to_sym]
|
|
824
|
+
else
|
|
825
|
+
@ui.puts "unknown command: #{arg}"
|
|
826
|
+
return :retry
|
|
827
|
+
end
|
|
828
|
+
|
|
623
829
|
### Thread control
|
|
624
830
|
|
|
625
831
|
# * `th[read]`
|
|
@@ -652,13 +858,28 @@ module DEBUGGER__
|
|
|
652
858
|
config_command arg
|
|
653
859
|
return :retry
|
|
654
860
|
|
|
861
|
+
# * `source <file>`
|
|
862
|
+
# * Evaluate lines in `<file>` as debug commands.
|
|
863
|
+
when 'source'
|
|
864
|
+
if arg
|
|
865
|
+
begin
|
|
866
|
+
cmds = File.readlines(path = File.expand_path(arg))
|
|
867
|
+
add_preset_commands path, cmds, kick: true, continue: false
|
|
868
|
+
rescue Errno::ENOENT
|
|
869
|
+
@ui.puts "File not found: #{arg}"
|
|
870
|
+
end
|
|
871
|
+
else
|
|
872
|
+
show_help 'source'
|
|
873
|
+
end
|
|
874
|
+
return :retry
|
|
875
|
+
|
|
655
876
|
### Help
|
|
656
877
|
|
|
657
878
|
# * `h[elp]`
|
|
658
879
|
# * Show help for all commands.
|
|
659
880
|
# * `h[elp] <command>`
|
|
660
881
|
# * Show help for the given command.
|
|
661
|
-
when 'h', 'help'
|
|
882
|
+
when 'h', 'help', '?'
|
|
662
883
|
if arg
|
|
663
884
|
show_help arg
|
|
664
885
|
else
|
|
@@ -668,21 +889,56 @@ module DEBUGGER__
|
|
|
668
889
|
|
|
669
890
|
### END
|
|
670
891
|
else
|
|
671
|
-
@
|
|
892
|
+
@tc << [:eval, :pp, line]
|
|
893
|
+
=begin
|
|
672
894
|
@repl_prev_line = nil
|
|
895
|
+
@ui.puts "unknown command: #{line}"
|
|
896
|
+
begin
|
|
897
|
+
require 'did_you_mean'
|
|
898
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
|
899
|
+
correction = spell_checker.correct(line.split(/\s/).first || '')
|
|
900
|
+
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
|
901
|
+
rescue LoadError
|
|
902
|
+
# Don't use D
|
|
903
|
+
end
|
|
673
904
|
return :retry
|
|
905
|
+
=end
|
|
674
906
|
end
|
|
675
907
|
|
|
676
908
|
rescue Interrupt
|
|
677
909
|
return :retry
|
|
678
910
|
rescue SystemExit
|
|
679
911
|
raise
|
|
912
|
+
rescue PostmortemError => e
|
|
913
|
+
@ui.puts e.message
|
|
914
|
+
return :retry
|
|
680
915
|
rescue Exception => e
|
|
681
916
|
@ui.puts "[REPL ERROR] #{e.inspect}"
|
|
682
917
|
@ui.puts e.backtrace.map{|e| ' ' + e}
|
|
683
918
|
return :retry
|
|
684
919
|
end
|
|
685
920
|
|
|
921
|
+
def step_command type, arg
|
|
922
|
+
case arg
|
|
923
|
+
when nil
|
|
924
|
+
@tc << [:step, type]
|
|
925
|
+
restart_all_threads
|
|
926
|
+
when /\A\d+\z/
|
|
927
|
+
@tc << [:step, type, arg.to_i]
|
|
928
|
+
restart_all_threads
|
|
929
|
+
when /\Aback\z/, /\Areset\z/
|
|
930
|
+
if type != :in
|
|
931
|
+
@ui.puts "only `step #{arg}` is supported."
|
|
932
|
+
:retry
|
|
933
|
+
else
|
|
934
|
+
@tc << [:step, arg.to_sym]
|
|
935
|
+
end
|
|
936
|
+
else
|
|
937
|
+
@ui.puts "Unknown option: #{arg}"
|
|
938
|
+
:retry
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
|
|
686
942
|
def config_show key
|
|
687
943
|
key = key.to_sym
|
|
688
944
|
if CONFIG_SET[key]
|
|
@@ -704,9 +960,9 @@ module DEBUGGER__
|
|
|
704
960
|
if CONFIG_SET[key = key.to_sym]
|
|
705
961
|
begin
|
|
706
962
|
if append
|
|
707
|
-
|
|
963
|
+
CONFIG.append_config(key, val)
|
|
708
964
|
else
|
|
709
|
-
|
|
965
|
+
CONFIG[key] = val
|
|
710
966
|
end
|
|
711
967
|
rescue => e
|
|
712
968
|
@ui.puts e.message
|
|
@@ -725,7 +981,7 @@ module DEBUGGER__
|
|
|
725
981
|
|
|
726
982
|
when /\Aunset\s+(.+)\z/
|
|
727
983
|
if CONFIG_SET[key = $1.to_sym]
|
|
728
|
-
|
|
984
|
+
CONFIG[key] = nil
|
|
729
985
|
end
|
|
730
986
|
config_show key
|
|
731
987
|
|
|
@@ -780,51 +1036,7 @@ module DEBUGGER__
|
|
|
780
1036
|
end
|
|
781
1037
|
end
|
|
782
1038
|
|
|
783
|
-
|
|
784
|
-
if klass.singleton_class?
|
|
785
|
-
"#{receiver}."
|
|
786
|
-
else
|
|
787
|
-
"#{klass}#"
|
|
788
|
-
end
|
|
789
|
-
end
|
|
790
|
-
|
|
791
|
-
def pretty_tp tp
|
|
792
|
-
loc = "#{tp.path}:#{tp.lineno}"
|
|
793
|
-
level = caller.size
|
|
794
|
-
|
|
795
|
-
info =
|
|
796
|
-
case tp.event
|
|
797
|
-
when :line
|
|
798
|
-
"line at #{loc}"
|
|
799
|
-
when :call, :c_call
|
|
800
|
-
klass = tp.defined_class
|
|
801
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} at #{loc}"
|
|
802
|
-
when :return, :c_return
|
|
803
|
-
klass = tp.defined_class
|
|
804
|
-
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} => #{tp.return_value.inspect} at #{loc}"
|
|
805
|
-
when :b_call
|
|
806
|
-
"b_call at #{loc}"
|
|
807
|
-
when :b_return
|
|
808
|
-
"b_return => #{tp.return_value} at #{loc}"
|
|
809
|
-
when :class
|
|
810
|
-
"class #{tp.self} at #{loc}"
|
|
811
|
-
when :end
|
|
812
|
-
"class #{tp.self} end at #{loc}"
|
|
813
|
-
else
|
|
814
|
-
"#{tp.event} at #{loc}"
|
|
815
|
-
end
|
|
816
|
-
|
|
817
|
-
case tp.event
|
|
818
|
-
when :call, :b_call, :return, :b_return, :class, :end
|
|
819
|
-
level -= 1
|
|
820
|
-
end
|
|
821
|
-
|
|
822
|
-
"Tracing:#{' ' * level} #{info}"
|
|
823
|
-
rescue => e
|
|
824
|
-
p e
|
|
825
|
-
pp e.backtrace
|
|
826
|
-
exit!
|
|
827
|
-
end
|
|
1039
|
+
# breakpoint management
|
|
828
1040
|
|
|
829
1041
|
def iterate_bps
|
|
830
1042
|
deleted_bps = []
|
|
@@ -856,7 +1068,29 @@ module DEBUGGER__
|
|
|
856
1068
|
nil
|
|
857
1069
|
end
|
|
858
1070
|
|
|
859
|
-
def
|
|
1071
|
+
def rehash_bps
|
|
1072
|
+
bps = @bps.values
|
|
1073
|
+
@bps.clear
|
|
1074
|
+
bps.each{|bp|
|
|
1075
|
+
add_bp bp
|
|
1076
|
+
}
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
def add_bp bp
|
|
1080
|
+
# don't repeat commands that add breakpoints
|
|
1081
|
+
@repl_prev_line = nil
|
|
1082
|
+
|
|
1083
|
+
if @bps.has_key? bp.key
|
|
1084
|
+
unless bp.duplicable?
|
|
1085
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
|
1086
|
+
bp.disable
|
|
1087
|
+
end
|
|
1088
|
+
else
|
|
1089
|
+
@bps[bp.key] = bp
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
def delete_bp arg = nil
|
|
860
1094
|
case arg
|
|
861
1095
|
when nil
|
|
862
1096
|
@bps.each{|key, bp| bp.delete}
|
|
@@ -895,14 +1129,14 @@ module DEBUGGER__
|
|
|
895
1129
|
|
|
896
1130
|
case expr[:sig]
|
|
897
1131
|
when /\A(\d+)\z/
|
|
898
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond:
|
|
1132
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
|
|
899
1133
|
when /\A(.+)[:\s+](\d+)\z/
|
|
900
|
-
add_line_breakpoint $1, $2.to_i, cond:
|
|
1134
|
+
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
|
901
1135
|
when /\A(.+)([\.\#])(.+)\z/
|
|
902
|
-
@tc << [:breakpoint, :method, $1, $2, $3,
|
|
1136
|
+
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
|
|
903
1137
|
return :noretry
|
|
904
1138
|
when nil
|
|
905
|
-
add_check_breakpoint
|
|
1139
|
+
add_check_breakpoint cond
|
|
906
1140
|
else
|
|
907
1141
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
|
908
1142
|
@ui.puts
|
|
@@ -910,6 +1144,34 @@ module DEBUGGER__
|
|
|
910
1144
|
end
|
|
911
1145
|
end
|
|
912
1146
|
|
|
1147
|
+
def repl_add_catch_breakpoint arg
|
|
1148
|
+
expr = parse_break arg.strip
|
|
1149
|
+
cond = expr[:if]
|
|
1150
|
+
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
|
1151
|
+
|
|
1152
|
+
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
|
|
1153
|
+
add_bp bp
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
def add_catch_breakpoint pat
|
|
1157
|
+
bp = CatchBreakpoint.new(pat)
|
|
1158
|
+
add_bp bp
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
def add_check_breakpoint expr
|
|
1162
|
+
bp = CheckBreakpoint.new(expr)
|
|
1163
|
+
add_bp bp
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
def add_line_breakpoint file, line, **kw
|
|
1167
|
+
file = resolve_path(file)
|
|
1168
|
+
bp = LineBreakpoint.new(file, line, **kw)
|
|
1169
|
+
|
|
1170
|
+
add_bp bp
|
|
1171
|
+
rescue Errno::ENOENT => e
|
|
1172
|
+
@ui.puts e.message
|
|
1173
|
+
end
|
|
1174
|
+
|
|
913
1175
|
# threads
|
|
914
1176
|
|
|
915
1177
|
def update_thread_list
|
|
@@ -918,17 +1180,15 @@ module DEBUGGER__
|
|
|
918
1180
|
unmanaged = []
|
|
919
1181
|
|
|
920
1182
|
list.each{|th|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
# ignore
|
|
926
|
-
when @th_clients.has_key?(th)
|
|
927
|
-
thcs << @th_clients[th]
|
|
1183
|
+
if thc = @th_clients[th]
|
|
1184
|
+
if !thc.management?
|
|
1185
|
+
thcs << thc
|
|
1186
|
+
end
|
|
928
1187
|
else
|
|
929
1188
|
unmanaged << th
|
|
930
1189
|
end
|
|
931
1190
|
}
|
|
1191
|
+
|
|
932
1192
|
return thcs.sort_by{|thc| thc.id}, unmanaged
|
|
933
1193
|
end
|
|
934
1194
|
|
|
@@ -955,7 +1215,7 @@ module DEBUGGER__
|
|
|
955
1215
|
thcs, _unmanaged_ths = update_thread_list
|
|
956
1216
|
|
|
957
1217
|
if tc = thcs[n]
|
|
958
|
-
if tc.
|
|
1218
|
+
if tc.waiting?
|
|
959
1219
|
@tc = tc
|
|
960
1220
|
else
|
|
961
1221
|
@ui.puts "#{tc.thread} is not controllable yet."
|
|
@@ -969,11 +1229,11 @@ module DEBUGGER__
|
|
|
969
1229
|
end
|
|
970
1230
|
|
|
971
1231
|
def setup_threads
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1232
|
+
@th_clients = {}
|
|
1233
|
+
|
|
1234
|
+
Thread.list.each{|th|
|
|
1235
|
+
thread_client_create(th)
|
|
1236
|
+
}
|
|
977
1237
|
end
|
|
978
1238
|
|
|
979
1239
|
def on_thread_begin th
|
|
@@ -985,8 +1245,7 @@ module DEBUGGER__
|
|
|
985
1245
|
end
|
|
986
1246
|
end
|
|
987
1247
|
|
|
988
|
-
def thread_client
|
|
989
|
-
thr = Thread.current
|
|
1248
|
+
def thread_client thr = Thread.current
|
|
990
1249
|
if @th_clients.has_key? thr
|
|
991
1250
|
@th_clients[thr]
|
|
992
1251
|
else
|
|
@@ -994,84 +1253,67 @@ module DEBUGGER__
|
|
|
994
1253
|
end
|
|
995
1254
|
end
|
|
996
1255
|
|
|
997
|
-
def
|
|
998
|
-
|
|
1256
|
+
private def thread_stopper
|
|
1257
|
+
@thread_stopper ||= TracePoint.new(:line) do
|
|
1258
|
+
# run on each thread
|
|
1259
|
+
tc = ThreadClient.current
|
|
1260
|
+
next if tc.management?
|
|
1261
|
+
next unless tc.running?
|
|
1262
|
+
next if tc == @tc
|
|
999
1263
|
|
|
1000
|
-
|
|
1001
|
-
TracePoint.new(:line) do
|
|
1002
|
-
th = Thread.current
|
|
1003
|
-
if current == th || @management_threads.include?(th)
|
|
1004
|
-
next
|
|
1005
|
-
else
|
|
1006
|
-
tc = ThreadClient.current
|
|
1007
|
-
tc.on_pause
|
|
1008
|
-
end
|
|
1009
|
-
end.enable do
|
|
1010
|
-
yield
|
|
1011
|
-
ensure
|
|
1012
|
-
@th_clients.each{|thr, tc|
|
|
1013
|
-
case thr
|
|
1014
|
-
when current, (@tc && @tc.thread)
|
|
1015
|
-
next
|
|
1016
|
-
else
|
|
1017
|
-
tc << :continue if thr != Thread.current
|
|
1018
|
-
end
|
|
1019
|
-
}
|
|
1020
|
-
end
|
|
1021
|
-
else
|
|
1022
|
-
yield
|
|
1264
|
+
tc.on_pause
|
|
1023
1265
|
end
|
|
1024
1266
|
end
|
|
1025
1267
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
pending_line_breakpoints do |bp|
|
|
1033
|
-
if bp.path == (iseq.absolute_path || iseq.path)
|
|
1034
|
-
bp.try_activate
|
|
1035
|
-
end
|
|
1036
|
-
end
|
|
1268
|
+
private def running_thread_clients_count
|
|
1269
|
+
@th_clients.count{|th, tc|
|
|
1270
|
+
next if tc.management?
|
|
1271
|
+
next unless tc.running?
|
|
1272
|
+
true
|
|
1273
|
+
}
|
|
1037
1274
|
end
|
|
1038
1275
|
|
|
1039
|
-
|
|
1276
|
+
private def waiting_thread_clients
|
|
1277
|
+
@th_clients.map{|th, tc|
|
|
1278
|
+
next if tc.management?
|
|
1279
|
+
next unless tc.waiting?
|
|
1280
|
+
tc
|
|
1281
|
+
}.compact
|
|
1282
|
+
end
|
|
1040
1283
|
|
|
1041
|
-
def
|
|
1042
|
-
|
|
1043
|
-
@repl_prev_line = nil
|
|
1284
|
+
private def stop_all_threads
|
|
1285
|
+
return if running_thread_clients_count == 0
|
|
1044
1286
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
@ui.puts "duplicated breakpoint: #{bp}"
|
|
1048
|
-
bp.disable
|
|
1049
|
-
end
|
|
1050
|
-
else
|
|
1051
|
-
@bps[bp.key] = bp
|
|
1052
|
-
end
|
|
1287
|
+
stopper = thread_stopper
|
|
1288
|
+
stopper.enable unless stopper.enabled?
|
|
1053
1289
|
end
|
|
1054
1290
|
|
|
1055
|
-
def
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1291
|
+
private def restart_all_threads
|
|
1292
|
+
stopper = thread_stopper
|
|
1293
|
+
stopper.disable if stopper.enabled?
|
|
1294
|
+
|
|
1295
|
+
waiting_thread_clients.each{|tc|
|
|
1296
|
+
next if @tc == tc
|
|
1297
|
+
tc << :continue
|
|
1060
1298
|
}
|
|
1299
|
+
@tc = nil
|
|
1061
1300
|
end
|
|
1062
1301
|
|
|
1063
|
-
|
|
1064
|
-
@bps.has_key? [file, line]
|
|
1065
|
-
end
|
|
1302
|
+
## event
|
|
1066
1303
|
|
|
1067
|
-
def
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
end
|
|
1304
|
+
def on_load iseq, src
|
|
1305
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
|
1306
|
+
@sr.add iseq, src
|
|
1071
1307
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1308
|
+
pending_line_breakpoints = @bps.find_all do |key, bp|
|
|
1309
|
+
LineBreakpoint === bp && !bp.iseq
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
pending_line_breakpoints.each do |_key, bp|
|
|
1313
|
+
if bp.path == (iseq.absolute_path || iseq.path)
|
|
1314
|
+
bp.try_activate
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1075
1317
|
end
|
|
1076
1318
|
|
|
1077
1319
|
def resolve_path file
|
|
@@ -1092,23 +1334,6 @@ module DEBUGGER__
|
|
|
1092
1334
|
raise
|
|
1093
1335
|
end
|
|
1094
1336
|
|
|
1095
|
-
def add_line_breakpoint file, line, **kw
|
|
1096
|
-
file = resolve_path(file)
|
|
1097
|
-
bp = LineBreakpoint.new(file, line, **kw)
|
|
1098
|
-
|
|
1099
|
-
add_breakpoint bp
|
|
1100
|
-
rescue Errno::ENOENT => e
|
|
1101
|
-
@ui.puts e.message
|
|
1102
|
-
end
|
|
1103
|
-
|
|
1104
|
-
def pending_line_breakpoints
|
|
1105
|
-
@bps.find_all do |key, bp|
|
|
1106
|
-
LineBreakpoint === bp && !bp.iseq
|
|
1107
|
-
end.each do |key, bp|
|
|
1108
|
-
yield bp
|
|
1109
|
-
end
|
|
1110
|
-
end
|
|
1111
|
-
|
|
1112
1337
|
def method_added tp
|
|
1113
1338
|
b = tp.binding
|
|
1114
1339
|
if var_name = b.local_variables.first
|
|
@@ -1137,10 +1362,55 @@ module DEBUGGER__
|
|
|
1137
1362
|
@ui.width
|
|
1138
1363
|
end
|
|
1139
1364
|
|
|
1140
|
-
def
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1365
|
+
def check_postmortem
|
|
1366
|
+
if @postmortem
|
|
1367
|
+
raise PostmortemError, "Can not use this command on postmortem mode."
|
|
1368
|
+
end
|
|
1369
|
+
end
|
|
1370
|
+
|
|
1371
|
+
def enter_postmortem_session frames
|
|
1372
|
+
@postmortem = true
|
|
1373
|
+
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
|
1374
|
+
ensure
|
|
1375
|
+
@postmortem = false
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
def postmortem=(is_enable)
|
|
1379
|
+
if is_enable
|
|
1380
|
+
unless @postmortem_hook
|
|
1381
|
+
@postmortem_hook = TracePoint.new(:raise){|tp|
|
|
1382
|
+
exc = tp.raised_exception
|
|
1383
|
+
frames = DEBUGGER__.capture_frames(__dir__)
|
|
1384
|
+
exc.instance_variable_set(:@postmortem_frames, frames)
|
|
1385
|
+
}
|
|
1386
|
+
at_exit{
|
|
1387
|
+
@postmortem_hook.disable
|
|
1388
|
+
if CONFIG[:postmortem] && (exc = $!) != nil
|
|
1389
|
+
exc = exc.cause while exc.cause
|
|
1390
|
+
|
|
1391
|
+
begin
|
|
1392
|
+
@ui.puts "Enter postmortem mode with #{exc.inspect}"
|
|
1393
|
+
@ui.puts exc.backtrace.map{|e| ' ' + e}
|
|
1394
|
+
@ui.puts "\n"
|
|
1395
|
+
|
|
1396
|
+
enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
|
|
1397
|
+
rescue SystemExit
|
|
1398
|
+
exit!
|
|
1399
|
+
rescue Exception => e
|
|
1400
|
+
@ui = STDERR unless @ui
|
|
1401
|
+
@ui.puts "Error while postmortem console: #{e.inspect}"
|
|
1402
|
+
end
|
|
1403
|
+
end
|
|
1404
|
+
}
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
if !@postmortem_hook.enabled?
|
|
1408
|
+
@postmortem_hook.enable
|
|
1409
|
+
end
|
|
1410
|
+
else
|
|
1411
|
+
if @postmortem_hook && @postmortem_hook.enabled?
|
|
1412
|
+
@postmortem_hook.disable
|
|
1413
|
+
end
|
|
1144
1414
|
end
|
|
1145
1415
|
end
|
|
1146
1416
|
end
|
|
@@ -1187,18 +1457,18 @@ module DEBUGGER__
|
|
|
1187
1457
|
# start methods
|
|
1188
1458
|
|
|
1189
1459
|
def self.start nonstop: false, **kw
|
|
1190
|
-
set_config(kw)
|
|
1460
|
+
CONFIG.set_config(**kw)
|
|
1191
1461
|
|
|
1192
1462
|
unless defined? SESSION
|
|
1193
|
-
require_relative '
|
|
1194
|
-
initialize_session
|
|
1463
|
+
require_relative 'local'
|
|
1464
|
+
initialize_session UI_LocalConsole.new
|
|
1195
1465
|
end
|
|
1196
1466
|
|
|
1197
1467
|
setup_initial_suspend unless nonstop
|
|
1198
1468
|
end
|
|
1199
1469
|
|
|
1200
|
-
def self.open host: nil, port:
|
|
1201
|
-
set_config(kw)
|
|
1470
|
+
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
1471
|
+
CONFIG.set_config(**kw)
|
|
1202
1472
|
|
|
1203
1473
|
if port
|
|
1204
1474
|
open_tcp host: host, port: port, nonstop: nonstop
|
|
@@ -1208,7 +1478,7 @@ module DEBUGGER__
|
|
|
1208
1478
|
end
|
|
1209
1479
|
|
|
1210
1480
|
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
|
1211
|
-
set_config(kw)
|
|
1481
|
+
CONFIG.set_config(**kw)
|
|
1212
1482
|
require_relative 'server'
|
|
1213
1483
|
|
|
1214
1484
|
if defined? SESSION
|
|
@@ -1221,7 +1491,7 @@ module DEBUGGER__
|
|
|
1221
1491
|
end
|
|
1222
1492
|
|
|
1223
1493
|
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
1224
|
-
set_config(kw)
|
|
1494
|
+
CONFIG.set_config(**kw)
|
|
1225
1495
|
require_relative 'server'
|
|
1226
1496
|
|
|
1227
1497
|
if defined? SESSION
|
|
@@ -1236,13 +1506,13 @@ module DEBUGGER__
|
|
|
1236
1506
|
# boot utilities
|
|
1237
1507
|
|
|
1238
1508
|
def self.setup_initial_suspend
|
|
1239
|
-
if
|
|
1509
|
+
if !CONFIG[:nonstop]
|
|
1240
1510
|
if loc = ::DEBUGGER__.require_location
|
|
1241
|
-
# require 'debug/
|
|
1511
|
+
# require 'debug/start' or 'debug'
|
|
1242
1512
|
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
|
1243
1513
|
else
|
|
1244
1514
|
# -r
|
|
1245
|
-
add_line_breakpoint $0,
|
|
1515
|
+
add_line_breakpoint $0, 0, oneshot: true, hook_call: false
|
|
1246
1516
|
end
|
|
1247
1517
|
end
|
|
1248
1518
|
end
|
|
@@ -1280,10 +1550,10 @@ module DEBUGGER__
|
|
|
1280
1550
|
[[File.expand_path('~/.rdbgrc'), true],
|
|
1281
1551
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
|
1282
1552
|
# ['./.rdbgrc', true], # disable because of security concern
|
|
1283
|
-
[
|
|
1553
|
+
[CONFIG[:init_script], false],
|
|
1284
1554
|
].each{|(path, rc)|
|
|
1285
1555
|
next unless path
|
|
1286
|
-
next if rc &&
|
|
1556
|
+
next if rc && CONFIG[:no_rc] # ignore rc
|
|
1287
1557
|
|
|
1288
1558
|
if File.file? path
|
|
1289
1559
|
if path.end_with?('.rb')
|
|
@@ -1297,62 +1567,12 @@ module DEBUGGER__
|
|
|
1297
1567
|
}
|
|
1298
1568
|
|
|
1299
1569
|
# given debug commands
|
|
1300
|
-
if
|
|
1301
|
-
cmds =
|
|
1570
|
+
if CONFIG[:commands]
|
|
1571
|
+
cmds = CONFIG[:commands].split(';;')
|
|
1302
1572
|
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
|
1303
1573
|
end
|
|
1304
1574
|
end
|
|
1305
1575
|
|
|
1306
|
-
def self.parse_help
|
|
1307
|
-
helps = Hash.new{|h, k| h[k] = []}
|
|
1308
|
-
desc = cat = nil
|
|
1309
|
-
cmds = []
|
|
1310
|
-
|
|
1311
|
-
File.read(__FILE__).each_line do |line|
|
|
1312
|
-
case line
|
|
1313
|
-
when /\A\s*### (.+)/
|
|
1314
|
-
cat = $1
|
|
1315
|
-
break if $1 == 'END'
|
|
1316
|
-
when /\A when (.+)/
|
|
1317
|
-
next unless cat
|
|
1318
|
-
next unless desc
|
|
1319
|
-
ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
|
|
1320
|
-
helps[cat] << [ws, desc]
|
|
1321
|
-
desc = nil
|
|
1322
|
-
cmds.concat ws
|
|
1323
|
-
when /\A\s+# (\s*\*.+)/
|
|
1324
|
-
if desc
|
|
1325
|
-
desc << "\n" + $1
|
|
1326
|
-
else
|
|
1327
|
-
desc = $1
|
|
1328
|
-
end
|
|
1329
|
-
end
|
|
1330
|
-
end
|
|
1331
|
-
@commands = cmds
|
|
1332
|
-
@helps = helps
|
|
1333
|
-
end
|
|
1334
|
-
|
|
1335
|
-
def self.helps
|
|
1336
|
-
(defined?(@helps) && @helps) || parse_help
|
|
1337
|
-
end
|
|
1338
|
-
|
|
1339
|
-
def self.commands
|
|
1340
|
-
(defined?(@commands) && @commands) || (parse_help; @commands)
|
|
1341
|
-
end
|
|
1342
|
-
|
|
1343
|
-
def self.help
|
|
1344
|
-
r = []
|
|
1345
|
-
self.helps.each{|cat, cmds|
|
|
1346
|
-
r << "### #{cat}"
|
|
1347
|
-
r << ''
|
|
1348
|
-
cmds.each{|ws, desc|
|
|
1349
|
-
r << desc
|
|
1350
|
-
}
|
|
1351
|
-
r << ''
|
|
1352
|
-
}
|
|
1353
|
-
r.join("\n")
|
|
1354
|
-
end
|
|
1355
|
-
|
|
1356
1576
|
class ::Module
|
|
1357
1577
|
undef method_added
|
|
1358
1578
|
def method_added mid; end
|
|
@@ -1408,4 +1628,69 @@ module DEBUGGER__
|
|
|
1408
1628
|
end
|
|
1409
1629
|
end
|
|
1410
1630
|
end
|
|
1631
|
+
|
|
1632
|
+
module ForkInterceptor
|
|
1633
|
+
def fork(&given_block)
|
|
1634
|
+
return super unless defined?(SESSION) && SESSION.active?
|
|
1635
|
+
|
|
1636
|
+
# before fork
|
|
1637
|
+
if CONFIG[:parent_on_fork]
|
|
1638
|
+
parent_hook = -> child_pid {
|
|
1639
|
+
# Do nothing
|
|
1640
|
+
}
|
|
1641
|
+
child_hook = -> {
|
|
1642
|
+
DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
|
|
1643
|
+
SESSION.deactivate
|
|
1644
|
+
}
|
|
1645
|
+
else
|
|
1646
|
+
parent_pid = Process.pid
|
|
1647
|
+
|
|
1648
|
+
parent_hook = -> child_pid {
|
|
1649
|
+
DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
|
|
1650
|
+
SESSION.deactivate
|
|
1651
|
+
|
|
1652
|
+
at_exit{
|
|
1653
|
+
trap(:SIGINT, :IGNORE)
|
|
1654
|
+
Process.waitpid(child_pid)
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
child_hook = -> {
|
|
1658
|
+
DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
|
1659
|
+
SESSION.activate on_fork: true
|
|
1660
|
+
}
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
if given_block
|
|
1664
|
+
new_block = proc {
|
|
1665
|
+
# after fork: child
|
|
1666
|
+
child_hook.call
|
|
1667
|
+
given_block.call
|
|
1668
|
+
}
|
|
1669
|
+
pid = super(&new_block)
|
|
1670
|
+
parent_hook.call(pid)
|
|
1671
|
+
pid
|
|
1672
|
+
else
|
|
1673
|
+
if pid = super
|
|
1674
|
+
# after fork: parent
|
|
1675
|
+
parent_hook.call pid
|
|
1676
|
+
else
|
|
1677
|
+
# after fork: child
|
|
1678
|
+
child_hook.call
|
|
1679
|
+
end
|
|
1680
|
+
|
|
1681
|
+
pid
|
|
1682
|
+
end
|
|
1683
|
+
end
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
class ::Object
|
|
1687
|
+
include ForkInterceptor
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
module ::Process
|
|
1691
|
+
class << self
|
|
1692
|
+
prepend ForkInterceptor
|
|
1693
|
+
end
|
|
1694
|
+
end
|
|
1411
1695
|
end
|
|
1696
|
+
|