debug 1.0.0.beta8 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +108 -106
- data/README.md +90 -30
- data/debug.gemspec +1 -0
- data/exe/rdbg +3 -6
- 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 -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 +32 -7
- data/lib/debug/session.rb +568 -274
- 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 +26 -25
- metadata +18 -2
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
|
@@ -246,7 +315,7 @@ module DEBUGGER__
|
|
246
315
|
end
|
247
316
|
else
|
248
317
|
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
|
249
|
-
line = @ui.readline
|
318
|
+
line = @ui.readline prompt
|
250
319
|
end
|
251
320
|
|
252
321
|
case line
|
@@ -280,27 +349,37 @@ module DEBUGGER__
|
|
280
349
|
|
281
350
|
# * `s[tep]`
|
282
351
|
# * Step in. Resume the program until next breakable point.
|
352
|
+
# * `s[tep] <n>`
|
353
|
+
# * Step in, resume the program at `<n>`th breakable point.
|
283
354
|
when 's', 'step'
|
284
355
|
cancel_auto_continue
|
285
|
-
|
356
|
+
check_postmortem
|
357
|
+
step_command :in, arg
|
286
358
|
|
287
359
|
# * `n[ext]`
|
288
360
|
# * Step over. Resume the program until next line.
|
361
|
+
# * `n[ext] <n>`
|
362
|
+
# * Step over, same as `step <n>`.
|
289
363
|
when 'n', 'next'
|
290
364
|
cancel_auto_continue
|
291
|
-
|
365
|
+
check_postmortem
|
366
|
+
step_command :next, arg
|
292
367
|
|
293
368
|
# * `fin[ish]`
|
294
369
|
# * Finish this frame. Resume the program until the current frame is finished.
|
370
|
+
# * `fin[ish] <n>`
|
371
|
+
# * Finish frames, same as `step <n>`.
|
295
372
|
when 'fin', 'finish'
|
296
373
|
cancel_auto_continue
|
297
|
-
|
374
|
+
check_postmortem
|
375
|
+
step_command :finish, arg
|
298
376
|
|
299
377
|
# * `c[ontinue]`
|
300
378
|
# * Resume the program.
|
301
379
|
when 'c', 'continue'
|
302
380
|
cancel_auto_continue
|
303
381
|
@tc << :continue
|
382
|
+
restart_all_threads
|
304
383
|
|
305
384
|
# * `q[uit]` or `Ctrl-D`
|
306
385
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
@@ -308,6 +387,7 @@ module DEBUGGER__
|
|
308
387
|
if ask 'Really quit?'
|
309
388
|
@ui.quit arg.to_i
|
310
389
|
@tc << :continue
|
390
|
+
restart_all_threads
|
311
391
|
else
|
312
392
|
return :retry
|
313
393
|
end
|
@@ -316,7 +396,7 @@ module DEBUGGER__
|
|
316
396
|
# * Same as q[uit] but without the confirmation prompt.
|
317
397
|
when 'q!', 'quit!'
|
318
398
|
@ui.quit arg.to_i
|
319
|
-
|
399
|
+
restart_all_threads
|
320
400
|
|
321
401
|
# * `kill`
|
322
402
|
# * Stop the debuggee process with `Kernal#exit!`.
|
@@ -354,6 +434,8 @@ module DEBUGGER__
|
|
354
434
|
# * break if: `<expr>` is true at any lines.
|
355
435
|
# * Note that this feature is super slow.
|
356
436
|
when 'b', 'break'
|
437
|
+
check_postmortem
|
438
|
+
|
357
439
|
if arg == nil
|
358
440
|
show_bps
|
359
441
|
return :retry
|
@@ -370,6 +452,7 @@ module DEBUGGER__
|
|
370
452
|
|
371
453
|
# skip
|
372
454
|
when 'bv'
|
455
|
+
check_postmortem
|
373
456
|
require 'json'
|
374
457
|
|
375
458
|
h = Hash.new{|h, k| h[k] = []}
|
@@ -396,8 +479,10 @@ module DEBUGGER__
|
|
396
479
|
# * `catch <Error>`
|
397
480
|
# * Set breakpoint on raising `<Error>`.
|
398
481
|
when 'catch'
|
482
|
+
check_postmortem
|
483
|
+
|
399
484
|
if arg
|
400
|
-
bp =
|
485
|
+
bp = repl_add_catch_breakpoint arg
|
401
486
|
show_bps bp if bp
|
402
487
|
else
|
403
488
|
show_bps
|
@@ -408,6 +493,8 @@ module DEBUGGER__
|
|
408
493
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
409
494
|
# * Note that this feature is super slow.
|
410
495
|
when 'wat', 'watch'
|
496
|
+
check_postmortem
|
497
|
+
|
411
498
|
if arg && arg.match?(/\A@\w+/)
|
412
499
|
@tc << [:breakpoint, :watch, arg]
|
413
500
|
else
|
@@ -420,15 +507,17 @@ module DEBUGGER__
|
|
420
507
|
# * `del[ete] <bpnum>`
|
421
508
|
# * delete specified breakpoint.
|
422
509
|
when 'del', 'delete'
|
510
|
+
check_postmortem
|
511
|
+
|
423
512
|
bp =
|
424
513
|
case arg
|
425
514
|
when nil
|
426
515
|
show_bps
|
427
516
|
if ask "Remove all breakpoints?", 'N'
|
428
|
-
|
517
|
+
delete_bp
|
429
518
|
end
|
430
519
|
when /\d+/
|
431
|
-
|
520
|
+
delete_bp arg.to_i
|
432
521
|
else
|
433
522
|
nil
|
434
523
|
end
|
@@ -501,25 +590,57 @@ module DEBUGGER__
|
|
501
590
|
|
502
591
|
@tc << [:show, :edit, arg]
|
503
592
|
|
504
|
-
# * `i[nfo]
|
593
|
+
# * `i[nfo]`
|
594
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
595
|
+
# * `i[nfo] l[ocal[s]]`
|
505
596
|
# * Show information about the current frame (local variables)
|
506
597
|
# * It includes `self` as `%self` and a return value as `%return`.
|
598
|
+
# * `i[nfo] i[var[s]]` or `i[nfo] instance`
|
599
|
+
# * Show information about insttance variables about `self`.
|
600
|
+
# * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
|
601
|
+
# * Show information about accessible constants except toplevel constants.
|
602
|
+
# * `i[nfo] g[lobal[s]]`
|
603
|
+
# * Show information about global variables
|
604
|
+
# * `i[nfo] ... </pattern/>`
|
605
|
+
# * Filter the output with `</pattern/>`.
|
507
606
|
# * `i[nfo] th[read[s]]`
|
508
607
|
# * Show all threads (same as `th[read]`).
|
509
608
|
when 'i', 'info'
|
510
|
-
|
609
|
+
if /\/(.+)\/\z/ =~ arg
|
610
|
+
pat = Regexp.compile($1)
|
611
|
+
sub = $~.pre_match.strip
|
612
|
+
else
|
613
|
+
sub = arg
|
614
|
+
end
|
615
|
+
|
616
|
+
case sub
|
511
617
|
when nil
|
512
|
-
@tc << [:show, :
|
513
|
-
when 'l',
|
514
|
-
@tc << [:show, :
|
618
|
+
@tc << [:show, :default, pat] # something useful
|
619
|
+
when 'l', /^locals?/
|
620
|
+
@tc << [:show, :locals, pat]
|
621
|
+
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
622
|
+
@tc << [:show, :ivars, pat]
|
623
|
+
when 'c', /^consts?/i, /^constants?/i
|
624
|
+
@tc << [:show, :consts, pat]
|
625
|
+
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
626
|
+
@tc << [:show, :globals, pat]
|
515
627
|
when 'th', /threads?/
|
516
628
|
thread_list
|
517
629
|
return :retry
|
518
630
|
else
|
631
|
+
@ui.puts "unrecognized argument for info command: #{arg}"
|
519
632
|
show_help 'info'
|
520
633
|
return :retry
|
521
634
|
end
|
522
635
|
|
636
|
+
# * `o[utline]` or `ls`
|
637
|
+
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
638
|
+
# * `o[utline] <expr>` or `ls <expr>`
|
639
|
+
# * Show you available methods and instance variables of the given object.
|
640
|
+
# * If the object is a class/module, it also lists its constants.
|
641
|
+
when 'outline', 'o', 'ls'
|
642
|
+
@tc << [:show, :outline, arg]
|
643
|
+
|
523
644
|
# * `display`
|
524
645
|
# * Show display setting.
|
525
646
|
# * `display <expr>`
|
@@ -598,6 +719,105 @@ module DEBUGGER__
|
|
598
719
|
# don't repeat irb command
|
599
720
|
@repl_prev_line = nil
|
600
721
|
|
722
|
+
### Trace
|
723
|
+
# * `trace`
|
724
|
+
# * Show available tracers list.
|
725
|
+
# * `trace line`
|
726
|
+
# * Add a line tracer. It indicates line events.
|
727
|
+
# * `trace call`
|
728
|
+
# * Add a call tracer. It indicate call/return events.
|
729
|
+
# * `trace exception`
|
730
|
+
# * Add an exception tracer. It indicates raising exceptions.
|
731
|
+
# * `trace object <expr>`
|
732
|
+
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
733
|
+
# * `trace ... </pattern/>`
|
734
|
+
# * Indicates only matched events to `</pattern/>` (RegExp).
|
735
|
+
# * `trace ... into: <file>`
|
736
|
+
# * Save trace information into: `<file>`.
|
737
|
+
# * `trace off <num>`
|
738
|
+
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
739
|
+
# * `trace off [line|call|pass]`
|
740
|
+
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
741
|
+
when 'trace'
|
742
|
+
if (re = /\s+into:\s*(.+)/) =~ arg
|
743
|
+
into = $1
|
744
|
+
arg.sub!(re, '')
|
745
|
+
end
|
746
|
+
|
747
|
+
if (re = /\s\/(.+)\/\z/) =~ arg
|
748
|
+
pattern = $1
|
749
|
+
arg.sub!(re, '')
|
750
|
+
end
|
751
|
+
|
752
|
+
case arg
|
753
|
+
when nil
|
754
|
+
@ui.puts 'Tracers:'
|
755
|
+
@tracers.each_with_index{|t, i|
|
756
|
+
@ui.puts "* \##{i} #{t}"
|
757
|
+
}
|
758
|
+
@ui.puts
|
759
|
+
return :retry
|
760
|
+
|
761
|
+
when /\Aline\z/
|
762
|
+
@tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
|
763
|
+
@ui.puts "Enable #{t.to_s}"
|
764
|
+
return :retry
|
765
|
+
|
766
|
+
when /\Acall\z/
|
767
|
+
@tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
|
768
|
+
@ui.puts "Enable #{t.to_s}"
|
769
|
+
return :retry
|
770
|
+
|
771
|
+
when /\Aexception\z/
|
772
|
+
@tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
773
|
+
@ui.puts "Enable #{t.to_s}"
|
774
|
+
return :retry
|
775
|
+
|
776
|
+
when /\Aobject\s+(.+)/
|
777
|
+
@tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
778
|
+
|
779
|
+
when /\Aoff\s+(\d+)\z/
|
780
|
+
if t = @tracers[$1.to_i]
|
781
|
+
t.disable
|
782
|
+
@ui.puts "Disable #{t.to_s}"
|
783
|
+
else
|
784
|
+
@ui.puts "Unmatched: #{$1}"
|
785
|
+
end
|
786
|
+
return :retry
|
787
|
+
|
788
|
+
when /\Aoff(\s+(line|call|exception|object))?\z/
|
789
|
+
@tracers.each{|t|
|
790
|
+
if $2.nil? || t.type == $2
|
791
|
+
t.disable
|
792
|
+
@ui.puts "Disable #{t.to_s}"
|
793
|
+
end
|
794
|
+
}
|
795
|
+
return :retry
|
796
|
+
|
797
|
+
else
|
798
|
+
@ui.puts "Unknown trace option: #{arg.inspect}"
|
799
|
+
return :retry
|
800
|
+
end
|
801
|
+
|
802
|
+
# Record
|
803
|
+
# * `record`
|
804
|
+
# * Show recording status.
|
805
|
+
# * `record [on|off]`
|
806
|
+
# * Start/Stop recording.
|
807
|
+
# * `step back`
|
808
|
+
# * Start replay. Step back with the last execution log.
|
809
|
+
# * `s[tep]` does stepping forward with the last log.
|
810
|
+
# * `step reset`
|
811
|
+
# * Stop replay .
|
812
|
+
when 'record'
|
813
|
+
case arg
|
814
|
+
when nil, 'on', 'off'
|
815
|
+
@tc << [:record, arg&.to_sym]
|
816
|
+
else
|
817
|
+
@ui.puts "unknown command: #{arg}"
|
818
|
+
return :retry
|
819
|
+
end
|
820
|
+
|
601
821
|
### Thread control
|
602
822
|
|
603
823
|
# * `th[read]`
|
@@ -630,13 +850,28 @@ module DEBUGGER__
|
|
630
850
|
config_command arg
|
631
851
|
return :retry
|
632
852
|
|
853
|
+
# * `source <file>`
|
854
|
+
# * Evaluate lines in `<file>` as debug commands.
|
855
|
+
when 'source'
|
856
|
+
if arg
|
857
|
+
begin
|
858
|
+
cmds = File.readlines(path = File.expand_path(arg))
|
859
|
+
add_preset_commands path, cmds, kick: true, continue: false
|
860
|
+
rescue Errno::ENOENT
|
861
|
+
@ui.puts "File not found: #{arg}"
|
862
|
+
end
|
863
|
+
else
|
864
|
+
show_help 'source'
|
865
|
+
end
|
866
|
+
return :retry
|
867
|
+
|
633
868
|
### Help
|
634
869
|
|
635
870
|
# * `h[elp]`
|
636
871
|
# * Show help for all commands.
|
637
872
|
# * `h[elp] <command>`
|
638
873
|
# * Show help for the given command.
|
639
|
-
when 'h', 'help'
|
874
|
+
when 'h', 'help', '?'
|
640
875
|
if arg
|
641
876
|
show_help arg
|
642
877
|
else
|
@@ -646,21 +881,56 @@ module DEBUGGER__
|
|
646
881
|
|
647
882
|
### END
|
648
883
|
else
|
649
|
-
@
|
884
|
+
@tc << [:eval, :pp, line]
|
885
|
+
=begin
|
650
886
|
@repl_prev_line = nil
|
887
|
+
@ui.puts "unknown command: #{line}"
|
888
|
+
begin
|
889
|
+
require 'did_you_mean'
|
890
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
891
|
+
correction = spell_checker.correct(line.split(/\s/).first || '')
|
892
|
+
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
893
|
+
rescue LoadError
|
894
|
+
# Don't use D
|
895
|
+
end
|
651
896
|
return :retry
|
897
|
+
=end
|
652
898
|
end
|
653
899
|
|
654
900
|
rescue Interrupt
|
655
901
|
return :retry
|
656
902
|
rescue SystemExit
|
657
903
|
raise
|
904
|
+
rescue PostmortemError => e
|
905
|
+
@ui.puts e.message
|
906
|
+
return :retry
|
658
907
|
rescue Exception => e
|
659
908
|
@ui.puts "[REPL ERROR] #{e.inspect}"
|
660
909
|
@ui.puts e.backtrace.map{|e| ' ' + e}
|
661
910
|
return :retry
|
662
911
|
end
|
663
912
|
|
913
|
+
def step_command type, arg
|
914
|
+
case arg
|
915
|
+
when nil
|
916
|
+
@tc << [:step, type]
|
917
|
+
restart_all_threads
|
918
|
+
when /\A\d+\z/
|
919
|
+
@tc << [:step, type, arg.to_i]
|
920
|
+
restart_all_threads
|
921
|
+
when /\Aback\z/, /\Areset\z/
|
922
|
+
if type != :in
|
923
|
+
@ui.puts "only `step #{arg}` is supported."
|
924
|
+
:retry
|
925
|
+
else
|
926
|
+
@tc << [:step, arg.to_sym]
|
927
|
+
end
|
928
|
+
else
|
929
|
+
@ui.puts "Unknown option: #{arg}"
|
930
|
+
:retry
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
664
934
|
def config_show key
|
665
935
|
key = key.to_sym
|
666
936
|
if CONFIG_SET[key]
|
@@ -682,9 +952,9 @@ module DEBUGGER__
|
|
682
952
|
if CONFIG_SET[key = key.to_sym]
|
683
953
|
begin
|
684
954
|
if append
|
685
|
-
|
955
|
+
CONFIG.append_config(key, val)
|
686
956
|
else
|
687
|
-
|
957
|
+
CONFIG[key] = val
|
688
958
|
end
|
689
959
|
rescue => e
|
690
960
|
@ui.puts e.message
|
@@ -703,7 +973,7 @@ module DEBUGGER__
|
|
703
973
|
|
704
974
|
when /\Aunset\s+(.+)\z/
|
705
975
|
if CONFIG_SET[key = $1.to_sym]
|
706
|
-
|
976
|
+
CONFIG[key] = nil
|
707
977
|
end
|
708
978
|
config_show key
|
709
979
|
|
@@ -758,51 +1028,7 @@ module DEBUGGER__
|
|
758
1028
|
end
|
759
1029
|
end
|
760
1030
|
|
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
|
1031
|
+
# breakpoint management
|
806
1032
|
|
807
1033
|
def iterate_bps
|
808
1034
|
deleted_bps = []
|
@@ -834,7 +1060,29 @@ module DEBUGGER__
|
|
834
1060
|
nil
|
835
1061
|
end
|
836
1062
|
|
837
|
-
def
|
1063
|
+
def rehash_bps
|
1064
|
+
bps = @bps.values
|
1065
|
+
@bps.clear
|
1066
|
+
bps.each{|bp|
|
1067
|
+
add_bp bp
|
1068
|
+
}
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
def add_bp bp
|
1072
|
+
# don't repeat commands that add breakpoints
|
1073
|
+
@repl_prev_line = nil
|
1074
|
+
|
1075
|
+
if @bps.has_key? bp.key
|
1076
|
+
unless bp.duplicable?
|
1077
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
1078
|
+
bp.disable
|
1079
|
+
end
|
1080
|
+
else
|
1081
|
+
@bps[bp.key] = bp
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def delete_bp arg = nil
|
838
1086
|
case arg
|
839
1087
|
when nil
|
840
1088
|
@bps.each{|key, bp| bp.delete}
|
@@ -873,14 +1121,14 @@ module DEBUGGER__
|
|
873
1121
|
|
874
1122
|
case expr[:sig]
|
875
1123
|
when /\A(\d+)\z/
|
876
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond:
|
1124
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
|
877
1125
|
when /\A(.+)[:\s+](\d+)\z/
|
878
|
-
add_line_breakpoint $1, $2.to_i, cond:
|
1126
|
+
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
879
1127
|
when /\A(.+)([\.\#])(.+)\z/
|
880
|
-
@tc << [:breakpoint, :method, $1, $2, $3,
|
1128
|
+
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
|
881
1129
|
return :noretry
|
882
1130
|
when nil
|
883
|
-
add_check_breakpoint
|
1131
|
+
add_check_breakpoint cond
|
884
1132
|
else
|
885
1133
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
886
1134
|
@ui.puts
|
@@ -888,6 +1136,29 @@ module DEBUGGER__
|
|
888
1136
|
end
|
889
1137
|
end
|
890
1138
|
|
1139
|
+
def repl_add_catch_breakpoint arg
|
1140
|
+
expr = parse_break arg.strip
|
1141
|
+
cond = expr[:if]
|
1142
|
+
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1143
|
+
|
1144
|
+
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
|
1145
|
+
add_bp bp
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
def add_check_breakpoint expr
|
1149
|
+
bp = CheckBreakpoint.new(expr)
|
1150
|
+
add_bp bp
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
def add_line_breakpoint file, line, **kw
|
1154
|
+
file = resolve_path(file)
|
1155
|
+
bp = LineBreakpoint.new(file, line, **kw)
|
1156
|
+
|
1157
|
+
add_bp bp
|
1158
|
+
rescue Errno::ENOENT => e
|
1159
|
+
@ui.puts e.message
|
1160
|
+
end
|
1161
|
+
|
891
1162
|
# threads
|
892
1163
|
|
893
1164
|
def update_thread_list
|
@@ -896,17 +1167,15 @@ module DEBUGGER__
|
|
896
1167
|
unmanaged = []
|
897
1168
|
|
898
1169
|
list.each{|th|
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
# ignore
|
904
|
-
when @th_clients.has_key?(th)
|
905
|
-
thcs << @th_clients[th]
|
1170
|
+
if thc = @th_clients[th]
|
1171
|
+
if !thc.management?
|
1172
|
+
thcs << thc
|
1173
|
+
end
|
906
1174
|
else
|
907
1175
|
unmanaged << th
|
908
1176
|
end
|
909
1177
|
}
|
1178
|
+
|
910
1179
|
return thcs.sort_by{|thc| thc.id}, unmanaged
|
911
1180
|
end
|
912
1181
|
|
@@ -933,7 +1202,7 @@ module DEBUGGER__
|
|
933
1202
|
thcs, _unmanaged_ths = update_thread_list
|
934
1203
|
|
935
1204
|
if tc = thcs[n]
|
936
|
-
if tc.
|
1205
|
+
if tc.waiting?
|
937
1206
|
@tc = tc
|
938
1207
|
else
|
939
1208
|
@ui.puts "#{tc.thread} is not controllable yet."
|
@@ -947,11 +1216,11 @@ module DEBUGGER__
|
|
947
1216
|
end
|
948
1217
|
|
949
1218
|
def setup_threads
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
1219
|
+
@th_clients = {}
|
1220
|
+
|
1221
|
+
Thread.list.each{|th|
|
1222
|
+
thread_client_create(th)
|
1223
|
+
}
|
955
1224
|
end
|
956
1225
|
|
957
1226
|
def on_thread_begin th
|
@@ -963,8 +1232,7 @@ module DEBUGGER__
|
|
963
1232
|
end
|
964
1233
|
end
|
965
1234
|
|
966
|
-
def thread_client
|
967
|
-
thr = Thread.current
|
1235
|
+
def thread_client thr = Thread.current
|
968
1236
|
if @th_clients.has_key? thr
|
969
1237
|
@th_clients[thr]
|
970
1238
|
else
|
@@ -972,84 +1240,67 @@ module DEBUGGER__
|
|
972
1240
|
end
|
973
1241
|
end
|
974
1242
|
|
975
|
-
def
|
976
|
-
|
1243
|
+
private def thread_stopper
|
1244
|
+
@thread_stopper ||= TracePoint.new(:line) do
|
1245
|
+
# run on each thread
|
1246
|
+
tc = ThreadClient.current
|
1247
|
+
next if tc.management?
|
1248
|
+
next unless tc.running?
|
1249
|
+
next if tc == @tc
|
977
1250
|
|
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
|
1251
|
+
tc.on_pause
|
1001
1252
|
end
|
1002
1253
|
end
|
1003
1254
|
|
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
|
1255
|
+
private def running_thread_clients_count
|
1256
|
+
@th_clients.count{|th, tc|
|
1257
|
+
next if tc.management?
|
1258
|
+
next unless tc.running?
|
1259
|
+
true
|
1260
|
+
}
|
1015
1261
|
end
|
1016
1262
|
|
1017
|
-
|
1263
|
+
private def waiting_thread_clients
|
1264
|
+
@th_clients.map{|th, tc|
|
1265
|
+
next if tc.management?
|
1266
|
+
next unless tc.waiting?
|
1267
|
+
tc
|
1268
|
+
}.compact
|
1269
|
+
end
|
1018
1270
|
|
1019
|
-
def
|
1020
|
-
|
1021
|
-
@repl_prev_line = nil
|
1271
|
+
private def stop_all_threads
|
1272
|
+
return if running_thread_clients_count == 0
|
1022
1273
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
@ui.puts "duplicated breakpoint: #{bp}"
|
1026
|
-
bp.disable
|
1027
|
-
end
|
1028
|
-
else
|
1029
|
-
@bps[bp.key] = bp
|
1030
|
-
end
|
1274
|
+
stopper = thread_stopper
|
1275
|
+
stopper.enable unless stopper.enabled?
|
1031
1276
|
end
|
1032
1277
|
|
1033
|
-
def
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1278
|
+
private def restart_all_threads
|
1279
|
+
stopper = thread_stopper
|
1280
|
+
stopper.disable if stopper.enabled?
|
1281
|
+
|
1282
|
+
waiting_thread_clients.each{|tc|
|
1283
|
+
next if @tc == tc
|
1284
|
+
tc << :continue
|
1038
1285
|
}
|
1286
|
+
@tc = nil
|
1039
1287
|
end
|
1040
1288
|
|
1041
|
-
|
1042
|
-
@bps.has_key? [file, line]
|
1043
|
-
end
|
1289
|
+
## event
|
1044
1290
|
|
1045
|
-
def
|
1046
|
-
|
1047
|
-
|
1048
|
-
end
|
1291
|
+
def on_load iseq, src
|
1292
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
1293
|
+
@sr.add iseq, src
|
1049
1294
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1295
|
+
pending_line_breakpoints = @bps.find_all do |key, bp|
|
1296
|
+
LineBreakpoint === bp && !bp.iseq
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
pending_line_breakpoints.each do |_key, bp|
|
1300
|
+
if bp.path == (iseq.absolute_path || iseq.path)
|
1301
|
+
bp.try_activate
|
1302
|
+
end
|
1303
|
+
end
|
1053
1304
|
end
|
1054
1305
|
|
1055
1306
|
def resolve_path file
|
@@ -1070,23 +1321,6 @@ module DEBUGGER__
|
|
1070
1321
|
raise
|
1071
1322
|
end
|
1072
1323
|
|
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
1324
|
def method_added tp
|
1091
1325
|
b = tp.binding
|
1092
1326
|
if var_name = b.local_variables.first
|
@@ -1115,10 +1349,55 @@ module DEBUGGER__
|
|
1115
1349
|
@ui.width
|
1116
1350
|
end
|
1117
1351
|
|
1118
|
-
def
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1352
|
+
def check_postmortem
|
1353
|
+
if @postmortem
|
1354
|
+
raise PostmortemError, "Can not use this command on postmortem mode."
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
def enter_postmortem_session frames
|
1359
|
+
@postmortem = true
|
1360
|
+
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
1361
|
+
ensure
|
1362
|
+
@postmortem = false
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def postmortem=(is_enable)
|
1366
|
+
if is_enable
|
1367
|
+
unless @postmortem_hook
|
1368
|
+
@postmortem_hook = TracePoint.new(:raise){|tp|
|
1369
|
+
exc = tp.raised_exception
|
1370
|
+
frames = DEBUGGER__.capture_frames(__dir__)
|
1371
|
+
exc.instance_variable_set(:@postmortem_frames, frames)
|
1372
|
+
}
|
1373
|
+
at_exit{
|
1374
|
+
@postmortem_hook.disable
|
1375
|
+
if CONFIG[:postmortem] && (exc = $!) != nil
|
1376
|
+
exc = exc.cause while exc.cause
|
1377
|
+
|
1378
|
+
begin
|
1379
|
+
@ui.puts "Enter postmortem mode with #{exc.inspect}"
|
1380
|
+
@ui.puts exc.backtrace.map{|e| ' ' + e}
|
1381
|
+
@ui.puts "\n"
|
1382
|
+
|
1383
|
+
enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
|
1384
|
+
rescue SystemExit
|
1385
|
+
exit!
|
1386
|
+
rescue Exception => e
|
1387
|
+
@ui = STDERR unless @ui
|
1388
|
+
@ui.puts "Error while postmortem console: #{e.inspect}"
|
1389
|
+
end
|
1390
|
+
end
|
1391
|
+
}
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
if !@postmortem_hook.enabled?
|
1395
|
+
@postmortem_hook.enable
|
1396
|
+
end
|
1397
|
+
else
|
1398
|
+
if @postmortem_hook && @postmortem_hook.enabled?
|
1399
|
+
@postmortem_hook.disable
|
1400
|
+
end
|
1122
1401
|
end
|
1123
1402
|
end
|
1124
1403
|
end
|
@@ -1165,18 +1444,18 @@ module DEBUGGER__
|
|
1165
1444
|
# start methods
|
1166
1445
|
|
1167
1446
|
def self.start nonstop: false, **kw
|
1168
|
-
set_config(kw)
|
1447
|
+
CONFIG.set_config(**kw)
|
1169
1448
|
|
1170
1449
|
unless defined? SESSION
|
1171
|
-
require_relative '
|
1172
|
-
initialize_session
|
1450
|
+
require_relative 'local'
|
1451
|
+
initialize_session UI_LocalConsole.new
|
1173
1452
|
end
|
1174
1453
|
|
1175
1454
|
setup_initial_suspend unless nonstop
|
1176
1455
|
end
|
1177
1456
|
|
1178
|
-
def self.open host: nil, port:
|
1179
|
-
set_config(kw)
|
1457
|
+
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1458
|
+
CONFIG.set_config(**kw)
|
1180
1459
|
|
1181
1460
|
if port
|
1182
1461
|
open_tcp host: host, port: port, nonstop: nonstop
|
@@ -1186,7 +1465,7 @@ module DEBUGGER__
|
|
1186
1465
|
end
|
1187
1466
|
|
1188
1467
|
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
1189
|
-
set_config(kw)
|
1468
|
+
CONFIG.set_config(**kw)
|
1190
1469
|
require_relative 'server'
|
1191
1470
|
|
1192
1471
|
if defined? SESSION
|
@@ -1199,7 +1478,7 @@ module DEBUGGER__
|
|
1199
1478
|
end
|
1200
1479
|
|
1201
1480
|
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1202
|
-
set_config(kw)
|
1481
|
+
CONFIG.set_config(**kw)
|
1203
1482
|
require_relative 'server'
|
1204
1483
|
|
1205
1484
|
if defined? SESSION
|
@@ -1214,13 +1493,13 @@ module DEBUGGER__
|
|
1214
1493
|
# boot utilities
|
1215
1494
|
|
1216
1495
|
def self.setup_initial_suspend
|
1217
|
-
if
|
1496
|
+
if !CONFIG[:nonstop]
|
1218
1497
|
if loc = ::DEBUGGER__.require_location
|
1219
|
-
# require 'debug/
|
1498
|
+
# require 'debug/start' or 'debug'
|
1220
1499
|
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
1221
1500
|
else
|
1222
1501
|
# -r
|
1223
|
-
add_line_breakpoint $0,
|
1502
|
+
add_line_breakpoint $0, 0, oneshot: true, hook_call: false
|
1224
1503
|
end
|
1225
1504
|
end
|
1226
1505
|
end
|
@@ -1258,10 +1537,10 @@ module DEBUGGER__
|
|
1258
1537
|
[[File.expand_path('~/.rdbgrc'), true],
|
1259
1538
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
1260
1539
|
# ['./.rdbgrc', true], # disable because of security concern
|
1261
|
-
[
|
1540
|
+
[CONFIG[:init_script], false],
|
1262
1541
|
].each{|(path, rc)|
|
1263
1542
|
next unless path
|
1264
|
-
next if rc &&
|
1543
|
+
next if rc && CONFIG[:no_rc] # ignore rc
|
1265
1544
|
|
1266
1545
|
if File.file? path
|
1267
1546
|
if path.end_with?('.rb')
|
@@ -1275,62 +1554,12 @@ module DEBUGGER__
|
|
1275
1554
|
}
|
1276
1555
|
|
1277
1556
|
# given debug commands
|
1278
|
-
if
|
1279
|
-
cmds =
|
1557
|
+
if CONFIG[:commands]
|
1558
|
+
cmds = CONFIG[:commands].split(';;')
|
1280
1559
|
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
1281
1560
|
end
|
1282
1561
|
end
|
1283
1562
|
|
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
1563
|
class ::Module
|
1335
1564
|
undef method_added
|
1336
1565
|
def method_added mid; end
|
@@ -1386,4 +1615,69 @@ module DEBUGGER__
|
|
1386
1615
|
end
|
1387
1616
|
end
|
1388
1617
|
end
|
1618
|
+
|
1619
|
+
module ForkInterceptor
|
1620
|
+
def fork(&given_block)
|
1621
|
+
return super unless defined?(SESSION) && SESSION.active?
|
1622
|
+
|
1623
|
+
# before fork
|
1624
|
+
if CONFIG[:parent_on_fork]
|
1625
|
+
parent_hook = -> child_pid {
|
1626
|
+
# Do nothing
|
1627
|
+
}
|
1628
|
+
child_hook = -> {
|
1629
|
+
DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
|
1630
|
+
SESSION.deactivate
|
1631
|
+
}
|
1632
|
+
else
|
1633
|
+
parent_pid = Process.pid
|
1634
|
+
|
1635
|
+
parent_hook = -> child_pid {
|
1636
|
+
DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
|
1637
|
+
SESSION.deactivate
|
1638
|
+
|
1639
|
+
at_exit{
|
1640
|
+
trap(:SIGINT, :IGNORE)
|
1641
|
+
Process.waitpid(child_pid)
|
1642
|
+
}
|
1643
|
+
}
|
1644
|
+
child_hook = -> {
|
1645
|
+
DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
1646
|
+
SESSION.activate on_fork: true
|
1647
|
+
}
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
if given_block
|
1651
|
+
new_block = proc {
|
1652
|
+
# after fork: child
|
1653
|
+
child_hook.call
|
1654
|
+
given_block.call
|
1655
|
+
}
|
1656
|
+
pid = super(&new_block)
|
1657
|
+
parent_hook.call(pid)
|
1658
|
+
pid
|
1659
|
+
else
|
1660
|
+
if pid = super
|
1661
|
+
# after fork: parent
|
1662
|
+
parent_hook.call pid
|
1663
|
+
else
|
1664
|
+
# after fork: child
|
1665
|
+
child_hook.call
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
pid
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
class ::Object
|
1674
|
+
include ForkInterceptor
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
module ::Process
|
1678
|
+
class << self
|
1679
|
+
prepend ForkInterceptor
|
1680
|
+
end
|
1681
|
+
end
|
1389
1682
|
end
|
1683
|
+
|